diff --git a/.gitignore b/.gitignore index b0b2aafe151..805ea28a8f0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,2 @@ /_build -/bundles/DoctrineFixturesBundle -/bundles/DoctrineMigrationsBundle -/bundles/DoctrineMongoDBBundle -/bundles/SensioFrameworkExtraBundle -/bundles/SensioGeneratorBundle -/cmf /_exts diff --git a/.travis.yml b/.travis.yml index 73c2a7043f4..73bca138115 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,8 +3,9 @@ language: python python: - "2.7" +sudo: false + install: - - "bash install.sh" - "pip install -q -r requirements.txt --use-mirrors" script: sphinx-build -nW -b html -d _build/doctrees . _build/html diff --git a/_exts b/_exts index 03bc1c60172..52f7bd2216c 160000 --- a/_exts +++ b/_exts @@ -1 +1 @@ -Subproject commit 03bc1c60172a280619e3476f22b111b4a187895d +Subproject commit 52f7bd2216cc22ef52494f346c5643bb2a74513f diff --git a/best_practices/business-logic.rst b/best_practices/business-logic.rst new file mode 100644 index 00000000000..b15903be992 --- /dev/null +++ b/best_practices/business-logic.rst @@ -0,0 +1,341 @@ +Organizing Your Business Logic +============================== + +In computer software, **business logic** or domain logic is "the part of the +program that encodes the real-world business rules that determine how data can +be created, displayed, stored, and changed" (read `full definition`_). + +In Symfony applications, business logic is all the custom code you write for +your app that's not specific to the framework (e.g. routing and controllers). +Domain classes, Doctrine entities and regular PHP classes that are used as +services are good examples of business logic. + +For most projects, you should store everything inside the AppBundle. +Inside here, you can create whatever directories you want to organize things: + +.. code-block:: text + + symfony2-project/ + ├─ app/ + ├─ src/ + │ └─ AppBundle/ + │ └─ Utils/ + │ └─ MyClass.php + ├─ vendor/ + └─ web/ + +Storing Classes Outside of the Bundle? +-------------------------------------- + +But there's no technical reason for putting business logic inside of a bundle. +If you like, you can create your own namespace inside the ``src/`` directory +and put things there: + +.. code-block:: text + + symfony2-project/ + ├─ app/ + ├─ src/ + │ ├─ Acme/ + │ │ └─ Utils/ + │ │ └─ MyClass.php + │ └─ AppBundle/ + ├─ vendor/ + └─ web/ + +.. tip:: + + The recommended approach of using the ``AppBundle/`` directory is for + simplicity. If you're advanced enough to know what needs to live in + a bundle and what can live outside of one, then feel free to do that. + +Services: Naming and Format +--------------------------- + +The blog application needs a utility that can transform a post title (e.g. +"Hello World") into a slug (e.g. "hello-world"). The slug will be used as +part of the post URL. + +Let's, create a new ``Slugger`` class inside ``src/AppBundle/Utils/`` and +add the following ``slugify()`` method: + +.. code-block:: php + + // src/AppBundle/Utils/Slugger.php + namespace AppBundle\Utils; + + class Slugger + { + public function slugify($string) + { + return preg_replace( + '/[^a-z0-9]/', '-', strtolower(trim(strip_tags($string))) + ); + } + } + +Next, define a new service for that class. + +.. code-block:: yaml + + # app/config/services.yml + services: + # keep your service names short + app.slugger: + class: AppBundle\Utils\Slugger + +Traditionally, the naming convention for a service involved following the +class name and location to avoid name collisions. Thus, the service +*would have been* called ``app.utils.slugger``. But by using short service names, +your code will be easier to read and use. + +.. best-practice:: + + The name of your application's services should be as short as possible, + but unique enough that you can search your project for the service if + you ever need to. + +Now you can use the custom slugger in any controller class, such as the +``AdminController``: + +.. code-block:: php + + public function createAction(Request $request) + { + // ... + + if ($form->isSubmitted() && $form->isValid()) { + $slug = $this->get('app.slugger')->slugify($post->getTitle()); + $post->setSlug($slug); + + // ... + } + } + +Service Format: YAML +-------------------- + +In the previous section, YAML was used to define the service. + +.. best-practice:: + + Use the YAML format to define your own services. + +This is controversial, and in our experience, YAML and XML usage is evenly +distributed among developers, with a slight preference towards YAML. +Both formats have the same performance, so this is ultimately a matter of +personal taste. + +We recommend YAML because it's friendly to newcomers and concise. You can +of course use whatever format you like. + +Service: No Class Parameter +--------------------------- + +You may have noticed that the previous service definition doesn't configure +the class namespace as a parameter: + +.. code-block:: yaml + + # app/config/services.yml + + # service definition with class namespace as parameter + parameters: + slugger.class: AppBundle\Utils\Slugger + + services: + app.slugger: + class: "%slugger.class%" + +This practice is cumbersome and completely unnecessary for your own services: + +.. best-practice:: + + Don't define parameters for the classes of your services. + +This practice was wrongly adopted from third-party bundles. When Symfony +introduced its service container, some developers used this technique to easily +allow overriding services. However, overriding a service by just changing its +class name is a very rare use case because, frequently, the new service has +different constructor arguments. + +Using a Persistence Layer +------------------------- + +Symfony is an HTTP framework that only cares about generating an HTTP response +for each HTTP request. That's why Symfony doesn't provide a way to talk to +a persistence layer (e.g. database, external API). You can choose whatever +library or strategy you want for this. + +In practice, many Symfony applications rely on the independent +`Doctrine project`_ to define their model using entities and repositories. +Just like with business logic, we recommend storing Doctrine entities in the +AppBundle. + +The three entities defined by our sample blog application are a good example: + +.. code-block:: text + + symfony2-project/ + ├─ ... + └─ src/ + └─ AppBundle/ + └─ Entity/ + ├─ Comment.php + ├─ Post.php + └─ User.php + +.. tip:: + + If you're more advanced, you can of course store them under your own + namespace in ``src/``. + +Doctrine Mapping Information +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Doctrine Entities are plain PHP objects that you store in some "database". +Doctrine only knows about your entities through the mapping metadata configured +for your model classes. Doctrine supports four metadata formats: YAML, XML, +PHP and annotations. + +.. best-practice:: + + Use annotations to define the mapping information of the Doctrine entities. + +Annotations are by far the most convenient and agile way of setting up and +looking for mapping information: + +.. code-block:: php + + namespace AppBundle\Entity; + + use Doctrine\ORM\Mapping as ORM; + use Doctrine\Common\Collections\ArrayCollection; + + /** + * @ORM\Entity + */ + class Post + { + const NUM_ITEMS = 10; + + /** + * @ORM\Id + * @ORM\GeneratedValue + * @ORM\Column(type="integer") + */ + private $id; + + /** + * @ORM\Column(type="string") + */ + private $title; + + /** + * @ORM\Column(type="string") + */ + private $slug; + + /** + * @ORM\Column(type="text") + */ + private $content; + + /** + * @ORM\Column(type="string") + */ + private $authorEmail; + + /** + * @ORM\Column(type="datetime") + */ + private $publishedAt; + + /** + * @ORM\OneToMany( + * targetEntity="Comment", + * mappedBy="post", + * orphanRemoval=true + * ) + * @ORM\OrderBy({"publishedAt" = "ASC"}) + */ + private $comments; + + public function __construct() + { + $this->publishedAt = new \DateTime(); + $this->comments = new ArrayCollection(); + } + + // getters and setters ... + } + +All formats have the same performance, so this is once again ultimately a +matter of taste. + +Data Fixtures +~~~~~~~~~~~~~ + +As fixtures support is not enabled by default in Symfony, you should execute +the following command to install the Doctrine fixtures bundle: + +.. code-block:: bash + + $ composer require "doctrine/doctrine-fixtures-bundle" + +Then, enable the bundle in ``AppKernel.php``, but only for the ``dev`` and +``test`` environments: + +.. code-block:: php + + use Symfony\Component\HttpKernel\Kernel; + + class AppKernel extends Kernel + { + public function registerBundles() + { + $bundles = array( + // ... + ); + + if (in_array($this->getEnvironment(), array('dev', 'test'))) { + // ... + $bundles[] = new Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle(); + } + + return $bundles; + } + + // ... + } + +We recommend creating just *one* `fixture class`_ for simplicity, though +you're welcome to have more if that class gets quite large. + +Assuming you have at least one fixtures class and that the database access +is configured properly, you can load your fixtures by executing the following +command: + +.. code-block:: bash + + $ php app/console doctrine:fixtures:load + + Careful, database will be purged. Do you want to continue Y/N ? Y + > purging database + > loading AppBundle\DataFixtures\ORM\LoadFixtures + +Coding Standards +---------------- + +The Symfony source code follows the `PSR-1`_ and `PSR-2`_ coding standards that +were defined by the PHP community. You can learn more about +:doc:`the Symfony Coding standards ` and even +use the `PHP-CS-Fixer`_, which is a command-line utility that can fix the +coding standards of an entire codebase in a matter of seconds. + +.. _`full definition`: http://en.wikipedia.org/wiki/Business_logic +.. _`Doctrine project`: http://www.doctrine-project.org/ +.. _`fixture class`: http://symfony.com/doc/current/bundles/DoctrineFixturesBundle/index.html#writing-simple-fixtures +.. _`PSR-1`: http://www.php-fig.org/psr/psr-1/ +.. _`PSR-2`: http://www.php-fig.org/psr/psr-2/ +.. _`PHP-CS-Fixer`: https://github.com/FriendsOfPHP/PHP-CS-Fixer diff --git a/best_practices/configuration.rst b/best_practices/configuration.rst new file mode 100644 index 00000000000..df9779b121e --- /dev/null +++ b/best_practices/configuration.rst @@ -0,0 +1,182 @@ +Configuration +============= + +Configuration usually involves different application parts (such as infrastructure +and security credentials) and different environments (development, production). +That's why Symfony recommends that you split the application configuration into +three parts. + +.. _config-parameters.yml: + +Infrastructure-Related Configuration +------------------------------------ + +.. best-practice:: + + Define the infrastructure-related configuration options in the + ``app/config/parameters.yml`` file. + +The default ``parameters.yml`` file follows this recommendation and defines the +options related to the database and mail server infrastructure: + +.. code-block:: yaml + + # app/config/parameters.yml + parameters: + database_driver: pdo_mysql + database_host: 127.0.0.1 + database_port: ~ + database_name: symfony + database_user: root + database_password: ~ + + mailer_transport: smtp + mailer_host: 127.0.0.1 + mailer_user: ~ + mailer_password: ~ + + # ... + +These options aren't defined inside the ``app/config/config.yml`` file because +they have nothing to do with the application's behavior. In other words, your +application doesn't care about the location of your database or the credentials +to access to it, as long as the database is correctly configured. + +Canonical Parameters +~~~~~~~~~~~~~~~~~~~~ + +.. best-practice:: + + Define all your application's parameters in the + ``app/config/parameters.yml.dist`` file. + +Since version 2.3, Symfony includes a configuration file called ``parameters.yml.dist``, +which stores the canonical list of configuration parameters for the application. + +Whenever a new configuration parameter is defined for the application, you +should also add it to this file and submit the changes to your version control +system. Then, whenever a developer updates the project or deploys it to a server, +Symfony will check if there is any difference between the canonical +``parameters.yml.dist`` file and your local ``parameters.yml`` file. If there +is a difference, Symfony will ask you to provide a value for the new parameter +and it will add it to your local ``parameters.yml`` file. + +Application-Related Configuration +--------------------------------- + +.. best-practice:: + + Define the application behavior related configuration options in the + ``app/config/config.yml`` file. + +The ``config.yml`` file contains the options used by the application to modify +its behavior, such as the sender of email notifications, or the enabled +`feature toggles`_. Defining these values in ``parameters.yml`` file would +add an extra layer of configuration that's not needed because you don't need +or want these configuration values to change on each server. + +The configuration options defined in the ``config.yml`` file usually vary from +one :doc:`environment ` to another. That's +why Symfony already includes ``app/config/config_dev.yml`` and ``app/config/config_prod.yml`` +files so that you can override specific values for each environment. + +Constants vs Configuration Options +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +One of the most common errors when defining application configuration is to +create new options for values that never change, such as the number of items for +paginated results. + +.. best-practice:: + + Use constants to define configuration options that rarely change. + +The traditional approach for defining configuration options has caused many +Symfony apps to include an option like the following, which would be used +to control the number of posts to display on the blog homepage: + +.. code-block:: yaml + + # app/config/config.yml + parameters: + homepage.num_items: 10 + +If you ask yourself when the last time was that you changed the value of +*any* option like this, odds are that you *never* have. Creating a configuration +option for a value that you are never going to configure just isn't necessary. +Our recommendation is to define these values as constants in your application. +You could, for example, define a ``NUM_ITEMS`` constant in the ``Post`` entity: + +.. code-block:: php + + // src/AppBundle/Entity/Post.php + namespace AppBundle\Entity; + + class Post + { + const NUM_ITEMS = 10; + + // ... + } + +The main advantage of defining constants is that you can use their values +everywhere in your application. When using parameters, they are only available +from places with access to the Symfony container. + +Constants can be used for example in your Twig templates thanks to the +``constant()`` function: + +.. code-block:: html+jinja + +

+ Displaying the {{ constant('NUM_ITEMS', post) }} most recent results. +

+ +And Doctrine entities and repositories can now easily access these values, +whereas they cannot access the container parameters: + +.. code-block:: php + + namespace AppBundle\Repository; + + use Doctrine\ORM\EntityRepository; + use AppBundle\Entity\Post; + + class PostRepository extends EntityRepository + { + public function findLatest($limit = Post::NUM_ITEMS) + { + // ... + } + } + +The only notable disadvantage of using constants for this kind of configuration +values is that you cannot redefine them easily in your tests. + +Semantic Configuration: Don't Do It +----------------------------------- + +.. best-practice:: + + Don't define a semantic dependency injection configuration for your bundles. + +As explained in :doc:`/cookbook/bundles/extension` article, Symfony bundles +have two choices on how to handle configuration: normal service configuration +through the ``services.yml`` file and semantic configuration through a special +``*Extension`` class. + +Although semantic configuration is much more powerful and provides nice features +such as configuration validation, the amount of work needed to define that +configuration isn't worth it for bundles that aren't meant to be shared as +third-party bundles. + +Moving Sensitive Options Outside of Symfony Entirely +---------------------------------------------------- + +When dealing with sensitive options, like database credentials, we also recommend +that you store them outside the Symfony project and make them available +through environment variables. Learn how to do it in the following article: +:doc:`/cookbook/configuration/external_parameters` + +.. _`feature toggles`: http://en.wikipedia.org/wiki/Feature_toggle +.. _`constant() function`: http://twig.sensiolabs.org/doc/functions/constant.html diff --git a/best_practices/controllers.rst b/best_practices/controllers.rst new file mode 100644 index 00000000000..a31db8e21c6 --- /dev/null +++ b/best_practices/controllers.rst @@ -0,0 +1,218 @@ +Controllers +=========== + +Symfony follows the philosophy of *"thin controllers and fat models"*. This +means that controllers should hold just the thin layer of *glue-code* +needed to coordinate the different parts of the application. + +As a rule of thumb, you should follow the 5-10-20 rule, where controllers should +only define 5 variables or less, contain 10 actions or less and include 20 lines +of code or less in each action. This isn't an exact science, but it should +help you realize when code should be refactored out of the controller and +into a service. + +.. best-practice:: + + Make your controller extend the FrameworkBundle base controller and use + annotations to configure routing, caching and security whenever possible. + +Coupling the controllers to the underlying framework allows you to leverage +all of its features and increases your productivity. + +And since your controllers should be thin and contain nothing more than a +few lines of *glue-code*, spending hours trying to decouple them from your +framework doesn't benefit you in the long run. The amount of time *wasted* +isn't worth the benefit. + +In addition, using annotations for routing, caching and security simplifies +configuration. You don't need to browse tens of files created with different +formats (YAML, XML, PHP): all the configuration is just where you need it +and it only uses one format. + +Overall, this means you should aggressively decouple your business logic +from the framework while, at the same time, aggressively coupling your controllers +and routing *to* the framework in order to get the most out of it. + +Routing Configuration +--------------------- + +To load routes defined as annotations in your controllers, add the following +configuration to the main routing configuration file: + +.. code-block:: yaml + + # app/config/routing.yml + app: + resource: "@AppBundle/Controller/" + type: annotation + +This configuration will load annotations from any controller stored inside the +``src/AppBundle/Controller/`` directory and even from its subdirectories. +So if your application defines lots of controllers, it's perfectly ok to +reorganize them into subdirectories: + +.. code-block:: text + + / + ├─ ... + └─ src/ + └─ AppBundle/ + ├─ ... + └─ Controller/ + ├─ DefaultController.php + ├─ ... + ├─ Api/ + │ ├─ ... + │ └─ ... + └─ Backend/ + ├─ ... + └─ ... + +Template Configuration +---------------------- + +.. best-practice:: + + Don't use the ``@Template()`` annotation to configure the template used by + the controller. + +The ``@Template`` annotation is useful, but also involves some magic. For +that reason, we don't recommend using it. + +Most of the time, ``@Template`` is used without any parameters, which makes +it more difficult to know which template is being rendered. It also makes +it less obvious to beginners that a controller should always return a Response +object (unless you're using a view layer). + +Lastly, the ``@Template`` annotation uses a ``TemplateListener`` class that hooks +into the ``kernel.view`` event dispatched by the framework. This listener introduces +a measurable performance impact. In the sample blog application, rendering the +homepage took 5 milliseconds using the ``$this->render()`` method and 26 milliseconds +using the ``@Template`` annotation. + +How the Controller Looks +------------------------ + +Considering all this, here is an example of how the controller should look +for the homepage of our app: + +.. code-block:: php + + namespace AppBundle\Controller; + + use Symfony\Bundle\FrameworkBundle\Controller\Controller; + use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; + + class DefaultController extends Controller + { + /** + * @Route("/", name="homepage") + */ + public function indexAction() + { + $posts = $this->getDoctrine() + ->getRepository('AppBundle:Post') + ->findLatest(); + + return $this->render('default/index.html.twig', array( + 'posts' => $posts + )); + } + } + +.. _best-practices-paramconverter: + +Using the ParamConverter +------------------------ + +If you're using Doctrine, then you can *optionally* use the `ParamConverter`_ +to automatically query for an entity and pass it as an argument to your controller. + +.. best-practice:: + + Use the ParamConverter trick to automatically query for Doctrine entities + when it's simple and convenient. + +For example: + +.. code-block:: php + + use AppBundle\Entity\Post; + use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; + + /** + * @Route("/{id}", name="admin_post_show") + */ + public function showAction(Post $post) + { + $deleteForm = $this->createDeleteForm($post); + + return $this->render('admin/post/show.html.twig', array( + 'post' => $post, + 'delete_form' => $deleteForm->createView(), + )); + } + +Normally, you'd expect a ``$id`` argument to ``showAction``. Instead, by +creating a new argument (``$post``) and type-hinting it with the ``Post`` +class (which is a Doctrine entity), the ParamConverter automatically queries +for an object whose ``$id`` property matches the ``{id}`` value. It will +also show a 404 page if no ``Post`` can be found. + +When Things Get More Advanced +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This works without any configuration because the wildcard name ``{id}`` matches +the name of the property on the entity. If this isn't true, or if you have +even more complex logic, the easiest thing to do is just query for the entity +manually. In our application, we have this situation in ``CommentController``: + +.. code-block:: php + + /** + * @Route("/comment/{postSlug}/new", name = "comment_new") + */ + public function newAction(Request $request, $postSlug) + { + $post = $this->getDoctrine() + ->getRepository('AppBundle:Post') + ->findOneBy(array('slug' => $postSlug)); + + if (!$post) { + throw $this->createNotFoundException(); + } + + // ... + } + +You can also use the ``@ParamConverter`` configuration, which is infinitely +flexible: + +.. code-block:: php + + use AppBundle\Entity\Post; + use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; + use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; + use Symfony\Component\HttpFoundation\Request; + + /** + * @Route("/comment/{postSlug}/new", name = "comment_new") + * @ParamConverter("post", options={"mapping": {"postSlug": "slug"}}) + */ + public function newAction(Request $request, Post $post) + { + // ... + } + +The point is this: the ParamConverter shortcut is great for simple situations. +But you shouldn't forget that querying for entities directly is still very +easy. + +Pre and Post Hooks +------------------ + +If you need to execute some code before or after the execution of your controllers, +you can use the EventDispatcher component to +:doc:`set up before and after filters `. + +.. _`ParamConverter`: http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html diff --git a/best_practices/creating-the-project.rst b/best_practices/creating-the-project.rst new file mode 100644 index 00000000000..d4eed5062f5 --- /dev/null +++ b/best_practices/creating-the-project.rst @@ -0,0 +1,195 @@ +Creating the Project +==================== + +Installing Symfony +------------------ + +In the past, Symfony projects were created with `Composer`_, the dependency manager +for PHP applications. However, the current recommendation is to use the **Symfony +Installer**, which has to be installed before creating your first project. + +Linux and Mac OS X Systems +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Open your command console and execute the following: + +.. code-block:: bash + + $ curl -LsS http://symfony.com/installer > symfony.phar + $ sudo mv symfony.phar /usr/local/bin/symfony + $ chmod a+x /usr/local/bin/symfony + +Now you can execute the Symfony Installer as a global system command called +``symfony``. + +Windows Systems +~~~~~~~~~~~~~~~ + +Open your command console and execute the following: + +.. code-block:: bash + + c:\> php -r "readfile('http://symfony.com/installer');" > symfony.phar + +Then, move the downloaded ``symfony.phar`` file to your projects directory and +execute it as follows: + +.. code-block:: bash + + c:\> php symfony.phar + +Creating the Blog Application +----------------------------- + +Now that everything is correctly set up, you can create a new project based on +Symfony. In your command console, browse to a directory where you have permission +to create files and execute the following commands: + +.. code-block:: bash + + # Linux, Mac OS X + $ cd projects/ + $ symfony new blog + + # Windows + c:\> cd projects/ + c:\projects\> php symfony.phar new blog + +This command creates a new directory called ``blog`` that contains a fresh new +project based on the most recent stable Symfony version available. In addition, +the installer checks if your system meets the technical requirements to execute +Symfony applications. If not, you'll see the list of changes needed to meet those +requirements. + +.. tip:: + + Symfony releases are digitally signed for security reasons. If you want to + verify the integrity of your Symfony installation, take a look at the + `public checksums repository`_ and follow `these steps`_ to verify the + signatures. + +Structuring the Application +--------------------------- + +After creating the application, enter the ``blog/`` directory and you'll see a +number of files and directories generated automatically: + +.. code-block:: text + + blog/ + ├─ app/ + │ ├─ console + │ ├─ cache/ + │ ├─ config/ + │ ├─ logs/ + │ └─ Resources/ + ├─ src/ + │ └─ AppBundle/ + ├─ vendor/ + └─ web/ + +This file and directory hierarchy is the convention proposed by Symfony to +structure your applications. The recommended purpose of each directory is the +following: + +* ``app/cache/``, stores all the cache files generated by the application; +* ``app/config/``, stores all the configuration defined for any environment; +* ``app/logs/``, stores all the log files generated by the application; +* ``app/Resources/``, stores all the templates and the translation files for the + application; +* ``src/AppBundle/``, stores the Symfony specific code (controllers and routes), + your domain code (e.g. Doctrine classes) and all your business logic; +* ``vendor/``, this is the directory where Composer installs the application's + dependencies and you should never modify any of its contents; +* ``web/``, stores all the front controller files and all the web assets, such + as stylesheets, JavaScript files and images. + +Application Bundles +~~~~~~~~~~~~~~~~~~~ + +When Symfony 2.0 was released, most developers naturally adopted the symfony +1.x way of dividing applications into logical modules. That's why many Symfony +apps use bundles to divide their code into logical features: UserBundle, +ProductBundle, InvoiceBundle, etc. + +But a bundle is *meant* to be something that can be reused as a stand-alone +piece of software. If UserBundle cannot be used *"as is"* in other Symfony +apps, then it shouldn't be its own bundle. Moreover InvoiceBundle depends on +ProductBundle, then there's no advantage to having two separate bundles. + +.. best-practice:: + + Create only one bundle called AppBundle for your application logic + +Implementing a single AppBundle bundle in your projects will make your code +more concise and easier to understand. Starting in Symfony 2.6, the official +Symfony documentation uses the AppBundle name. + +.. note:: + + There is no need to prefix the AppBundle with your own vendor (e.g. + AcmeAppBundle), because this application bundle is never going to be + shared. + +All in all, this is the typical directory structure of a Symfony application +that follows these best practices: + +.. code-block:: text + + blog/ + ├─ app/ + │ ├─ console + │ ├─ cache/ + │ ├─ config/ + │ ├─ logs/ + │ └─ Resources/ + ├─ src/ + │ └─ AppBundle/ + ├─ vendor/ + └─ web/ + ├─ app.php + └─ app_dev.php + +.. tip:: + + If your Symfony installation doesn't come with a pre-generated AppBundle, + you can generate it by hand executing this command: + + .. code-block:: bash + + $ php app/console generate:bundle --namespace=AppBundle --dir=src --format=annotation --no-interaction + +Extending the Directory Structure +--------------------------------- + +If your project or infrastructure requires some changes to the default directory +structure of Symfony, you can +:doc:`override the location of the main directories `: +``cache/``, ``logs/`` and ``web/``. + +In addition, Symfony3 will use a slightly different directory structure when +it's released: + +.. code-block:: text + + blog-symfony3/ + ├─ app/ + │ ├─ config/ + │ └─ Resources/ + ├─ bin/ + │ └─ console + ├─ src/ + ├─ var/ + │ ├─ cache/ + │ └─ logs/ + ├─ vendor/ + └─ web/ + +The changes are pretty superficial, but for now, we recommend that you use +the Symfony directory structure. + +.. _`Composer`: https://getcomposer.org/ +.. _`Get Started`: https://getcomposer.org/doc/00-intro.md +.. _`Composer download page`: https://getcomposer.org/download/ +.. _`public checksums repository`: https://github.com/sensiolabs/checksums +.. _`these steps`: http://fabien.potencier.org/article/73/signing-project-releases diff --git a/best_practices/forms.rst b/best_practices/forms.rst new file mode 100644 index 00000000000..d72d189ccfb --- /dev/null +++ b/best_practices/forms.rst @@ -0,0 +1,209 @@ +Forms +===== + +Forms are one of the most misused Symfony components due to its vast scope and +endless list of features. In this chapter we'll show you some of the best +practices so you can leverage forms but get work done quickly. + +Building Forms +-------------- + +.. best-practice:: + + Define your forms as PHP classes. + +The Form component allows you to build forms right inside your controller +code. This is perfectly fine if you don't need to reuse the form somewhere else. +But for organization and reuse, we recommend that you define each +form in its own PHP class:: + + namespace AppBundle\Form; + + use Symfony\Component\Form\AbstractType; + use Symfony\Component\Form\FormBuilderInterface; + use Symfony\Component\OptionsResolver\OptionsResolverInterface; + + class PostType extends AbstractType + { + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder + ->add('title') + ->add('summary', 'textarea') + ->add('content', 'textarea') + ->add('authorEmail', 'email') + ->add('publishedAt', 'datetime') + ; + } + + public function setDefaultOptions(OptionsResolverInterface $resolver) + { + $resolver->setDefaults(array( + 'data_class' => 'AppBundle\Entity\Post' + )); + } + + public function getName() + { + return 'post'; + } + } + +To use the class, use ``createForm`` and instantiate the new class:: + + use AppBundle\Form\PostType; + // ... + + public function newAction(Request $request) + { + $post = new Post(); + $form = $this->createForm(new PostType(), $post); + + // ... + } + +Registering Forms as Services +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can also +:ref:`register your form type as a service `. +But this is *not* recommended unless you plan to reuse the new form type in many +places or embed it in other forms directly or via the +:doc:`collection type `. + +For most forms that are used only to edit or create something, registering +the form as a service is over-kill, and makes it more difficult to figure +out exactly which form class is being used in a controller. + +Form Button Configuration +------------------------- + +Form classes should try to be agnostic to *where* they will be used. This +makes them easier to re-use later. + +.. best-practice:: + + Add buttons in the templates, not in the form classes or the controllers. + +Since Symfony 2.5, you can add buttons as fields on your form. This is a nice +way to simplify the template that renders your form. But if you add the buttons +directly in your form class, this would effectively limit the scope of that form: + +.. code-block:: php + + class PostType extends AbstractType + { + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder + // ... + ->add('save', 'submit', array('label' => 'Create Post')) + ; + } + + // ... + } + +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:: + + namespace AppBundle\Controller\Admin; + + use Symfony\Component\HttpFoundation\Request; + use Symfony\Bundle\FrameworkBundle\Controller\Controller; + use AppBundle\Entity\Post; + use AppBundle\Form\PostType; + + class PostController extends Controller + { + // ... + + public function newAction(Request $request) + { + $post = new Post(); + $form = $this->createForm(new PostType(), $post); + $form->add('submit', 'submit', array( + 'label' => 'Create', + 'attr' => array('class' => 'btn btn-default pull-right') + )); + + // ... + } + } + +This is also an important error, because you are mixing presentation markup +(labels, CSS classes, etc.) with pure PHP code. Separation of concerns is +always a good practice to follow, so put all the view-related things in the +view layer: + +.. code-block:: html+jinja + + {{ form_start(form) }} + {{ form_widget(form) }} + + + {{ form_end(form) }} + +Rendering the Form +------------------ + +There are a lot of ways to render your form, ranging from rendering the entire +thing in one line to rendering each part of each field independently. The +best way depends on how much customization you need. + +One of the simplest ways - which is especially useful during development - +is to render the form tags and use ``form_widget()`` to render all of the +fields: + +.. code-block:: html+jinja + + {{ form_start(form, {'attr': {'class': 'my-form-class'} }) }} + {{ form_widget(form) }} + {{ form_end(form) }} + +If you need more control over how your fields are rendered, then you should +remove the ``form_widget(form)`` function and render your fields individually. +See the :doc:`/cookbook/form/form_customization` article for more information +on this and how you can control *how* the form renders at a global level +using form theming. + +Handling Form Submits +--------------------- + +Handling a form submit usually follows a similar template: + +.. code-block:: php + + public function newAction(Request $request) + { + // build the form ... + + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $em = $this->getDoctrine()->getManager(); + $em->persist($post); + $em->flush(); + + return $this->redirect($this->generateUrl( + 'admin_post_show', + array('id' => $post->getId()) + )); + } + + // render the template + } + +There are really only two notable things here. First, we recommend that you +use a single action for both rendering the form and handling the form submit. +For example, you *could* have a ``newAction`` that *only* renders the form +and a ``createAction`` that *only* processes the form submit. Both those +actions will be almost identical. So it's much simpler to let ``newAction`` +handle everything. + +Second, we recommend using ``$form->isSubmitted()`` in the ``if`` statement +for clarity. This isn't technically needed, since ``isValid()`` first calls +``isSubmitted()``. But without this, the flow doesn't read well as it *looks* +like the form is *always* processed (even on the GET request). diff --git a/best_practices/i18n.rst b/best_practices/i18n.rst new file mode 100644 index 00000000000..9da3fad3271 --- /dev/null +++ b/best_practices/i18n.rst @@ -0,0 +1,96 @@ +Internationalization +==================== + +Internationalization and localization adapt the applications and their contents +to the specific region or language of the users. In Symfony this is an opt-in +feature that needs to be enabled before using it. To do this, uncomment the +following ``translator`` configuration option and set your application locale: + +.. code-block:: yaml + + # app/config/config.yml + framework: + # ... + translator: { fallback: "%locale%" } + + # app/config/parameters.yml + parameters: + # ... + locale: en + +Translation Source File Format +------------------------------ + +The Symfony Translation component supports lots of different translation +formats: PHP, Qt, ``.po``, ``.mo``, JSON, CSV, INI, etc. + +.. best-practice:: + + Use the XLIFF format for your translation files. + +Of all the available translation formats, only XLIFF and gettext have broad +support in the tools used by professional translators. And since it's based +on XML, you can validate XLIFF file contents as you write them. + +Symfony 2.6 added support for notes inside XLIFF files, making them more +user-friendly for translators. At the end, good translations are all about +context, and these XLIFF notes allow you to define that context. + +.. tip:: + + The Apache-licensed `JMSTranslationBundle`_ offers you a web interface for + viewing and editing these translation files. It also has advanced extractors + that can read your project and automatically update the XLIFF files. + +Translation Source File Location +-------------------------------- + +.. best-practice:: + + Store the translation files in the ``app/Resources/translations/`` directory. + +Traditionally, Symfony developers have created these files in the +``Resources/translations/`` directory of each bundle. + +But since the ``app/Resources/`` directory is considered the global location +for the application's resources, storing translations in ``app/Resources/translations/`` +centralizes them *and* gives them priority over any other translation file. +This lets you override translations defined in third-party bundles. + +Translation Keys +---------------- + +.. best-practice:: + + Always use keys for translations instead of content strings. + +Using keys simplifies the management of the translation files because you +can change the original contents without having to update all of the translation +files. + +Keys should always describe their *purpose* and *not* their location. For +example, if a form has a field with the label "Username", then a nice key +would be ``label.username``, *not* ``edit_form.label.username``. + +Example Translation File +------------------------ + +Applying all the previous best practices, the sample translation file for +English in the application would be: + +.. code-block:: xml + + + + + + + + title.post_list + Post List + + + + + +.. _`JMSTranslationBundle`: https://github.com/schmittjoh/JMSTranslationBundle diff --git a/best_practices/index.rst b/best_practices/index.rst new file mode 100644 index 00000000000..8df4abb1364 --- /dev/null +++ b/best_practices/index.rst @@ -0,0 +1,19 @@ +Official Symfony Best Practices +=============================== + +.. toctree:: + :hidden: + + introduction + creating-the-project + configuration + business-logic + controllers + templates + forms + i18n + security + web-assets + tests + +.. include:: /best_practices/map.rst.inc diff --git a/best_practices/introduction.rst b/best_practices/introduction.rst new file mode 100644 index 00000000000..d1d0e760d32 --- /dev/null +++ b/best_practices/introduction.rst @@ -0,0 +1,97 @@ +.. index:: + single: Symfony Framework Best Practices + +The Symfony Framework Best Practices +==================================== + +The Symfony framework is well-known for being *really* flexible and is used +to build micro-sites, enterprise applications that handle billions of connections +and even as the basis for *other* frameworks. Since its release in July 2011, +the community has learned a lot about what's possible and how to do things *best*. + +These community resources - like blog posts or presentations - have created +an unofficial set of recommendations for developing Symfony applications. +Unfortunately, a lot of these recommendations are unneeded for web applications. +Much of the time, they unnecessarily overcomplicate things and don't follow the +original pragmatic philosophy of Symfony. + +What is this Guide About? +------------------------- + +This guide aims to fix that by describing the **best practices for developing +web apps with the Symfony full-stack framework**. These are best practices that +fit the philosophy of the framework as envisioned by its original creator +`Fabien Potencier`_. + +.. note:: + + **Best practice** is a noun that means *"a well defined procedure that is + known to produce near-optimum results"*. And that's exactly what this + guide aims to provide. Even if you don't agree with every recommendation, + we believe these will help you build great applications with less complexity. + +This guide is **specially suited** for: + +* Websites and web applications developed with the full-stack Symfony framework. + +For other situations, this guide might be a good **starting point** that you can +then **extend and fit to your specific needs**: + +* Bundles shared publicly to the Symfony community; +* Advanced developers or teams who have created their own standards; +* Some complex applications that have highly customized requirements; +* Bundles that may be shared internally within a company. + +We know that old habits die hard and some of you will be shocked by some +of these best practices. But by following these, you'll be able to develop +apps faster, with less complexity and with the same or even higher quality. +It's also a moving target that will continue to improve. + +Keep in mind that these are **optional recommendations** that you and your +team may or may not follow to develop Symfony applications. If you want to +continue using your own best practices and methodologies, you can of course +do it. Symfony is flexible enough to adapt to your needs. That will never +change. + +Who this Book Is for (Hint: It's not a Tutorial) +------------------------------------------------ + +Any Symfony developer, whether you are an expert or a newcomer, can read this +guide. But since this isn't a tutorial, you'll need some basic knowledge of +Symfony to follow everything. If you are totally new to Symfony, welcome! +Start with :doc:`The Quick Tour ` tutorial first. + +We've deliberately kept this guide short. We won't repeat explanations that +you can find in the vast Symfony documentation, like discussions about dependency +injection or front controllers. We'll solely focus on explaining how to do +what you already know. + +The Application +--------------- + +In addition to this guide, you'll find a sample application developed with +all these best practices in mind. **The application is a simple blog engine**, +because that will allow us to focus on the Symfony concepts and features without +getting buried in difficult details. + +Instead of developing the application step by step in this guide, you'll find +selected snippets of code through the chapters. Please refer to the last chapter +of this guide to find more details about this application and the instructions +to install it. + +Don't Update Your Existing Applications +--------------------------------------- + +After reading this handbook, some of you may be considering refactoring your +existing Symfony applications. Our recommendation is sound and clear: **you +should not refactor your existing applications to comply with these best +practices**. The reasons for not doing it are various: + +* Your existing applications are not wrong, they just follow another set of + guidelines; +* A full codebase refactorization is prone to introduce errors in your + applications; +* The amount of work spent on this could be better dedicated to improving + your tests or adding features that provide real value to the end users. + +.. _`Fabien Potencier`: https://connect.sensiolabs.com/profile/fabpot diff --git a/best_practices/map.rst.inc b/best_practices/map.rst.inc new file mode 100644 index 00000000000..f9dfd0c3e9d --- /dev/null +++ b/best_practices/map.rst.inc @@ -0,0 +1,11 @@ +* :doc:`/best_practices/introduction` +* :doc:`/best_practices/creating-the-project` +* :doc:`/best_practices/configuration` +* :doc:`/best_practices/business-logic` +* :doc:`/best_practices/controllers` +* :doc:`/best_practices/templates` +* :doc:`/best_practices/forms` +* :doc:`/best_practices/i18n` +* :doc:`/best_practices/security` +* :doc:`/best_practices/web-assets` +* :doc:`/best_practices/tests` diff --git a/best_practices/security.rst b/best_practices/security.rst new file mode 100644 index 00000000000..c7fd3a7725b --- /dev/null +++ b/best_practices/security.rst @@ -0,0 +1,363 @@ +Security +======== + +Authentication and Firewalls (i.e. Getting the User's Credentials) +------------------------------------------------------------------ + +You can configure Symfony to authenticate your users using any method you +want and to load user information from any source. This is a complex topic, +but the :doc:`Security Cookbook Section ` has a +lot of information about this. + +Regardless of your needs, authentication is configured in ``security.yml``, +primarily under the ``firewalls`` key. + +.. best-practice:: + + Unless you have two legitimately different authentication systems and + users (e.g. form login for the main site and a token system for your + API only), we recommend having only *one* firewall entry with the ``anonymous`` + key enabled. + +Most applications only have one authentication system and one set of users. +For this reason, you only need *one* firewall entry. There are exceptions +of course, especially if you have separated web and API sections on your +site. But the point is to keep things simple. + +Additionally, you should use the ``anonymous`` key under your firewall. If +you need to require users to be logged in for different sections of your +site (or maybe nearly *all* sections), use the ``access_control`` area. + +.. best-practice:: + + Use the ``bcrypt`` encoder for encoding your users' passwords. + +If your users have a password, then we recommend encoding it using the ``bcrypt`` +encoder, instead of the traditional SHA-512 hashing encoder. The main advantages +of ``bcrypt`` are the inclusion of a *salt* value to protect against rainbow +table attacks, and its adaptive nature, which allows to make it slower to +remain resistant to brute-force search attacks. + +With this in mind, here is the authentication setup from our application, +which uses a login form to load users from the database: + +.. code-block:: yaml + + # app/config/security.yml + security: + encoders: + AppBundle\Entity\User: bcrypt + + providers: + database_users: + entity: { class: AppBundle:User, property: username } + + firewalls: + secured_area: + pattern: ^/ + anonymous: true + form_login: + check_path: security_login_check + login_path: security_login_form + + logout: + path: security_logout + target: homepage + + # ... access_control exists, but is not shown here + +.. tip:: + + The source code for our project contains comments that explain each part. + +Authorization (i.e. Denying Access) +----------------------------------- + +Symfony gives you several ways to enforce authorization, including the ``access_control`` +configuration in :doc:`security.yml `, the +:ref:`@Security annotation ` and using +:ref:`isGranted ` on the ``security.authorization_checker`` +service directly. + +.. best-practice:: + + * For protecting broad URL patterns, use ``access_control``; + * Whenever possible, use the ``@Security`` annotation; + * Check security directly on the ``security.authorization_checker`` service whenever + you have a more complex situation. + +There are also different ways to centralize your authorization logic, like +with a custom security voter or with ACL. + +.. best-practice:: + + * For fine-grained restrictions, define a custom security voter; + * For restricting access to *any* object by *any* user via an admin + interface, use the Symfony ACL. + +.. _best-practices-security-annotation: + +The @Security Annotation +------------------------ + +For controlling access on a controller-by-controller basis, use the ``@Security`` +annotation whenever possible. It's easy to read and is placed consistently +above each action. + +In our application, you need the ``ROLE_ADMIN`` in order to create a new post. +Using ``@Security``, this looks like: + +.. code-block:: php + + use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; + use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security; + // ... + + /** + * Displays a form to create a new Post entity. + * + * @Route("/new", name="admin_post_new") + * @Security("has_role('ROLE_ADMIN')") + */ + public function newAction() + { + // ... + } + +Using Expressions for Complex Security Restrictions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If your security logic is a little bit more complex, you can use an `expression`_ +inside ``@Security``. In the following example, a user can only access the +controller if their email matches the value returned by the ``getAuthorEmail`` +method on the ``Post`` object: + +.. code-block:: php + + use AppBundle\Entity\Post; + use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; + use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security; + + /** + * @Route("/{id}/edit", name="admin_post_edit") + * @Security("user.getEmail() == post.getAuthorEmail()") + */ + public function editAction(Post $post) + { + // ... + } + +Notice that this requires the use of the `ParamConverter`_, which automatically +queries for the ``Post`` object and puts it on the ``$post`` argument. This +is what makes it possible to use the ``post`` variable in the expression. + +This has one major drawback: an expression in an annotation cannot easily +be reused in other parts of the application. Imagine that you want to add +a link in a template that will only be seen by authors. Right now you'll +need to repeat the expression code using Twig syntax: + +.. code-block:: html+jinja + + {% if app.user and app.user.email == post.authorEmail %} + ... + {% endif %} + +The easiest solution - if your logic is simple enough - is to add a new method +to the ``Post`` entity that checks if a given user is its author: + +.. code-block:: php + + // src/AppBundle/Entity/Post.php + // ... + + class Post + { + // ... + + /** + * Is the given User the author of this Post? + * + * @return bool + */ + public function isAuthor(User $user = null) + { + return $user && $user->getEmail() == $this->getAuthorEmail(); + } + } + +Now you can reuse this method both in the template and in the security expression: + +.. code-block:: php + + use AppBundle\Entity\Post; + use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security; + + /** + * @Route("/{id}/edit", name="admin_post_edit") + * @Security("post.isAuthor(user)") + */ + public function editAction(Post $post) + { + // ... + } + +.. code-block:: html+jinja + + {% if post.isAuthor(app.user) %} + ... + {% endif %} + +.. _best-practices-directly-isGranted: +.. _checking-permissions-without-security: +.. _manually-checking-permissions: + +Checking Permissions without @Security +-------------------------------------- + +The above example with ``@Security`` only works because we're using the +:ref:`ParamConverter `, which gives the expression +access to the a ``post`` variable. If you don't use this, or have some other +more advanced use-case, you can always do the same security check in PHP: + +.. code-block:: php + + /** + * @Route("/{id}/edit", name="admin_post_edit") + */ + public function editAction($id) + { + $post = $this->getDoctrine()->getRepository('AppBundle:Post') + ->find($id); + + if (!$post) { + throw $this->createNotFoundException(); + } + + if (!$post->isAuthor($this->getUser())) { + throw $this->createAccessDeniedException(); + } + + // ... + } + +Security Voters +--------------- + +If your security logic is complex and can't be centralized into a method +like ``isAuthor()``, you should leverage custom voters. These are an order +of magnitude easier than :doc:`ACLs ` and will give +you the flexibility you need in almost all cases. + +First, create a voter class. The following example shows a voter that implements +the same ``getAuthorEmail`` logic you used above: + +.. code-block:: php + + namespace AppBundle\Security; + + use Symfony\Component\Security\Core\Authorization\Voter\AbstractVoter; + use Symfony\Component\Security\Core\User\UserInterface; + + // AbstractVoter class requires Symfony 2.6 or higher version + class PostVoter extends AbstractVoter + { + const CREATE = 'create'; + const EDIT = 'edit'; + + protected function getSupportedAttributes() + { + return array(self::CREATE, self::EDIT); + } + + protected function getSupportedClasses() + { + return array('AppBundle\Entity\Post'); + } + + protected function isGranted($attribute, $post, $user = null) + { + if (!$user instanceof UserInterface) { + return false; + } + + if ($attribute === self::CREATE && in_array('ROLE_ADMIN', $user->getRoles(), true)) { + return true; + } + + if ($attribute === self::EDIT && $user->getEmail() === $post->getAuthorEmail()) { + return true; + } + + return false; + } + } + +To enable the security voter in the application, define a new service: + +.. code-block:: yaml + + # app/config/services.yml + services: + # ... + post_voter: + class: AppBundle\Security\PostVoter + public: false + tags: + - { name: security.voter } + +Now, you can use the voter with the ``@Security`` annotation: + +.. code-block:: php + + /** + * @Route("/{id}/edit", name="admin_post_edit") + * @Security("is_granted('edit', post)") + */ + public function editAction(Post $post) + { + // ... + } + +You can also use this directly with the ``security.authorization_checker`` service or +via the even easier shortcut in a controller: + +.. code-block:: php + + /** + * @Route("/{id}/edit", name="admin_post_edit") + */ + public function editAction($id) + { + $post = // query for the post ... + + $this->denyAccessUnlessGranted('edit', $post); + + // or without the shortcut: + // + // if (!$this->get('security.authorization_checker')->isGranted('edit', $post)) { + // throw $this->createAccessDeniedException(); + // } + } + +Learn More +---------- + +The `FOSUserBundle`_, developed by the Symfony community, adds support for a +database-backed user system in Symfony. It also handles common tasks like +user registration and forgotten password functionality. + +Enable the :doc:`Remember Me feature ` to +allow your users to stay logged in for a long period of time. + +When providing customer support, sometimes it's necessary to access the application +as some *other* user so that you can reproduce the problem. Symfony provides +the ability to :doc:`impersonate users `. + +If your company uses a user login method not supported by Symfony, you can +develop :doc:`your own user provider ` and +:doc:`your own authentication provider `. + +.. _`ParamConverter`: http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html +.. _`@Security annotation`: http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/security.html +.. _`expression`: http://symfony.com/doc/current/components/expression_language/introduction.html +.. _`FOSUserBundle`: https://github.com/FriendsOfSymfony/FOSUserBundle diff --git a/best_practices/templates.rst b/best_practices/templates.rst new file mode 100644 index 00000000000..801646587b2 --- /dev/null +++ b/best_practices/templates.rst @@ -0,0 +1,161 @@ +Templates +========= + +When PHP was created 20 years ago, developers loved its simplicity and how +well it blended HTML and dynamic code. But as time passed, other template +languages - like `Twig`_ - were created to make templating even better. + +.. best-practice:: + + Use Twig templating format for your templates. + +Generally speaking, PHP templates are much more verbose than Twig templates because +they lack native support for lots of modern features needed by templates, +like inheritance, automatic escaping and named arguments for filters and +functions. + +Twig is the default templating format in Symfony and has the largest community +support of all non-PHP template engines (it's used in high profile projects +such as Drupal 8). + +In addition, Twig is the only template format with guaranteed support in Symfony +3.0. As a matter of fact, PHP may be removed from the officially supported +template engines. + +Template Locations +------------------ + +.. best-practice:: + + Store all your application's templates in ``app/Resources/views/`` directory. + +Traditionally, Symfony developers stored the application templates in the +``Resources/views/`` directory of each bundle. Then they used the logical name +to refer to them (e.g. ``AcmeDemoBundle:Default:index.html.twig``). + +But for the templates used in your application, it's much more convenient +to store them in the ``app/Resources/views/`` directory. For starters, this +drastically simplifies their logical names: + +================================================= ================================== +Templates Stored inside Bundles Templates Stored in ``app/`` +================================================= ================================== +``AcmeDemoBundle:Default:index.html.twig`` ``default/index.html.twig`` +``::layout.html.twig`` ``layout.html.twig`` +``AcmeDemoBundle::index.html.twig`` ``index.html.twig`` +``AcmeDemoBundle:Default:subdir/index.html.twig`` ``default/subdir/index.html.twig`` +``AcmeDemoBundle:Default/subdir:index.html.twig`` ``default/subdir/index.html.twig`` +================================================= ================================== + +Another advantage is that centralizing your templates simplifies the work +of your designers. They don't need to look for templates in lots of directories +scattered through lots of bundles. + +Twig Extensions +--------------- + +.. best-practice:: + + Define your Twig extensions in the ``AppBundle/Twig/`` directory and + configure them using the ``app/config/services.yml`` file. + +Our application needs a custom ``md2html`` Twig filter so that we can transform +the Markdown contents of each post into HTML. + +To do this, first, install the excellent `Parsedown`_ Markdown parser as +a new dependency of the project: + +.. code-block:: bash + + $ composer require erusev/parsedown + +Then, create a new ``Markdown`` service that will be used later by the Twig +extension. The service definition only requires the path to the class: + +.. code-block:: yaml + + # app/config/services.yml + services: + # ... + markdown: + class: AppBundle\Utils\Markdown + +And the ``Markdown`` class just needs to define one single method to transform +Markdown content into HTML:: + + namespace AppBundle\Utils; + + class Markdown + { + private $parser; + + public function __construct() + { + $this->parser = new \Parsedown(); + } + + public function toHtml($text) + { + $html = $this->parser->text($text); + + return $html; + } + } + +Next, create a new Twig extension and define a new filter called ``md2html`` +using the ``Twig_SimpleFilter`` class. Inject the newly defined ``markdown`` +service in the constructor of the Twig extension: + +.. code-block:: php + + namespace AppBundle\Twig; + + use AppBundle\Utils\Markdown; + + class AppExtension extends \Twig_Extension + { + private $parser; + + public function __construct(Markdown $parser) + { + $this->parser = $parser; + } + + public function getFilters() + { + return array( + new \Twig_SimpleFilter( + 'md2html', + array($this, 'markdownToHtml'), + array('is_safe' => array('html')) + ), + ); + } + + public function markdownToHtml($content) + { + return $this->parser->toHtml($content); + } + + public function getName() + { + return 'app_extension'; + } + } + +Lastly define a new service to enable this Twig extension in the app (the service +name is irrelevant because you never use it in your own code): + +.. code-block:: yaml + + # app/config/services.yml + services: + app.twig.app_extension: + class: AppBundle\Twig\AppExtension + arguments: ["@markdown"] + public: false + tags: + - { name: twig.extension } + +.. _`Twig`: http://twig.sensiolabs.org/ +.. _`Parsedown`: http://parsedown.org/ diff --git a/best_practices/tests.rst b/best_practices/tests.rst new file mode 100644 index 00000000000..0bbcbd665de --- /dev/null +++ b/best_practices/tests.rst @@ -0,0 +1,114 @@ +Tests +===== + +Roughly speaking, there are two types of test. Unit testing allows you to +test the input and output of specific functions. Functional testing allows +you to command a "browser" where you browse to pages on your site, click +links, fill out forms and assert that you see certain things on the page. + +Unit Tests +---------- + +Unit tests are used to test your "business logic", which should live in classes +that are independent of Symfony. For that reason, Symfony doesn't really +have an opinion on what tools you use for unit testing. However, the most +popular tools are `PhpUnit`_ and `PhpSpec`_. + +Functional Tests +---------------- + +Creating really good functional tests can be tough so some developers skip +these completely. Don't skip the functional tests! By defining some *simple* +functional tests, you can quickly spot any big errors before you deploy them: + +.. best-practice:: + + Define a functional test that at least checks if your application pages + are successfully loading. + +A functional test can be as easy as this: + +.. code-block:: php + + /** @dataProvider provideUrls */ + public function testPageIsSuccessful($url) + { + $client = self::createClient(); + $client->request('GET', $url); + + $this->assertTrue($client->getResponse()->isSuccessful()); + } + + public function provideUrls() + { + return array( + array('/'), + array('/posts'), + array('/post/fixture-post-1'), + array('/blog/category/fixture-category'), + array('/archives'), + // ... + ); + } + +This code checks that all the given URLs load successfully, which means that +their HTTP response status code is between ``200`` and ``299``. This may +not look that useful, but given how little effort this took, it's worth +having it in your application. + +In computer software, this kind of test is called `smoke testing`_ and consists +of *"preliminary testing to reveal simple failures severe enough to reject a +prospective software release"*. + +Hardcode URLs in a Functional Test +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Some of you may be asking why the previous functional test doesn't use the URL +generator service: + +.. best-practice:: + + Hardcode the URLs used in the functional tests instead of using the URL + generator. + +Consider the following functional test that uses the ``router`` service to +generate the URL of the tested page: + +.. code-block:: php + + public function testBlogArchives() + { + $client = self::createClient(); + $url = $client->getContainer()->get('router')->generate('blog_archives'); + $client->request('GET', $url); + + // ... + } + +This will work, but it has one *huge* drawback. If a developer mistakenly +changes the path of the ``blog_archives`` route, the test will still pass, +but the original (old) URL won't work! This means that any bookmarks for +that URL will be broken and you'll lose any search engine page ranking. + +Testing JavaScript Functionality +-------------------------------- + +The built-in functional testing client is great, but it can't be used to +test any JavaScript behavior on your pages. If you need to test this, consider +using the `Mink`_ library from within PHPUnit. + +Of course, if you have a heavy JavaScript frontend, you should consider using +pure JavaScript-based testing tools. + +Learn More about Functional Tests +--------------------------------- + +Consider using `Faker`_ and `Alice`_ libraries to generate real-looking data +for your test fixtures. + +.. _`Faker`: https://github.com/fzaninotto/Faker +.. _`Alice`: https://github.com/nelmio/alice +.. _`PhpUnit`: https://phpunit.de/ +.. _`PhpSpec`: http://www.phpspec.net/ +.. _`Mink`: http://mink.behat.org +.. _`smoke testing`: http://en.wikipedia.org/wiki/Smoke_testing_(software) diff --git a/best_practices/web-assets.rst b/best_practices/web-assets.rst new file mode 100644 index 00000000000..a45d85542b5 --- /dev/null +++ b/best_practices/web-assets.rst @@ -0,0 +1,97 @@ +Web Assets +========== + +Web assets are things like CSS, JavaScript and image files that make the +frontend of your site look and work great. Symfony developers have traditionally +stored these assets in the ``Resources/public/`` directory of each bundle. + +.. best-practice:: + + Store your assets in the ``web/`` directory. + +Scattering your web assets across tens of different bundles makes it more +difficult to manage them. Your designers' lives will be much easier if all +the application assets are in one location. + +Templates also benefit from centralizing your assets, because the links are +much more concise: + +.. code-block:: html+jinja + + + + + {# ... #} + + + + +.. note:: + + Keep in mind that ``web/`` is a public directory and that anything stored + here will be publicly accessible. For that reason, you should put your + compiled web assets here, but not their source files (e.g. SASS files). + +Using Assetic +------------- + +These days, you probably can't simply create static CSS and JavaScript files +and include them in your template. Instead, you'll probably want to combine +and minify these to improve client-side performance. You may also want to +use LESS or Sass (for example), which means you'll need some way to process +these into CSS files. + +A lot of tools exist to solve these problems, including pure-frontend (non-PHP) +tools like GruntJS. + +.. best-practice:: + + Use Assetic to compile, combine and minimize web assets, unless you're + comfortable with frontend tools like GruntJS. + +:doc:`Assetic ` is an asset manager capable +of compiling assets developed with a lot of different frontend technologies +like LESS, Sass and CoffeeScript. +Combining all your assets with Assetic is a matter of wrapping all the assets +with a single Twig tag: + +.. code-block:: html+jinja + + {% stylesheets + 'css/bootstrap.min.css' + 'css/main.css' + filter='cssrewrite' output='css/compiled/all.css' %} + + {% endstylesheets %} + + {# ... #} + + {% javascripts + 'js/jquery.min.js' + 'js/bootstrap.min.js' + output='js/compiled/all.js' %} + + {% endjavascripts %} + +Frontend-Based Applications +--------------------------- + +Recently, frontend technologies like AngularJS have become pretty popular +for developing frontend web applications that talk to an API. + +If you are developing an application like this, you should use the tools +that are recommended by the technology, such as Bower and GruntJS. You should +develop your frontend application separately from your Symfony backend (even +separating the repositories if you want). + +Learn More about Assetic +------------------------ + +Assetic can also minimize CSS and JavaScript assets +:doc:`using UglifyCSS/UglifyJS ` to speed up your +websites. You can even :doc:`compress images ` +with Assetic to reduce their size before serving them to the user. Check out +the `official Assetic documentation`_ to learn more about all the available +features. + +.. _`official Assetic documentation`: https://github.com/kriswallsmith/assetic diff --git a/book/controller.rst b/book/controller.rst index 17902efcf9e..dc8a7c663d9 100644 --- a/book/controller.rst +++ b/book/controller.rst @@ -4,7 +4,7 @@ Controller ========== -A controller is a PHP function you create that takes information from the +A controller is a PHP callable you create that takes information from the HTTP request and constructs and returns an HTTP response (as a Symfony ``Response`` object). The response could be an HTML page, an XML document, a serialized JSON array, an image, a redirect, a 404 error or anything else @@ -12,7 +12,7 @@ you can dream up. The controller contains whatever arbitrary logic *your application* needs to render the content of a page. See how simple this is by looking at a Symfony controller in action. -The following controller would render a page that simply prints ``Hello world!``:: +This renders a page that prints the famous ``Hello world!``:: use Symfony\Component\HttpFoundation\Response; @@ -40,9 +40,9 @@ common examples: * *Controller C* handles the form submission of a contact form. It reads the form information from the request, saves the contact information to - the database and emails the contact information to the webmaster. Finally, - it creates a ``Response`` object that redirects the client's browser to - the contact form "thank you" page. + the database and emails the contact information to you. Finally, it creates + a ``Response`` object that redirects the client's browser to the contact + form "thank you" page. .. index:: single: Controller; Request-controller-response lifecycle @@ -51,8 +51,8 @@ Requests, Controller, Response Lifecycle ---------------------------------------- Every request handled by a Symfony project goes through the same simple lifecycle. -The framework takes care of the repetitive tasks and ultimately executes a -controller, which houses your custom application code: +The framework takes care of all the repetitive stuff: you just need to write +your custom code in the controller function: #. Each request is handled by a single front controller file (e.g. ``app.php`` or ``app_dev.php``) that bootstraps the application; @@ -87,14 +87,13 @@ A Simple Controller ------------------- While a controller can be any PHP callable (a function, method on an object, -or a ``Closure``), in Symfony, a controller is usually a single method inside -a controller object. Controllers are also called *actions*. +or a ``Closure``), a controller is usually a method inside a controller class. +Controllers are also called *actions*. .. code-block:: php - :linenos: - // src/Acme/HelloBundle/Controller/HelloController.php - namespace Acme\HelloBundle\Controller; + // src/AppBundle/Controller/HelloController.php + namespace AppBundle\Controller; use Symfony\Component\HttpFoundation\Response; @@ -146,12 +145,32 @@ to the controller: .. configuration-block:: + .. code-block:: php-annotations + + // src/AppBundle/Controller/HelloController.php + namespace AppBundle\Controller; + + use Symfony\Component\HttpFoundation\Response; + use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; + + class HelloController + { + /** + * @Route("/hello/{name}", name="hello") + */ + public function indexAction($name) + { + return new Response('Hello '.$name.'!'); + } + } + .. code-block:: yaml # app/config/routing.yml hello: path: /hello/{name} - defaults: { _controller: AcmeHelloBundle:Hello:index } + # uses a special syntax to point to the controller - see note below + defaults: { _controller: AppBundle:Hello:index } .. code-block:: xml @@ -163,7 +182,8 @@ to the controller: http://symfony.com/schema/routing/routing-1.0.xsd"> - AcmeHelloBundle:Hello:index + + AppBundle:Hello:index @@ -175,34 +195,30 @@ to the controller: $collection = new RouteCollection(); $collection->add('hello', new Route('/hello/{name}', array( - '_controller' => 'AcmeHelloBundle:Hello:index', + // uses a special syntax to point to the controller - see note below + '_controller' => 'AppBundle:Hello:index', ))); return $collection; -Going to ``/hello/ryan`` now executes the ``HelloController::indexAction()`` -controller and passes in ``ryan`` for the ``$name`` variable. Creating a -"page" means simply creating a controller method and associated route. - -Notice the syntax used to refer to the controller: ``AcmeHelloBundle:Hello:index``. -Symfony uses a flexible string notation to refer to different controllers. -This is the most common syntax and tells Symfony to look for a controller -class called ``HelloController`` inside a bundle named ``AcmeHelloBundle``. The -method ``indexAction()`` is then executed. +Now, you can go to ``/hello/ryan`` (e.g. ``http://localhost:8000/app_dev.php/hello/ryan`` +if you're using the :doc:`built-in web server `) +and Symfony will execute the ``HelloController::indexAction()`` controller +and pass in ``ryan`` for the ``$name`` variable. Creating a "page" means +simply creating a controller method and an associated route. -For more details on the string format used to reference different controllers, -see :ref:`controller-string-syntax`. +Simple, right? -.. note:: +.. sidebar:: The AppBundle:Hello:index controller syntax - This example places the routing configuration directly in the ``app/config/`` - directory. A better way to organize your routes is to place each route - in the bundle it belongs to. For more information on this, see - :ref:`routing-include-external-resources`. + If you use the YML or XML formats, you'll refer to the controller using + a special shortcut syntax: ``AppBundle:Hello:index``. For more details + on the controller format, see :ref:`controller-string-syntax`. -.. tip:: +.. seealso:: - You can learn much more about the routing system in the :doc:`Routing chapter `. + You can learn much more about the routing system in the + :doc:`Routing chapter `. .. index:: single: Controller; Controller arguments @@ -212,38 +228,55 @@ see :ref:`controller-string-syntax`. Route Parameters as Controller Arguments ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -You already know that the ``_controller`` parameter ``AcmeHelloBundle:Hello:index`` -refers to a ``HelloController::indexAction()`` method that lives inside the -``AcmeHelloBundle`` bundle. What's more interesting is the arguments that are -passed to that method:: +You already know that the route points to the +``HelloController::indexAction()`` method that lives inside AppBundle. What's +more interesting is the argument that is passed to that method:: - // src/Acme/HelloBundle/Controller/HelloController.php - namespace Acme\HelloBundle\Controller; + // src/AppBundle/Controller/HelloController.php + // ... + use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; - use Symfony\Bundle\FrameworkBundle\Controller\Controller; - - class HelloController extends Controller + /** + * @Route("/hello/{name}", name="hello") + */ + public function indexAction($name) { - public function indexAction($name) - { - // ... - } + // ... } The controller has a single argument, ``$name``, which corresponds to the -``{name}`` parameter from the matched route (``ryan`` in the example). In -fact, when executing your controller, Symfony matches each argument of -the controller with a parameter from the matched route. Take the following -example: +``{name}`` parameter from the matched route (``ryan`` if you go to ``/hello/ryan``). +When executing your controller, Symfony matches each argument with a parameter +from the route. So the value for ``{name}`` is passed to ``$name``. + +Take the following more-interesting example: .. configuration-block:: + .. code-block:: php-annotations + + // src/AppBundle/Controller/HelloController.php + // ... + + use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; + + class HelloController + { + /** + * @Route("/hello/{firstName}/{lastName}", name="hello") + */ + public function indexAction($firstName, $lastName) + { + // ... + } + } + .. code-block:: yaml # app/config/routing.yml hello: path: /hello/{firstName}/{lastName} - defaults: { _controller: AcmeHelloBundle:Hello:index, color: green } + defaults: { _controller: AppBundle:Hello:index } .. code-block:: xml @@ -255,8 +288,7 @@ example: http://symfony.com/schema/routing/routing-1.0.xsd"> - AcmeHelloBundle:Hello:index - green + AppBundle:Hello:index @@ -268,36 +300,28 @@ example: $collection = new RouteCollection(); $collection->add('hello', new Route('/hello/{firstName}/{lastName}', array( - '_controller' => 'AcmeHelloBundle:Hello:index', - 'color' => 'green', + '_controller' => 'AppBundle:Hello:index', ))); return $collection; -The controller for this can take several arguments:: +Now, the controller can have two arguments:: - public function indexAction($firstName, $lastName, $color) + public function indexAction($firstName, $lastName) { // ... } -Notice that both placeholder variables (``{firstName}``, ``{lastName}``) -as well as the default ``color`` variable are available as arguments in the -controller. When a route is matched, the placeholder variables are merged -with the ``defaults`` to make one array that's available to your controller. - Mapping route parameters to controller arguments is easy and flexible. Keep the following guidelines in mind while you develop. * **The order of the controller arguments does not matter** - Symfony is able to match the parameter names from the route to the variable - names in the controller method's signature. In other words, it realizes that - the ``{lastName}`` parameter matches up with the ``$lastName`` argument. - The arguments of the controller could be totally reordered and still work - perfectly:: + Symfony matches the parameter **names** from the route to the variable + **names** of the controller. The arguments of the controller could be totally + reordered and still work perfectly:: - public function indexAction($lastName, $color, $firstName) + public function indexAction($lastName, $firstName) { // ... } @@ -307,7 +331,7 @@ the following guidelines in mind while you develop. The following would throw a ``RuntimeException`` because there is no ``foo`` parameter defined in the route:: - public function indexAction($firstName, $lastName, $color, $foo) + public function indexAction($firstName, $lastName, $foo) { // ... } @@ -315,7 +339,7 @@ the following guidelines in mind while you develop. Making the argument optional, however, is perfectly ok. The following example would not throw an exception:: - public function indexAction($firstName, $lastName, $color, $foo = 'bar') + public function indexAction($firstName, $lastName, $foo = 'bar') { // ... } @@ -325,7 +349,7 @@ the following guidelines in mind while you develop. If, for example, the ``lastName`` weren't important for your controller, you could omit it entirely:: - public function indexAction($firstName, $color) + public function indexAction($firstName) { // ... } @@ -334,100 +358,70 @@ the following guidelines in mind while you develop. Every route also has a special ``_route`` parameter, which is equal to the name of the route that was matched (e.g. ``hello``). Though not usually - useful, this is equally available as a controller argument. + useful, this is also available as a controller argument. You can also + pass other variables from your route to your controller arguments. See + :doc:`/cookbook/routing/extra_information`. .. _book-controller-request-argument: The ``Request`` as a Controller Argument ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -For convenience, you can also have Symfony pass you the ``Request`` object -as an argument to your controller. This is especially convenient when you're -working with forms, for example:: +What if you need to read query parameters, grab a request header or get access +to an uploaded file? All of that information is stored in Symfony's ``Request`` +object. To get it in your controller, just add it as an argument and +**type-hint it with the Request class**:: use Symfony\Component\HttpFoundation\Request; - public function updateAction(Request $request) + public function indexAction($firstName, $lastName, Request $request) { - $form = $this->createForm(...); + $page = $request->query->get('page', 1); - $form->handleRequest($request); // ... } -.. index:: - single: Controller; Base controller class +.. seealso:: -Creating Static Pages ---------------------- + Want to know more about getting information from the request? See + :ref:`Access Request Information `. -You can create a static page without even creating a controller (only a route -and template are needed). - -Use it! See :doc:`/cookbook/templating/render_without_controller`. +.. index:: + single: Controller; Base controller class The Base Controller Class ------------------------- -For convenience, Symfony comes with a base ``Controller`` class that assists -with some of the most common controller tasks and gives your controller class -access to any resource it might need. By extending this ``Controller`` class, -you can take advantage of several helper methods. +For convenience, Symfony comes with an optional base ``Controller`` class. +If you extend it, you'll get access to a number of helper methods and all +of your service objects via the container (see :ref:`controller-accessing-services`). Add the ``use`` statement atop the ``Controller`` class and then modify the ``HelloController`` to extend it:: - // src/Acme/HelloBundle/Controller/HelloController.php - namespace Acme\HelloBundle\Controller; + // src/AppBundle/Controller/HelloController.php + namespace AppBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; - use Symfony\Component\HttpFoundation\Response; class HelloController extends Controller { - public function indexAction($name) - { - return new Response('Hello '.$name.'!'); - } + // ... } -This doesn't actually change anything about how your controller works. In -the next section, you'll learn about the helper methods that the base controller -class makes available. These methods are just shortcuts to using core Symfony -functionality that's available to you with or without the use of the base -``Controller`` class. A great way to see the core functionality in action -is to look in the -:class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller` class -itself. - -.. tip:: - - Extending the base class is *optional* in Symfony; it contains useful - shortcuts but nothing mandatory. You can also extend - :class:`Symfony\\Component\\DependencyInjection\\ContainerAware` or use - the class:`Symfony\\Component\\DependencyInjection\\ContainerAwareTrait` trait - (if you have PHP 5.4). The service container object will then be accessible - via the ``container`` property. - -.. versionadded:: 2.4 - The ``ContainerAwareTrait`` was introduced in Symfony 2.4. - -.. note:: - - You can also define your :doc:`Controllers as Services `. - This is optional, but can give you more control over the exact dependencies - that are injected into your controllers. +This doesn't actually change anything about how your controller works: it +just gives you access to helper methods that the base controller class makes +available. These are just shortcuts to using core Symfony functionality that's +available to you with or without the use of the base ``Controller`` class. +A great way to see the core functionality in action is to look in the +`Controller class`_. -.. index:: - single: Controller; Common tasks - -Common Controller Tasks ------------------------ +.. seealso:: -Though a controller can do virtually anything, most controllers will perform -the same basic tasks over and over again. These tasks, such as redirecting, -forwarding, rendering templates and accessing core services, are very easy -to manage in Symfony. + If you're curious about how a controller would work that did *not* extend + this base class, check out :doc:`Controllers as Services `. + This is optional, but can give you more control over the exact objects/dependencies + that are injected into your controller. .. index:: single: Controller; Redirecting @@ -435,7 +429,9 @@ to manage in Symfony. Redirecting ~~~~~~~~~~~ -If you want to redirect the user to another page, use the ``redirect()`` method:: +If you want to redirect the user to another page, use the +:method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::redirect` +method:: public function indexAction() { @@ -463,73 +459,6 @@ perform a 301 (permanent) redirect, modify the second argument:: return new RedirectResponse($this->generateUrl('homepage')); -.. index:: - single: Controller; Forwarding - -Forwarding -~~~~~~~~~~ - -You can also easily forward to another controller internally with the -:method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::forward` -method. Instead of redirecting the user's browser, it makes an internal sub-request, -and calls the specified controller. The ``forward()`` method returns the ``Response`` -object that's returned from that controller:: - - public function indexAction($name) - { - $response = $this->forward('AcmeHelloBundle:Hello:fancy', array( - 'name' => $name, - 'color' => 'green', - )); - - // ... further modify the response or return it directly - - return $response; - } - -Notice that the ``forward()`` method uses the same string representation of -the controller used in the routing configuration. In this case, the target -controller class will be ``HelloController`` inside some ``AcmeHelloBundle``. -The array passed to the method becomes the arguments on the resulting controller. -This same interface is used when embedding controllers into templates (see -:ref:`templating-embedding-controller`). The target controller method should -look something like the following:: - - public function fancyAction($name, $color) - { - // ... create and return a Response object - } - -And just like when creating a controller for a route, the order of the arguments -to ``fancyAction`` doesn't matter. Symfony matches the index key names -(e.g. ``name``) with the method argument names (e.g. ``$name``). If you -change the order of the arguments, Symfony will still pass the correct -value to each variable. - -.. tip:: - - Like other base ``Controller`` methods, the ``forward`` method is just - a shortcut for core Symfony functionality. A forward can be accomplished - directly by duplicating the current request. When this - :ref:`sub request ` is executed via the ``http_kernel`` - service the ``HttpKernel`` returns a ``Response`` object:: - - use Symfony\Component\HttpKernel\HttpKernelInterface; - - $path = array( - '_controller' => 'AcmeHelloBundle:Hello:fancy', - 'name' => $name, - 'color' => 'green', - ); - $request = $this->container->get('request'); - $subRequest = $request->duplicate(array(), null, $path); - - $httpKernel = $this->container->get('http_kernel'); - $response = $httpKernel->handle( - $subRequest, - HttpKernelInterface::SUB_REQUEST - ); - .. index:: single: Controller; Rendering templates @@ -538,71 +467,43 @@ value to each variable. Rendering Templates ~~~~~~~~~~~~~~~~~~~ -Though not a requirement, most controllers will ultimately render a template -that's responsible for generating the HTML (or other format) for the controller. -The ``renderView()`` method renders a template and returns its content. The -content from the template can be used to create a ``Response`` object:: - - use Symfony\Component\HttpFoundation\Response; - - $content = $this->renderView( - 'AcmeHelloBundle:Hello:index.html.twig', - array('name' => $name) - ); +If you're serving HTML, you'll want to render a template. The ``render()`` +method renders a template **and** puts that content into a ``Response`` +object for you:: - return new Response($content); + // renders app/Resources/views/Hello/index.html.twig + return $this->render('Hello/index.html.twig', array('name' => $name)); -This can even be done in just one step with the ``render()`` method, which -returns a ``Response`` object containing the content from the template:: +You can also put templates in deeper sub-directories. Just try to avoid creating +unnecessarily deep structures:: - return $this->render( - 'AcmeHelloBundle:Hello:index.html.twig', - array('name' => $name) - ); - -In both cases, the ``Resources/views/Hello/index.html.twig`` template inside -the ``AcmeHelloBundle`` will be rendered. + // renders app/Resources/views/Hello/Greetings/index.html.twig + return $this->render('Hello/Greetings/index.html.twig', array('name' => $name)); The Symfony templating engine is explained in great detail in the :doc:`Templating ` chapter. -.. tip:: - - You can even avoid calling the ``render`` method by using the ``@Template`` - annotation. See the - :doc:`FrameworkExtraBundle documentation ` - more details. - -.. tip:: - - The ``renderView`` method is a shortcut to direct use of the ``templating`` - service. The ``templating`` service can also be used directly:: - - $templating = $this->get('templating'); - $content = $templating->render( - 'AcmeHelloBundle:Hello:index.html.twig', - array('name' => $name) - ); - -.. note:: +.. sidebar:: Referencing Templates that Live inside the Bundle - It is possible to render templates in deeper subdirectories as well, however - be careful to avoid the pitfall of making your directory structure unduly - elaborate:: - - $templating->render( - 'AcmeHelloBundle:Hello/Greetings:index.html.twig', - array('name' => $name) - ); - // index.html.twig found in Resources/views/Hello/Greetings - // is rendered. + You can also put templates in the ``Resources/views`` directory of a + bundle and reference them with a + ``BundleName:DirectoryName:FileName`` syntax. For example, + ``AppBundle:Hello:index.html.twig`` would refer to the template located in + ``src/AppBundle/Resources/views/Hello/index.html.twig``. See :ref:`template-referencing-in-bundle`. .. index:: single: Controller; Accessing services +.. _controller-accessing-services: + Accessing other Services ~~~~~~~~~~~~~~~~~~~~~~~~ +Symfony comes packed with a lot of useful objects, called services. These +are used for rendering templates, sending emails, querying the database and +any other "work" you can think of. When you install a new bundle, it probably +brings in even *more* services. + When extending the base controller class, you can access any Symfony service via the ``get()`` method. Here are several common services you might need:: @@ -612,13 +513,15 @@ via the ``get()`` method. Here are several common services you might need:: $mailer = $this->get('mailer'); -There are countless other services available and you are encouraged to define -your own. To list all available services, use the ``container:debug`` console -command: +What other services exist? You can list all services, use the ``debug:container`` +console command: .. code-block:: bash - $ php app/console container:debug + $ php app/console debug:container + +.. versionadded:: 2.6 + Prior to Symfony 2.6, this command was called ``container:debug``. For more information, see the :doc:`/book/service_container` chapter. @@ -644,7 +547,8 @@ If you're extending the base controller class, do the following:: return $this->render(...); } -The ``createNotFoundException()`` method creates a special ``NotFoundHttpException`` +The ``createNotFoundException()`` method is just a shortcut to create a +special :class:`Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException` object, which ultimately triggers a 404 HTTP response inside Symfony. Of course, you're free to throw any ``Exception`` class in your controller - @@ -654,9 +558,11 @@ Symfony will automatically return a 500 HTTP response code. throw new \Exception('Something went wrong!'); -In every case, a styled error page is shown to the end user and a full debug -error page is shown to the developer (when viewing the page in debug mode). -Both of these error pages can be customized. For details, read the +In every case, an error page is shown to the end user and a full debug +error page is shown to the developer (i.e. when you're using ``app_dev.php`` - +see :ref:`page-creation-environments`). + +You'll want to customize the error page your user sees. To do that, see the ":doc:`/cookbook/controller/error_pages`" cookbook recipe. .. index:: @@ -701,7 +607,7 @@ Flash Messages You can also store small messages that will be stored on the user's session for exactly one additional request. This is useful when processing a form: -you want to redirect and have a special message shown on the *next* request. +you want to redirect and have a special message shown on the *next* page. These types of messages are called "flash" messages. For example, imagine you're processing a form submit:: @@ -717,7 +623,7 @@ For example, imagine you're processing a form submit:: if ($form->isValid()) { // do some sort of processing - $this->get('session')->getFlashBag()->add( + $request->getSession()->getFlashBag()->add( 'notice', 'Your changes were saved!' ); @@ -729,11 +635,11 @@ For example, imagine you're processing a form submit:: } After processing the request, the controller sets a ``notice`` flash message -and then redirects. The name (``notice``) isn't significant - it's just what -you're using to identify the type of the message. +in the session and then redirects. The name (``notice``) isn't significant - +it's just something you invent and reference next. -In the template of the next action, the following code could be used to render -the ``notice`` message: +In the template of the next page (or even better, in your base layout template), +the following code will render the ``notice`` message: .. configuration-block:: @@ -751,7 +657,7 @@ the ``notice`` message:
$message
" ?> - + By design, flash messages are meant to live for exactly one request (they're "gone in a flash"). They're designed to be used across redirects exactly as @@ -764,9 +670,9 @@ The Response Object ------------------- The only requirement for a controller is to return a ``Response`` object. The -:class:`Symfony\\Component\\HttpFoundation\\Response` class is a PHP -abstraction around the HTTP response - the text-based message filled with HTTP -headers and content that's sent back to the client:: +:class:`Symfony\\Component\\HttpFoundation\\Response` class is an abstraction +around the HTTP response: the text-based message filled with headers and +content that's sent back to the client:: use Symfony\Component\HttpFoundation\Response; @@ -777,25 +683,26 @@ headers and content that's sent back to the client:: $response = new Response(json_encode(array('name' => $name))); $response->headers->set('Content-Type', 'application/json'); -.. versionadded:: 2.4 - Support for HTTP status code constants was introduced in Symfony 2.4. +The ``headers`` property is a :class:`Symfony\\Component\\HttpFoundation\\HeaderBag` +object and has some nice methods for getting and setting the headers. The +header names are normalized so that using ``Content-Type`` is equivalent to +``content-type`` or even ``content_type``. -.. tip:: +There are also special classes to make certain kinds of responses easier: - The ``headers`` property is a - :class:`Symfony\\Component\\HttpFoundation\\HeaderBag` object with several - useful methods for reading and mutating the ``Response`` headers. The - header names are normalized so that using ``Content-Type`` is equivalent - to ``content-type`` or even ``content_type``. +* For JSON, there is :class:`Symfony\\Component\\HttpFoundation\\JsonResponse`. + See :ref:`component-http-foundation-json-response`. -.. tip:: +* For files, there is :class:`Symfony\\Component\\HttpFoundation\\BinaryFileResponse`. + See :ref:`component-http-foundation-serving-files`. + +* For streamed responses, there is :class:`Symfony\\Component\\HttpFoundation\\StreamedResponse`. + See :ref:`streaming-response`. - There are also special classes to make certain kinds of responses easier: +.. seealso:: - - For JSON, there is :class:`Symfony\\Component\\HttpFoundation\\JsonResponse`. - See :ref:`component-http-foundation-json-response`. - - For files, there is :class:`Symfony\\Component\\HttpFoundation\\BinaryFileResponse`. - See :ref:`component-http-foundation-serving-files`. + Don't worry! There is a lot more information about the Response object + in the component documentation. See :ref:`component-http-foundation-response`. .. index:: single: Controller; Request object @@ -824,13 +731,69 @@ controller if a variable is type-hinted with Like the ``Response`` object, the request headers are stored in a ``HeaderBag`` object and are easily accessible. +.. seealso:: + + Don't worry! There is a lot more information about the Request object + in the component documentation. See :ref:`component-http-foundation-request`. + +Creating Static Pages +--------------------- + +You can create a static page without even creating a controller (only a route +and template are needed). + +See :doc:`/cookbook/templating/render_without_controller`. + +.. index:: + single: Controller; Forwarding + +Forwarding to Another Controller +-------------------------------- + +Though not very common, you can also forward to another controller internally +with the :method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::forward` +method. Instead of redirecting the user's browser, it makes an internal sub-request, +and calls the controller. The ``forward()`` method returns the ``Response`` +object that's returned from *that* controller:: + + public function indexAction($name) + { + $response = $this->forward('AppBundle:Something:fancy', array( + 'name' => $name, + 'color' => 'green', + )); + + // ... further modify the response or return it directly + + return $response; + } + +Notice that the ``forward()`` method uses a special string representation +of the controller (see :ref:`controller-string-syntax`). In this case, the +target controller function will be ``SomethingController::fancyAction()`` +inside the AppBundle. The array passed to the method becomes the arguments on +the resulting controller. This same idea is used when embedding controllers +into templates (see :ref:`templating-embedding-controller`). The target +controller method would look something like this:: + + public function fancyAction($name, $color) + { + // ... create and return a Response object + } + +Just like when creating a controller for a route, the order of the arguments of +``fancyAction`` doesn't matter. Symfony matches the index key names (e.g. +``name``) with the method argument names (e.g. ``$name``). If you change the +order of the arguments, Symfony will still pass the correct value to each +variable. + Final Thoughts -------------- Whenever you create a page, you'll ultimately need to write some code that contains the logic for that page. In Symfony, this is called a controller, -and it's a PHP function that can do anything it needs in order to return -the final ``Response`` object that will be returned to the user. +and it's a PHP function where you can do anything in order to return the +final ``Response`` object that will be returned to the user. To make life easier, you can choose to extend a base ``Controller`` class, which contains shortcut methods for many common controller tasks. For example, @@ -846,3 +809,5 @@ Learn more from the Cookbook * :doc:`/cookbook/controller/error_pages` * :doc:`/cookbook/controller/service` + +.. _`Controller class`: https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php diff --git a/book/doctrine.rst b/book/doctrine.rst index 39a07663ad0..e60ab0118fe 100644 --- a/book/doctrine.rst +++ b/book/doctrine.rst @@ -5,11 +5,13 @@ Databases and Doctrine ====================== One of the most common and challenging tasks for any application -involves persisting and reading information to and from a database. Fortunately, -Symfony comes integrated with `Doctrine`_, a library whose sole goal is to -give you powerful tools to make this easy. In this chapter, you'll learn the -basic philosophy behind Doctrine and see how easy working with a database can -be. +involves persisting and reading information to and from a database. Although +the Symfony full-stack framework doesn't integrate any ORM by default, +the Symfony Standard Edition, which is the most widely used distribution, +comes integrated with `Doctrine`_, a library whose sole goal is to give +you powerful tools to make this easy. In this chapter, you'll learn the +basic philosophy behind Doctrine and see how easy working with a database +can be. .. note:: @@ -20,7 +22,7 @@ be. easy, and explained in the ":doc:`/cookbook/doctrine/dbal`" cookbook entry. You can also persist data to `MongoDB`_ using Doctrine ODM library. For - more information, read the ":doc:`/bundles/DoctrineMongoDBBundle/index`" + more information, read the "`DoctrineMongoDBBundle`_" documentation. A Simple Example: A Product @@ -30,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 ~~~~~~~~~~~~~~~~~~~~~~~~ @@ -84,8 +77,10 @@ information. By convention, this information is usually configured in an + xsi:schemaLocation="http://symfony.com/schema/dic/services + http://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/doctrine + http://symfony.com/schema/dic/doctrine/doctrine-1.0.xsd"> + xsi:schemaLocation="http://symfony.com/schema/dic/services + http://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/doctrine + http://symfony.com/schema/dic/doctrine/doctrine-1.0.xsd"> + - + @@ -348,7 +343,7 @@ see the :ref:`book-doctrine-field-types` section. You can also check out Doctrine's `Basic Mapping Documentation`_ for all details about mapping information. If you use annotations, you'll - need to prepend all annotations with ``ORM\`` (e.g. ``ORM\Column(..)``), + need to prepend all annotations with ``ORM\`` (e.g. ``ORM\Column(...)``), which is not shown in Doctrine's documentation. You'll also need to include the ``use Doctrine\ORM\Mapping as ORM;`` statement, which *imports* the ``ORM`` annotations prefix. @@ -366,7 +361,7 @@ see the :ref:`book-doctrine-field-types` section. .. note:: - When using another library or program (ie. Doxygen) that uses annotations, + When using another library or program (e.g. Doxygen) that uses annotations, you should place the ``@IgnoreAnnotation`` annotation on the class to indicate which annotations Symfony should ignore. @@ -392,9 +387,9 @@ 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 +This command makes sure that all the getters and setters are generated for the ``Product`` class. This is a safe command - you can run it over and over again: it only generates getters and setters that don't exist (i.e. it doesn't replace your existing methods). @@ -433,13 +428,16 @@ 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:: Doctrine doesn't care whether your properties are ``protected`` or ``private``, - or whether or not you have a getter or setter function for a property. + or whether you have a getter or setter function for a property. The getters and setters are generated here only because you'll need them to interact with your PHP object. @@ -469,10 +467,10 @@ in your application. To do this, run: new column to the existing ``product`` table. An even better way to take advantage of this functionality is via - :doc:`migrations `, which allow you to - generate these SQL statements and store them in migration classes that - can be run systematically on your production server in order to track - and migrate your database schema safely and reliably. + `migrations`_, which allow you to generate these SQL statements and store + them in migration classes that can be run systematically on your production + server in order to track and migrate your database schema safely and + reliably. Your database now has a fully-functional ``product`` table with columns that match the metadata you've specified. @@ -483,17 +481,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(); @@ -502,6 +499,7 @@ of the bundle: $product->setDescription('Lorem ipsum dolor'); $em = $this->getDoctrine()->getManager(); + $em->persist($product); $em->flush(); @@ -524,17 +522,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 @@ -542,13 +540,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 most efficient query/queries possible. For example, if you persist a - total of 100 ``Product`` objects and then subsequently call ``flush()``, - Doctrine will create a *single* prepared statement and re-use it for each - insert. This pattern is called *Unit of Work*, and it's used because it's - fast and efficient. + 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 @@ -558,7 +555,7 @@ an ``UPDATE`` query if the record already exists in the database. Doctrine provides a library that allows you to programmatically load testing data into your project (i.e. "fixture data"). For information, see - :doc:`/bundles/DoctrineFixturesBundle/index`. + the "`DoctrineFixturesBundle`_" documentation. Fetching Objects from the Database ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -570,7 +567,7 @@ on its ``id`` value:: public function showAction($id) { $product = $this->getDoctrine() - ->getRepository('AcmeStoreBundle:Product') + ->getRepository('AppBundle:Product') ->find($id); if (!$product) { @@ -585,8 +582,7 @@ on its ``id`` value:: .. tip:: You can achieve the equivalent of this without writing any code by using - the ``@ParamConverter`` shortcut. See the - :doc:`FrameworkExtraBundle documentation ` + the ``@ParamConverter`` shortcut. See the `FrameworkExtraBundle documentation`_ for more details. When you query for a particular type of object, you always use what's known @@ -595,12 +591,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 +656,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 +722,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') @@ -747,8 +743,8 @@ normal ``Query`` object, which can be used to get the result of the query. (``:price`` in the example above) as it prevents SQL injection attacks. The ``getResult()`` method returns an array of results. To get only one -result, you can use ``getSingleResult()`` (which throws exception there is no -result) or ``getOneOrNullResult()``:: +result, you can use ``getSingleResult()`` (which throws an exception if there +is no result) or ``getOneOrNullResult()``:: $product = $query->getOneOrNullResult(); @@ -764,7 +760,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,13 +769,13 @@ 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). The DQL syntax is incredibly powerful, allowing you to easily join between entities (the topic of :ref:`relations ` will be -covered later), group, etc. For more information, see the official Doctrine +covered later), group, etc. For more information, see the official `Doctrine Query Language`_ documentation. Custom Repository Classes @@ -796,13 +792,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 +807,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,16 +835,16 @@ 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, +repository class. This method will query for all the ``Product`` entities, 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 +854,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,8 +868,8 @@ 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') - ->findAllOrderedByName(); + $products = $em->getRepository('AppBundle:Product') + ->findAllOrderedByName(); .. note:: @@ -893,7 +889,9 @@ 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 +906,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,26 +928,27 @@ 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: products: targetEntity: Product mappedBy: category - # don't forget to init the collection in the __construct() method of the entity + # don't forget to init the collection in the __construct() method + # of the entity .. code-block:: xml - + - + + - + `. + `migrations`_. Saving Related Entities ~~~~~~~~~~~~~~~~~~~~~~~ @@ -1091,8 +1090,8 @@ Now you can see this new code in action! Imagine you're inside a controller:: // ... - use Acme\StoreBundle\Entity\Category; - use Acme\StoreBundle\Entity\Product; + use AppBundle\Entity\Category; + use AppBundle\Entity\Product; use Symfony\Component\HttpFoundation\Response; class DefaultController extends Controller @@ -1105,6 +1104,7 @@ Now you can see this new code in action! Imagine you're inside a controller:: $product = new Product(); $product->setName('Foo'); $product->setPrice(19.99); + $product->setDescription('Lorem ipsum dolor'); // relate this product to the category $product->setCategory($category); @@ -1135,7 +1135,7 @@ did before. First, fetch a ``$product`` object and then access its related public function showAction($id) { $product = $this->getDoctrine() - ->getRepository('AcmeStoreBundle:Product') + ->getRepository('AppBundle:Product') ->find($id); $categoryName = $product->getCategory()->getName(); @@ -1159,10 +1159,10 @@ the category (i.e. it's "lazily loaded"). You can also query in the other direction:: - public function showProductAction($id) + public function showProductsAction($id) { $category = $this->getDoctrine() - ->getRepository('AcmeStoreBundle:Category') + ->getRepository('AppBundle:Category') ->find($id); $products = $category->getProducts(); @@ -1183,12 +1183,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 +1220,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 +1243,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 +1304,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 +1316,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 +1325,14 @@ the current date, only when the entity is first persisted (i.e. inserted): .. code-block:: xml - + - + @@ -1360,7 +1360,7 @@ Doctrine's `Lifecycle Events documentation`_. transforming data in the entity (e.g. setting a created/updated field, generating a slug value). - If you need to do some heavier lifting - like perform logging or send + If you need to do some heavier lifting - like performing logging or sending an email - you should register an external class as an event listener or subscriber and give it access to whatever resources you need. For more information, see :doc:`/cookbook/doctrine/event_listeners_subscribers`. @@ -1370,7 +1370,7 @@ Doctrine's `Lifecycle Events documentation`_. Doctrine Field Types Reference ------------------------------ -Doctrine comes with a large number of field types available. Each of these +Doctrine comes with numerous field types available. Each of these maps a PHP data type to a specific column type in whatever database you're using. For each field type, the ``Column`` can be configured further, setting the ``length``, ``nullable`` behavior, ``name`` and other options. To see a @@ -1380,7 +1380,7 @@ list of all available types and more information, see Doctrine's Summary ------- -With Doctrine, you can focus on your objects and how they're useful in your +With Doctrine, you can focus on your objects and how they're used in your application and worry about database persistence second. This is because Doctrine allows you to use any PHP object to hold your data and relies on mapping metadata information to map an object's data to a particular database @@ -1399,8 +1399,8 @@ For more information about Doctrine, see the *Doctrine* section of the * :doc:`/cookbook/doctrine/common_extensions` * :doc:`/cookbook/doctrine/console` -* :doc:`/bundles/DoctrineFixturesBundle/index` -* :doc:`/bundles/DoctrineMongoDBBundle/index` +* `DoctrineFixturesBundle`_ +* `DoctrineMongoDBBundle`_ .. _`Doctrine`: http://www.doctrine-project.org/ .. _`MongoDB`: http://www.mongodb.org/ @@ -1413,3 +1413,7 @@ For more information about Doctrine, see the *Doctrine* section of the .. _`Lifecycle Events documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html#lifecycle-events .. _`Reserved SQL keywords documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#quoting-reserved-words .. _`Persistent classes`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#persistent-classes +.. _`DoctrineMongoDBBundle`: http://symfony.com/doc/current/bundles/DoctrineMongoDBBundle/index.html +.. _`migrations`: http://symfony.com/doc/current/bundles/DoctrineMigrationsBundle/index.html +.. _`DoctrineFixturesBundle`: http://symfony.com/doc/current/bundles/DoctrineFixturesBundle/index.html +.. _`FrameworkExtraBundle documentation`: http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html diff --git a/book/forms.rst b/book/forms.rst index 11e2219f4df..7bdfd2d991c 100644 --- a/book/forms.rst +++ b/book/forms.rst @@ -6,14 +6,15 @@ Forms Dealing with HTML forms is one of the most common - and challenging - tasks for a web developer. Symfony integrates a Form component that makes dealing with -forms easy. In this chapter, you'll build a complex form from the ground-up, +forms easy. In this chapter, you'll build a complex form from the ground up, 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 @@ -26,13 +27,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 +56,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 +74,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 @@ -103,10 +93,10 @@ from inside a controller:: $form = $this->createFormBuilder($task) ->add('task', 'text') ->add('dueDate', 'date') - ->add('save', 'submit', array('label' => 'Create Post')) + ->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,15 +144,17 @@ helper functions: .. code-block:: html+jinja - {# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #} - - {{ form(form) }} + {# app/Resources/views/Default/new.html.twig #} + {{ 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 @@ -173,12 +165,27 @@ 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 attribute + when using file uploads. + +``form_widget(form)`` + Renders all the fields, which includes the field element itself, a label + and any validation error messages for the field. + +``form_end(form)`` + 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"). @@ -219,7 +226,7 @@ controller:: $form = $this->createFormBuilder($task) ->add('task', 'text') ->add('dueDate', 'date') - ->add('save', 'submit', array('label' => 'Create Post')) + ->add('save', 'submit', array('label' => 'Create Task')) ->getForm(); $form->handleRequest($request); @@ -273,6 +280,12 @@ possible paths: from being able to hit the "Refresh" button of their browser and re-post the data. +.. seealso:: + + If you need more control over exactly when your form is submitted or which + data is passed to it, you can use the :method:`Symfony\\Component\\Form\\FormInterface::submit` + for this. Read more about it :ref:`in the cookbook `. + .. index:: single: Forms; Multiple Submit Buttons @@ -291,7 +304,7 @@ To do this, add a second button with the caption "Save and add" to your form:: $form = $this->createFormBuilder($task) ->add('task', 'text') ->add('dueDate', 'date') - ->add('save', 'submit', array('label' => 'Create Post')) + ->add('save', 'submit', array('label' => 'Create Task')) ->add('saveAndAdd', 'submit', array('label' => 'Save and Add')) ->getForm(); @@ -333,8 +346,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: ~ @@ -344,7 +357,7 @@ object. .. code-block:: php-annotations - // Acme/TaskBundle/Entity/Task.php + // AppBundle/Entity/Task.php use Symfony\Component\Validator\Constraints as Assert; class Task @@ -363,14 +376,14 @@ object. .. code-block:: xml - + - + @@ -383,7 +396,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; @@ -429,14 +442,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'), )) ?> @@ -504,6 +515,8 @@ fields were submitted. If you want to suppress validation, you can use the .. index:: single: Forms; Validation groups based on submitted data +.. _book-form-validation-groups: + Groups based on the Submitted Data ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -513,11 +526,12 @@ to an array callback:: use Symfony\Component\OptionsResolver\OptionsResolverInterface; + // ... public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( 'validation_groups' => array( - 'Acme\AcmeBundle\Entity\Client', + 'AppBundle\Entity\Client', 'determineValidationGroups', ), )); @@ -528,23 +542,51 @@ This will call the static method ``determineValidationGroups()`` on the The Form object is passed as an argument to that method (see next example). You can also define whole logic inline by using a ``Closure``:: + use Acme\AcmeBundle\Entity\Client; use Symfony\Component\Form\FormInterface; use Symfony\Component\OptionsResolver\OptionsResolverInterface; + // ... public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( 'validation_groups' => function(FormInterface $form) { $data = $form->getData(); - if (Entity\Client::TYPE_PERSON == $data->getType()) { + if (Client::TYPE_PERSON == $data->getType()) { return array('person'); - } else { - return array('company'); } + + return array('company'); + }, + )); + } + +Using the ``validation_groups`` option overrides the default validation +group which is being used. If you want to validate the default constraints +of the entity as well you have to adjust the option as follows:: + + use Acme\AcmeBundle\Entity\Client; + use Symfony\Component\Form\FormInterface; + use Symfony\Component\OptionsResolver\OptionsResolverInterface; + + // ... + public function setDefaultOptions(OptionsResolverInterface $resolver) + { + $resolver->setDefaults(array( + 'validation_groups' => function(FormInterface $form) { + $data = $form->getData(); + if (Client::TYPE_PERSON == $data->getType()) { + return array('Default', 'person'); + } + + return array('Default', 'company'); }, )); } +You can find more information about how the validation groups and the default constraints +work in the book section about :ref:`validation groups `. + .. index:: single: Forms; Validation groups based on clicked button @@ -706,14 +748,16 @@ the correct values of a number of field options. And though you'll need to manually add your server-side validation, these field type options can then be guessed from that information. -* ``required``: The ``required`` option can be guessed based on the validation - rules (i.e. is the field ``NotBlank`` or ``NotNull``) or the Doctrine metadata - (i.e. is the field ``nullable``). This is very useful, as your client-side - validation will automatically match your validation rules. +``required`` + The ``required`` option can be guessed based on the validation rules (i.e. is + the field ``NotBlank`` or ``NotNull``) or the Doctrine metadata (i.e. is the + field ``nullable``). This is very useful, as your client-side validation will + automatically match your validation rules. -* ``max_length``: If the field is some sort of text field, then the ``max_length`` - option can be guessed from the validation constraints (if ``Length`` or - ``Range`` is used) or from the Doctrine metadata (via the field's length). +``max_length`` + If the field is some sort of text field, then the ``max_length`` option can be + guessed from the validation constraints (if ``Length`` or ``Range`` is used) or + from the Doctrine metadata (via the field's length). .. note:: @@ -723,7 +767,7 @@ the correct values of a number of field options. If you'd like to change one of the guessed values, you can override it by passing the option in the options field array:: - ->add('task', null, array('max_length' => 4)) + ->add('task', null, array('attr' => array('maxlength' => 4))) .. index:: single: Forms; Rendering in a template @@ -740,7 +784,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) }} @@ -750,7 +794,7 @@ of code. Of course, you'll usually need much more flexibility when rendering: .. code-block:: html+php - + start($form) ?> errors($form) ?> @@ -758,24 +802,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_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 `. +``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. 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`` +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:: @@ -962,19 +1002,12 @@ to the ``form()`` or the ``form_start()`` helper: .. code-block:: html+jinja - {# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #} - {{ form(form, {'action': path('target_route'), 'method': 'GET'}) }} - + {# app/Resources/views/Default/new.html.twig #} {{ 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', @@ -1002,8 +1035,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; @@ -1028,10 +1061,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() { @@ -1050,7 +1083,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 @@ -1062,7 +1095,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', )); } @@ -1113,16 +1146,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"> @@ -1140,11 +1173,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', @@ -1153,7 +1186,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() @@ -1166,7 +1199,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 @@ -1226,14 +1259,16 @@ objects. For example, a registration form may contain data belonging to a ``User`` object as well as many ``Address`` objects. Fortunately, this is easy and natural with the Form component. +.. _forms-embedding-single-object: + 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; @@ -1254,7 +1289,8 @@ 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; @@ -1271,11 +1307,17 @@ Next, add a new ``category`` property to the ``Task`` class:: } } +.. tip:: + + The ``Valid`` Constraint has been added to the property ``category``. This + cascades the validation to the corresponding entity. If you omit this constraint + the child entity would not be validated. + 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; @@ -1291,7 +1333,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', )); } @@ -1318,16 +1360,7 @@ class: } The fields from ``CategoryType`` can now be rendered alongside those from -the ``TaskType`` class. To activate validation on CategoryType, add -the ``cascade_validation`` option to ``TaskType``:: - - public function setDefaultOptions(OptionsResolverInterface $resolver) - { - $resolver->setDefaults(array( - 'data_class' => 'Acme\TaskBundle\Entity\Task', - 'cascade_validation' => true, - )); - } +the ``TaskType`` class. Render the ``Category`` fields in the same way as the original ``Task`` fields: @@ -1404,7 +1437,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 %}
@@ -1417,7 +1450,7 @@ do this, create a new template file that will store the new markup: .. code-block:: html+php - +
label($form, $label) ?> errors($form) ?> @@ -1433,19 +1466,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')) ?> @@ -1466,14 +1499,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:: @@ -1488,9 +1513,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`_). @@ -1521,7 +1547,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 @@ -1578,9 +1604,8 @@ file: # app/config/config.yml twig: - form: - resources: - - 'AcmeTaskBundle:Form:fields.html.twig' + form_themes: + - 'form/fields.html.twig' # ... .. code-block:: xml @@ -1594,9 +1619,7 @@ file: http://symfony.com/schema/dic/twig http://symfony.com/schema/dic/twig/twig-1.0.xsd"> - - AcmeTaskBundle:Form:fields.html.twig - + form/fields.html.twig @@ -1605,10 +1628,8 @@ file: // app/config/config.php $container->loadFromExtension('twig', array( - 'form' => array( - 'resources' => array( - 'AcmeTaskBundle:Form:fields.html.twig', - ), + 'form_themes' => array( + 'form/fields.html.twig', ), // ... )); @@ -1623,7 +1644,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 %} @@ -1653,7 +1674,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: @@ -1666,7 +1687,7 @@ file: templating: form: resources: - - 'AcmeTaskBundle:Form' + - 'Form' # ... .. code-block:: xml @@ -1682,7 +1703,7 @@ file: - AcmeTaskBundle:Form + Form @@ -1696,15 +1717,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 @@ -1744,7 +1765,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 @@ -1765,6 +1786,13 @@ section. The ``intention`` option is optional but greatly enhances the security of the generated token by making it different for each form. +.. caution:: + + CSRF tokens are meant to be different for every user. This is why you + need to be cautious if you try to cache pages with forms including this + kind of protection. For more information, see + :doc:`/cookbook/cache/form_csrf_caching`. + .. index:: single: Forms; With no class @@ -1821,7 +1849,7 @@ an array. You can also access POST values (in this case "name") directly through the request object, like so:: - $this->get('request')->request->get('name'); + $request->request->get('name'); Be advised, however, that in most cases using the ``getData()`` method is a better choice, since it returns the data (usually an object) after @@ -1900,10 +1928,12 @@ Learn more from the Cookbook * :doc:`/cookbook/form/form_customization` * :doc:`/cookbook/form/dynamic_form_modification` * :doc:`/cookbook/form/data_transformers` +* :doc:`/cookbook/security/csrf_in_login_form` +* :doc:`/cookbook/cache/form_csrf_caching` .. _`Symfony Form component`: https://github.com/symfony/Form .. _`DateTime`: http://php.net/manual/en/class.datetime.php -.. _`Twig Bridge`: https://github.com/symfony/symfony/tree/2.3/src/Symfony/Bridge/Twig -.. _`form_div_layout.html.twig`: https://github.com/symfony/symfony/blob/2.3/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig +.. _`Twig Bridge`: https://github.com/symfony/symfony/tree/master/src/Symfony/Bridge/Twig +.. _`form_div_layout.html.twig`: https://github.com/symfony/symfony/blob/master/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig .. _`Cross-site request forgery`: http://en.wikipedia.org/wiki/Cross-site_request_forgery -.. _`view on GitHub`: https://github.com/symfony/symfony/tree/2.3/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form +.. _`view on GitHub`: https://github.com/symfony/symfony/tree/master/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form diff --git a/book/from_flat_php_to_symfony2.rst b/book/from_flat_php_to_symfony2.rst index c28d5175695..cc72a9589e1 100644 --- a/book/from_flat_php_to_symfony2.rst +++ b/book/from_flat_php_to_symfony2.rst @@ -49,7 +49,7 @@ persisted to the database. Writing in flat PHP is quick and dirty: - + @@ -121,12 +121,12 @@ is primarily an HTML file that uses a template-like PHP syntax: - + -By convention, the file that contains all of the application logic - ``index.php`` - +By convention, the file that contains all the application logic - ``index.php`` - is known as a "controller". The term :term:`controller` is a word you'll hear a lot, regardless of the language or framework you use. It refers simply to the area of *your* code that processes user input and prepares the response. @@ -238,14 +238,14 @@ the layout: - + -You've now introduced a methodology that allows for the reuse of the -layout. Unfortunately, to accomplish this, you're forced to use a few ugly +You now have a setup that will allow you to reuse the layout. +Unfortunately, to accomplish this, you're forced to use a few ugly PHP functions (``ob_start()``, ``ob_get_clean()``) in the template. Symfony uses a Templating component that allows this to be accomplished cleanly and easily. You'll see it in action shortly. @@ -367,7 +367,7 @@ on the requested URI: require_once 'controllers.php'; // route the request internally - $uri = $_SERVER['REQUEST_URI']; + $uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); if ('/index.php' == $uri) { list_action(); } elseif ('/index.php/show' == $uri && isset($_GET['id'])) { @@ -435,7 +435,7 @@ content: { "require": { - "symfony/symfony": "2.4.*" + "symfony/symfony": "2.5.*" }, "autoload": { "files": ["model.php","controllers.php"] @@ -447,7 +447,7 @@ into a vendor/ directory: .. code-block:: bash - $ php composer.phar install + $ composer install Beside downloading your dependencies, Composer generates a ``vendor/autoload.php`` file, which takes care of autoloading for all the files in the Symfony Framework as well as @@ -484,9 +484,6 @@ the HTTP response being returned. Use them to improve the blog: // echo the headers and send the response $response->send(); -.. versionadded:: 2.4 - Support for HTTP status code constants was introduced in Symfony 2.4. - The controllers are now responsible for returning a ``Response`` object. To make this easier, you can add a new ``render_template()`` function, which, incidentally, acts quite a bit like the Symfony templating engine: @@ -550,8 +547,8 @@ from scratch, you could at least use Symfony's standalone `Routing`_ and Instead of re-solving common problems, you can let Symfony take care of them for you. Here's the same sample application, now built in Symfony:: - // src/Acme/BlogBundle/Controller/BlogController.php - namespace Acme\BlogBundle\Controller; + // src/AppBundle/Controller/BlogController.php + namespace AppBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; @@ -564,17 +561,14 @@ them for you. Here's the same sample application, now built in Symfony:: ->createQuery('SELECT p FROM AcmeBlogBundle:Post p') ->execute(); - return $this->render( - 'AcmeBlogBundle:Blog:list.html.php', - array('posts' => $posts) - ); + return $this->render('Blog/list.html.php', array('posts' => $posts)); } public function showAction($id) { $post = $this->get('doctrine') ->getManager() - ->getRepository('AcmeBlogBundle:Post') + ->getRepository('AppBundle:Post') ->find($id); if (!$post) { @@ -582,22 +576,19 @@ them for you. Here's the same sample application, now built in Symfony:: throw $this->createNotFoundException(); } - return $this->render( - 'AcmeBlogBundle:Blog:show.html.php', - array('post' => $post) - ); + return $this->render('Blog/show.html.php', array('post' => $post)); } } -The two controllers are still lightweight. Each uses the :doc:`Doctrine ORM library ` -to retrieve objects from the database and the Templating component to -render a template and return a ``Response`` object. The list template is -now quite a bit simpler: +The two controllers are still lightweight. Each uses the +:doc:`Doctrine ORM library ` to retrieve objects from the +database and the Templating component to render a template and return a +``Response`` object. The list template is now quite a bit simpler: .. code-block:: html+php - - extend('::layout.html.php') ?> + + extend('layout.html.php') ?> set('title', 'List of Posts') ?> @@ -612,7 +603,7 @@ now quite a bit simpler: getTitle() ?> - + The layout is nearly identical: @@ -647,11 +638,11 @@ A routing configuration map provides this information in a readable format: # app/config/routing.yml blog_list: path: /blog - defaults: { _controller: AcmeBlogBundle:Blog:list } + defaults: { _controller: AppBundle:Blog:list } blog_show: path: /blog/show/{id} - defaults: { _controller: AcmeBlogBundle:Blog:show } + defaults: { _controller: AppBundle:Blog:show } Now that Symfony is handling all the mundane tasks, the front controller is dead simple. And since it does so little, you'll never have to touch @@ -719,8 +710,8 @@ for example, the list template written in Twig: .. code-block:: html+jinja - {# src/Acme/BlogBundle/Resources/views/Blog/list.html.twig #} - {% extends "::layout.html.twig" %} + {# app/Resources/views/Blog/list.html.twig #} + {% extends "layout.html.twig" %} {% block title %}List of Posts{% endblock %} diff --git a/book/http_cache.rst b/book/http_cache.rst index 778fc287f7b..5cae7a710e2 100644 --- a/book/http_cache.rst +++ b/book/http_cache.rst @@ -202,36 +202,41 @@ method:: Here is a list of the main options: -* ``default_ttl``: The number of seconds that a cache entry should be - considered fresh when no explicit freshness information is provided in a - response. Explicit ``Cache-Control`` or ``Expires`` headers override this - value (default: ``0``); - -* ``private_headers``: Set of request headers that trigger "private" - ``Cache-Control`` behavior on responses that don't explicitly state whether - the response is ``public`` or ``private`` via a ``Cache-Control`` directive. - (default: ``Authorization`` and ``Cookie``); - -* ``allow_reload``: Specifies whether the client can force a cache reload by - including a ``Cache-Control`` "no-cache" directive in the request. Set it to - ``true`` for compliance with RFC 2616 (default: ``false``); - -* ``allow_revalidate``: Specifies whether the client can force a cache - revalidate by including a ``Cache-Control`` "max-age=0" directive in the - request. Set it to ``true`` for compliance with RFC 2616 (default: false); - -* ``stale_while_revalidate``: Specifies the default number of seconds (the - granularity is the second as the Response TTL precision is a second) during - which the cache can immediately return a stale response while it revalidates - it in the background (default: ``2``); this setting is overridden by the - ``stale-while-revalidate`` HTTP ``Cache-Control`` extension (see RFC 5861); - -* ``stale_if_error``: Specifies the default number of seconds (the granularity - is the second) during which the cache can serve a stale response when an - error is encountered (default: ``60``). This setting is overridden by the - ``stale-if-error`` HTTP ``Cache-Control`` extension (see RFC 5861). - -If ``debug`` is ``true``, Symfony automatically adds a ``X-Symfony-Cache`` +``default_ttl`` + The number of seconds that a cache entry should be considered fresh when no + explicit freshness information is provided in a response. Explicit + ``Cache-Control`` or ``Expires`` headers override this value (default: ``0``). + +``private_headers`` + Set of request headers that trigger "private" ``Cache-Control`` behavior on + responses that don't explicitly state whether the response is ``public`` or + ``private`` via a ``Cache-Control`` directive (default: ``Authorization`` + and ``Cookie``). + +``allow_reload`` + Specifies whether the client can force a cache reload by including a + ``Cache-Control`` "no-cache" directive in the request. Set it to ``true`` for + compliance with RFC 2616 (default: ``false``). + +``allow_revalidate`` + Specifies whether the client can force a cache revalidate by including a + ``Cache-Control`` "max-age=0" directive in the request. Set it to ``true`` for + compliance with RFC 2616 (default: false). + +``stale_while_revalidate`` + Specifies the default number of seconds (the granularity is the second as the + Response TTL precision is a second) during which the cache can immediately + return a stale response while it revalidates it in the background (default: + ``2``); this setting is overridden by the ``stale-while-revalidate`` HTTP + ``Cache-Control`` extension (see RFC 5861). + +``stale_if_error`` + Specifies the default number of seconds (the granularity is the second) during + which the cache can serve a stale response when an error is encountered + (default: ``60``). This setting is overridden by the ``stale-if-error`` HTTP + ``Cache-Control`` extension (see RFC 5861). + +If ``debug`` is ``true``, Symfony automatically adds an ``X-Symfony-Cache`` header to the response containing useful information about cache hits and misses. @@ -328,7 +333,14 @@ its creation more manageable:: // set a custom Cache-Control directive $response->headers->addCacheControlDirective('must-revalidate', true); -Public vs private Responses +.. tip:: + + If you need to set cache headers for many different controller actions, + you might want to look into the FOSHttpCacheBundle_. It provides a way + to define cache headers based on the URL pattern and other request + properties. + +Public vs Private Responses ~~~~~~~~~~~~~~~~~~~~~~~~~~~ Both gateway and proxy caches are considered "shared" caches as the cached @@ -339,11 +351,12 @@ and then returned to every subsequent user who asked for their account page! To handle this situation, every response may be set to be public or private: -* *public*: Indicates that the response may be cached by both private and - shared caches; +*public* + Indicates that the response may be cached by both private and shared caches. -* *private*: Indicates that all or part of the response message is intended - for a single user and must not be cached by a shared cache. +*private* + Indicates that all or part of the response message is intended for a single + user and must not be cached by a shared cache. Symfony conservatively defaults each response to be private. To take advantage of shared caches (like the Symfony reverse proxy), the response will need @@ -370,6 +383,8 @@ This has two very reasonable consequences: blog post). Caching them would prevent certain requests from hitting and mutating your application. +.. _http-cache-defaults: + Caching Rules and Defaults ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -393,9 +408,10 @@ header when none is set by the developer by following these rules: ``private`` directive automatically (except when ``s-maxage`` is set). .. _http-expiration-validation: +.. _http-expiration-and-validation: -HTTP Expiration and Validation ------------------------------- +HTTP Expiration, Validation and Invalidation +-------------------------------------------- The HTTP specification defines two caching models: @@ -412,7 +428,9 @@ The HTTP specification defines two caching models: header) to check if the page has changed since being cached. The goal of both models is to never generate the same response twice by relying -on a cache to store and return "fresh" responses. +on a cache to store and return "fresh" responses. To achieve long caching times +but still provide updated content immediately, *cache invalidation* is +sometimes used. .. sidebar:: Reading the HTTP Specification @@ -421,7 +439,7 @@ on a cache to store and return "fresh" responses. model of the specification dominates your work. Unfortunately, the actual specification document - `RFC 2616`_ - can be difficult to read. - There is an on-going effort (`HTTP Bis`_) to rewrite the RFC 2616. It does + There is an ongoing effort (`HTTP Bis`_) to rewrite the RFC 2616. It does not describe a new version of HTTP, but mostly clarifies the original HTTP specification. The organization is also improved as the specification is split into seven parts; everything related to HTTP caching can be @@ -476,7 +494,7 @@ The resulting HTTP header will look like this: timezone as required by the specification. Note that in HTTP versions before 1.1 the origin server wasn't required to -send the ``Date`` header. Consequently the cache (e.g. the browser) might +send the ``Date`` header. Consequently, the cache (e.g. the browser) might need to rely on the local clock to evaluate the ``Expires`` header making the lifetime calculation vulnerable to clock skew. Another limitation of the ``Expires`` header is that the specification states that "HTTP/1.1 @@ -522,9 +540,9 @@ won't be asked to return the updated response until the cache finally becomes stale. The validation model addresses this issue. Under this model, the cache continues -to store responses. The difference is that, for each request, the cache asks -the application whether or not the cached response is still valid. If the -cache *is* still valid, your application should return a 304 status code +to store responses. The difference is that, for each request, the cache asks the +application if the cached response is still valid or if it needs to be regenerated. +If the cache *is* still valid, your application should return a 304 status code and no content. This tells the cache that it's ok to return the cached response. Under this model, you only save CPU if you're able to determine that the @@ -763,11 +781,10 @@ at some interval (the expiration) to verify that the content is still valid. .. tip:: You can also define HTTP caching headers for expiration and validation by using - annotations. See the - :doc:`FrameworkExtraBundle documentation `. + annotations. See the `FrameworkExtraBundle documentation`_. .. index:: - pair: Cache; Configuration + pair: Cache; Configuration More Response Methods ~~~~~~~~~~~~~~~~~~~~~ @@ -795,8 +812,113 @@ Additionally, most cache-related HTTP headers can be set via the single )); .. index:: - single: Cache; ESI - single: ESI + single: Cache; Invalidation + +.. _http-cache-invalidation: + +Cache Invalidation +~~~~~~~~~~~~~~~~~~ + + "There are only two hard things in Computer Science: cache invalidation + and naming things." -- Phil Karlton + +Once an URL is cached by a gateway cache, the cache will not ask the +application for that content anymore. This allows the cache to provide fast +responses and reduces the load on your application. However, you risk +delivering outdated content. A way out of this dilemma is to use long +cache lifetimes, but to actively notify the gateway cache when content +changes. Reverse proxies usually provide a channel to receive such +notifications, typically through special HTTP requests. + +.. caution:: + + While cache invalidation is powerful, avoid it when possible. If you fail + to invalidate something, outdated caches will be served for a potentially + long time. Instead, use short cache lifetimes or use the validation model, + and adjust your controllers to perform efficient validation checks as + explained in :ref:`optimizing-cache-validation`. + + Furthermore, since invalidation is a topic specific to each type of reverse + proxy, using this concept will tie you to a specific reverse proxy or need + additional efforts to support different proxies. + +Sometimes, however, you need that extra performance you can get when +explicitly invalidating. For invalidation, your application needs to detect +when content changes and tell the cache to remove the URLs which contain +that data from its cache. + +.. tip:: + + If you want to use cache invalidation, have a look at the + `FOSHttpCacheBundle`_. This bundle provides services to help with various + cache invalidation concepts, and also documents the configuration for the + a couple of common caching proxies. + +If one content corresponds to one URL, the ``PURGE`` model works well. +You send a request to the cache proxy with the HTTP method ``PURGE`` (using +the word "PURGE" is a convention, technically this can be any string) instead +of ``GET`` and make the cache proxy detect this and remove the data from the +cache instead of going to the application to get a response. + +Here is how you can configure the Symfony reverse proxy to support the +``PURGE`` HTTP method:: + + // app/AppCache.php + + // ... + use Symfony\Bundle\FrameworkBundle\HttpCache\HttpCache; + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpFoundation\Response; + + class AppCache extends HttpCache + { + protected function invalidate(Request $request, $catch = false) + { + if ('PURGE' !== $request->getMethod()) { + return parent::invalidate($request, $catch); + } + + if ('127.0.0.1' !== $request->getClientIp()) { + return new Response( + 'Invalid HTTP method', + Response::HTTP_BAD_REQUEST + ); + } + + $response = new Response(); + if ($this->getStore()->purge($request->getUri())) { + $response->setStatusCode(200, 'Purged'); + } else { + $response->setStatusCode(200, 'Not found'); + } + + return $response; + } + } + +.. caution:: + + You must protect the ``PURGE`` HTTP method somehow to avoid random people + purging your cached data. + +**Purge** instructs the cache to drop a resource in *all its variants* +(according to the ``Vary`` header, see above). An alternative to purging is +**refreshing** a content. Refreshing means that the caching proxy is +instructed to discard its local cache and fetch the content again. This way, +the new content is already available in the cache. The drawback of refreshing +is that variants are not invalidated. + +In many applications, the same content bit is used on various pages with +different URLs. More flexible concepts exist for those cases: + +* **Banning** invalidates responses matching regular expressions on the + URL or other criteria; +* **Cache tagging** lets you add a tag for each content used in a response + so that you can invalidate all URLs containing a certain content. + +.. index:: + single: Cache; ESI + single: ESI .. _edge-side-includes: @@ -870,8 +992,10 @@ First, to use ESI, be sure to enable it in your application configuration: + xsi:schemaLocation="http://symfony.com/schema/dic/services + http://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/symfony + http://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> @@ -915,20 +1039,20 @@ matter), Symfony uses the standard ``render`` helper to configure ESI tags: .. code-block:: jinja {# you can use a controller reference #} - {{ render_esi(controller('...:news', { 'max': 5 })) }} + {{ render_esi(controller('...:news', { 'maxPerPage': 5 })) }} {# ... or a URL #} - {{ render_esi(url('latest_news', { 'max': 5 })) }} + {{ render_esi(url('latest_news', { 'maxPerPage': 5 })) }} .. code-block:: html+php render( - new \Symfony\Component\HttpKernel\Controller\ControllerReference('...:news', array('max' => 5)), + new \Symfony\Component\HttpKernel\Controller\ControllerReference('...:news', array('maxPerPage' => 5)), array('strategy' => 'esi')) ?> render( - $view['router']->generate('latest_news', array('max' => 5), true), + $view['router']->generate('latest_news', array('maxPerPage' => 5), true), array('strategy' => 'esi'), ) ?> @@ -938,6 +1062,13 @@ wondering why you would want to use a helper instead of just writing the ESI tag yourself. That's because using a helper makes your application work even if there is no gateway cache installed. +.. tip:: + + As you'll see below, the ``maxPerPage`` variable you pass is available + as an argument to your controller (i.e. ``$maxPerPage``). The variables + passed through ``render_esi`` also become part of the cache key so that + you have unique caches for each combination of variables and values. + When using the default ``render`` function (or setting the renderer to ``inline``), Symfony merges the included page content into the main one before sending the response to the client. But if you use the ``esi`` renderer @@ -958,7 +1089,7 @@ of the master page. .. code-block:: php - public function newsAction($max) + public function newsAction($maxPerPage) { // ... @@ -991,8 +1122,10 @@ that must be enabled in your configuration: + xsi:schemaLocation="http://symfony.com/schema/dic/services + http://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/symfony + http://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> @@ -1027,76 +1160,14 @@ possible. The ``render_esi`` helper supports two other useful options: -* ``alt``: used as the ``alt`` attribute on the ESI tag, which allows you - to specify an alternative URL to be used if the ``src`` cannot be found; - -* ``ignore_errors``: if set to true, an ``onerror`` attribute will be added - to the ESI with a value of ``continue`` indicating that, in the event of - a failure, the gateway cache will simply remove the ESI tag silently. - -.. index:: - single: Cache; Invalidation - -.. _http-cache-invalidation: - -Cache Invalidation ------------------- - - "There are only two hard things in Computer Science: cache invalidation - and naming things." -- Phil Karlton - -You should never need to invalidate cached data because invalidation is already -taken into account natively in the HTTP cache models. If you use validation, -you never need to invalidate anything by definition; and if you use expiration -and need to invalidate a resource, it means that you set the expires date -too far away in the future. - -.. note:: - - Since invalidation is a topic specific to each type of reverse proxy, - if you don't worry about invalidation, you can switch between reverse - proxies without changing anything in your application code. - -Actually, all reverse proxies provide ways to purge cached data, but you -should avoid them as much as possible. The most standard way is to purge the -cache for a given URL by requesting it with the special ``PURGE`` HTTP method. - -Here is how you can configure the Symfony reverse proxy to support the -``PURGE`` HTTP method:: - - // app/AppCache.php +``alt`` + Used as the ``alt`` attribute on the ESI tag, which allows you to specify an + alternative URL to be used if the ``src`` cannot be found. - // ... - use Symfony\Bundle\FrameworkBundle\HttpCache\HttpCache; - use Symfony\Component\HttpFoundation\Request; - use Symfony\Component\HttpFoundation\Response; - - class AppCache extends HttpCache - { - protected function invalidate(Request $request, $catch = false) - { - if ('PURGE' !== $request->getMethod()) { - return parent::invalidate($request, $catch); - } - - $response = new Response(); - if ($this->getStore()->purge($request->getUri())) { - $response->setStatusCode(Response::HTTP_OK, 'Purged'); - } else { - $response->setStatusCode(Response::HTTP_NOT_FOUND, 'Not purged'); - } - - return $response; - } - } - -.. versionadded:: 2.4 - Support for HTTP status code constants was introduced in Symfony 2.4. - -.. caution:: - - You must protect the ``PURGE`` HTTP method somehow to avoid random people - purging your cached data. +``ignore_errors`` + If set to true, an ``onerror`` attribute will be added to the ESI with a value + of ``continue`` indicating that, in the event of a failure, the gateway cache + will simply remove the ESI tag silently. Summary ------- @@ -1123,4 +1194,6 @@ Learn more from the Cookbook .. _`HTTP Bis`: http://tools.ietf.org/wg/httpbis/ .. _`P4 - Conditional Requests`: http://tools.ietf.org/html/draft-ietf-httpbis-p4-conditional .. _`P6 - Caching: Browser and intermediary caches`: http://tools.ietf.org/html/draft-ietf-httpbis-p6-cache +.. _`FrameworkExtraBundle documentation`: http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/cache.html .. _`ESI`: http://www.w3.org/TR/esi-lang +.. _`FOSHttpCacheBundle`: http://foshttpcachebundle.readthedocs.org/ diff --git a/book/http_fundamentals.rst b/book/http_fundamentals.rst index 50a7eadd91f..06532b8fcb2 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,12 +33,12 @@ 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 +Symfony is built from the ground up around that reality. Whether you realize +it or not, HTTP is something you use every day. With Symfony, you'll learn how to master it. .. index:: @@ -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. @@ -186,7 +186,7 @@ PHP? In reality, PHP abstracts you a bit from the whole process:: $uri = $_SERVER['REQUEST_URI']; $foo = $_GET['foo']; - header('Content-type: text/html'); + header('Content-Type: text/html'); echo 'The URI requested is: '.$uri; echo 'The value of the "foo" parameter is: '.$foo; @@ -242,8 +242,8 @@ have all the request information at your fingertips:: $request->headers->get('host'); $request->headers->get('content_type'); - $request->getMethod(); // GET, POST, PUT, DELETE, HEAD - $request->getLanguages(); // an array of languages the client accepts + $request->getMethod(); // GET, POST, PUT, DELETE, HEAD + $request->getLanguages(); // an array of languages the client accepts As a bonus, the ``Request`` class does a lot of work in the background that you'll never need to worry about. For example, the ``isSecure()`` method @@ -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!

'); @@ -286,9 +287,6 @@ interface to construct the response that needs to be returned to the client:: // prints the HTTP headers followed by the content $response->send(); -.. versionadded:: 2.4 - Support for HTTP status code constants was introduced in Symfony 2.4. - If Symfony offered nothing else, you would already have a toolkit for easily accessing request information and an object-oriented interface for creating the response. Even as you learn the many powerful features in Symfony, keep @@ -375,7 +373,7 @@ on that value. This can get ugly quickly:: 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.', Response::HTTP_NOT_FOUND); @@ -431,11 +429,11 @@ 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 - + - AcmeDemoBundle:Main:contact + AppBundle:Main:contact @@ -455,24 +453,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; @@ -495,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 @@ -523,35 +515,34 @@ libraries that can be used inside *any* PHP project. These libraries, called the *Symfony Components*, contain something useful for almost any situation, regardless of how your project is developed. To name a few: -* :doc:`HttpFoundation ` - Contains - the ``Request`` and ``Response`` classes, as well as other classes for handling - sessions and file uploads; - -* :doc:`Routing ` - Powerful and fast routing system that - allows you to map a specific URI (e.g. ``/contact``) to some information - about how that request should be handled (e.g. execute the ``contactAction()`` - method); +:doc:`HttpFoundation ` + Contains the ``Request`` and ``Response`` classes, as well as other classes for + handling sessions and file uploads. -* `Form`_ - A full-featured and flexible framework for creating forms and - handling form submissions; +:doc:`Routing ` + Powerful and fast routing system that allows you to map a specific URI + (e.g. ``/contact``) to some information about how that request should be handled + (e.g. execute the ``contactAction()`` method). -* `Validator`_ - A system for creating rules about data and then validating - whether or not user-submitted data follows those rules; +:doc:`Form ` + A full-featured and flexible framework for creating forms and handling form + submissions. -* :doc:`ClassLoader ` - An autoloading library that allows - PHP classes to be used without needing to manually ``require`` the files - containing those classes; +`Validator`_ + A system for creating rules about data and then validating whether or not + user-submitted data follows those rules. -* :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; +: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; +:doc:`Security ` + A powerful library for handling all types of security inside an application. -* `Translation`_ - A framework for translating strings in your application. +: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* +Each one of these components is decoupled and can be used in *any* PHP project, regardless of whether or not you use the Symfony framework. Every part is made to be used if needed and replaced when necessary. @@ -586,8 +577,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/ diff --git a/book/index.rst b/book/index.rst index 915b0fc7a7f..185f7ccb88f 100644 --- a/book/index.rst +++ b/book/index.rst @@ -22,6 +22,5 @@ The Book service_container performance internals - stable_api .. include:: /book/map.rst.inc diff --git a/book/installation.rst b/book/installation.rst index 6dacc986a41..52b79df7f90 100644 --- a/book/installation.rst +++ b/book/installation.rst @@ -5,212 +5,197 @@ Installing and Configuring Symfony ================================== The goal of this chapter is to get you up and running with a working application -built on top of Symfony. Fortunately, Symfony offers "distributions", which -are functional Symfony "starter" projects that you can download and begin -developing in immediately. +built on top of Symfony. In order to simplify the process of creating new +applications, Symfony provides an installer that must be installed before +creating the first application. -.. tip:: +Installing the Symfony Installer +-------------------------------- - If you're looking for instructions on how best to create a new project - and store it via source control, see `Using Source Control`_. +Using the Symfony Installer is the only recommended way to create new Symfony +applications. This installer is a PHP application that has to be installed +only once and then it can create any number of Symfony applications. -.. _installing-a-symfony2-distribution: +.. note:: -Installing a Symfony Distribution ---------------------------------- + The installer requires PHP 5.4 or higher. If you still use the legacy + PHP 5.3 version, you cannot use the Symfony Installer. Read the + :ref:`book-creating-applications-without-the-installer` section to learn how + to proceed. -.. tip:: +Depending on your operating system, the installer must be installed in different +ways. - First, check that you have installed and configured a Web server (such - as Apache) with PHP. For more information on Symfony requirements, see the - :doc:`requirements reference `. +Linux and Mac OS X Systems +~~~~~~~~~~~~~~~~~~~~~~~~~~ -Symfony packages "distributions", which are fully-functional applications -that include the Symfony core libraries, a selection of useful bundles, a -sensible directory structure and some default configuration. When you download -a Symfony distribution, you're downloading a functional application skeleton -that can be used immediately to begin developing your application. +Open your command console and execute the following three commands: + +.. code-block:: bash -Start by visiting the Symfony download page at `http://symfony.com/download`_. -On this page, you'll see the *Symfony Standard Edition*, which is the main -Symfony distribution. There are 2 ways to get your project started: + $ curl -LsS http://symfony.com/installer > symfony.phar + $ sudo mv symfony.phar /usr/local/bin/symfony + $ chmod a+x /usr/local/bin/symfony -Option 1) Composer -~~~~~~~~~~~~~~~~~~ +This will create a global ``symfony`` command in your system that will be used +to create new Symfony applications. -`Composer`_ is a dependency management library for PHP, which you can use -to download the Symfony Standard Edition. +Windows Systems +~~~~~~~~~~~~~~~ -Start by `downloading Composer`_ anywhere onto your local computer. If you -have curl installed, it's as easy as: +Open your command console and execute the following command: .. code-block:: bash - $ curl -s https://getcomposer.org/installer | php + c:\> php -r "readfile('http://symfony.com/installer');" > symfony.phar -.. note:: +Then, move the downloaded ``symfony.phar`` file to your projects directory and +execute it as follows: + +.. code-block:: bash + + c:\> move symfony.phar c:\projects + c:\projects\> php symfony.phar - If your computer is not ready to use Composer, you'll see some recommendations - when running this command. Follow those recommendations to get Composer - working properly. +Creating the Symfony Application +-------------------------------- -Composer is an executable PHAR file, which you can use to download the Standard -Distribution: +Once the Symfony Installer is ready, create your first Symfony application with +the ``new`` command: .. code-block:: bash - $ php composer.phar create-project symfony/framework-standard-edition /path/to/webroot/Symfony '2.4.*' + # Linux, Mac OS X + $ symfony new my_project_name -.. tip:: + # Windows + c:\> cd projects/ + c:\projects\> php symfony.phar new my_project_name - To download the vendor files faster, add the ``--prefer-dist`` option at - the end of any Composer command. +This command creates a new directory called ``my_project_name`` that contains a +fresh new project based on the most recent stable Symfony version available. In +addition, the installer checks if your system meets the technical requirements +to execute Symfony applications. If not, you'll see the list of changes needed +to meet those requirements. -This command may take several minutes to run as Composer downloads the Standard -Distribution along with all of the vendor libraries that it needs. When it finishes, -you should have a directory that looks something like this: +.. tip:: -.. code-block:: text + For security reasons, all Symfony versions are digitally signed before + distributing them. If you want to verify the integrity of any Symfony + version, follow the steps `explained in this post`_. + +Basing your Project on a Specific Symfony Version +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - path/to/webroot/ <- your web server directory (sometimes named htdocs or public) - Symfony/ <- the new directory - app/ - cache/ - config/ - logs/ - src/ - ... - vendor/ - ... - web/ - app.php - ... - -Option 2) Download an Archive -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -You can also download an archive of the Standard Edition. Here, you'll -need to make two choices: - -* Download either a ``.tgz`` or ``.zip`` archive - both are equivalent, download - whatever you're more comfortable using; - -* Download the distribution with or without vendors. If you're planning on - using more third-party libraries or bundles and managing them via Composer, - you should probably download "without vendors". - -Download one of the archives somewhere under your local web server's root -directory and unpack it. From a UNIX command line, this can be done with -one of the following commands (replacing ``###`` with your actual filename): +If your project needs to be based on a specific Symfony version, pass the version +number as the second argument of the ``new`` command: .. code-block:: bash - # for .tgz file - $ tar zxvf Symfony_Standard_Vendors_2.4.###.tgz + # Linux, Mac OS X + $ symfony new my_project_name 2.3.23 - # for a .zip file - $ unzip Symfony_Standard_Vendors_2.4.###.zip + # Windows + c:\projects\> php symfony.phar new my_project_name 2.3.23 -If you've downloaded "without vendors", you'll definitely need to read the -next section. +Read the :doc:`Symfony Release process ` +to better understand why there are several Symfony versions and which one +to use for your projects. -.. note:: +.. _book-creating-applications-without-the-installer: - You can easily override the default directory structure. See - :doc:`/cookbook/configuration/override_dir_structure` for more - information. +Creating Symfony Applications without the Installer +--------------------------------------------------- -All public files and the front controller that handles incoming requests in -a Symfony application live in the ``Symfony/web/`` directory. So, assuming -you unpacked the archive into your web server's or virtual host's document root, -your application's URLs will start with ``http://localhost/Symfony/web/``. +If you still use PHP 5.3, or if you can't execute the installer for any reason, +you can create Symfony applications using the alternative installation method +based on `Composer`_. -.. note:: +Composer is the dependency manager used by modern PHP applications and it can +also be used to create new applications based on the Symfony framework. If you +don't have installed it globally, start by reading the next section. - The following examples assume you don't touch the document root settings - so all URLs start with ``http://localhost/Symfony/web/`` +Installing Composer Globally +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. _installation-updating-vendors: +Start with :doc:`installing Composer globally `. -Updating Vendors -~~~~~~~~~~~~~~~~ +Creating a Symfony Application with Composer +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -At this point, you've downloaded a fully-functional Symfony project in which -you'll start to develop your own application. A Symfony project depends on -a number of external libraries. These are downloaded into the ``vendor/`` directory -of your project via a library called `Composer`_. +Once Composer is installed on your computer, execute the ``create-project`` +command to create a new Symfony application based on its latest stable version: + +.. code-block:: bash -Depending on how you downloaded Symfony, you may or may not need to update -your vendors right now. But, updating your vendors is always safe, and guarantees -that you have all the vendor libraries you need. + $ composer create-project symfony/framework-standard-edition my_project_name -Step 1: Get `Composer`_ (The great new PHP packaging system) +If you need to base your application on a specific Symfony version, provide that +version as the second argument of the ``create-project`` command: .. code-block:: bash - $ curl -s http://getcomposer.org/installer | php + $ composer create-project symfony/framework-standard-edition my_project_name "2.3.*" -Make sure you download ``composer.phar`` in the same folder where -the ``composer.json`` file is located (this is your Symfony project -root by default). +.. tip:: -Step 2: Install vendors + If your Internet connection is slow, you may think that Composer is not + doing anything. If that's your case, add the ``-vvv`` flag to the previous + command to display a detailed output of everything that Composer is doing. -.. code-block:: bash +Running the Symfony Application +------------------------------- - $ php composer.phar install +Symfony leverages the internal web server provided by PHP to run applications +while developing them. Therefore, running a Symfony application is a matter of +browsing the project directory and executing this command: -This command downloads all of the necessary vendor libraries - including -Symfony itself - into the ``vendor/`` directory. +.. code-block:: bash -.. note:: + $ cd my_project_name/ + $ php app/console server:run - If you don't have ``curl`` installed, you can also just download the ``installer`` - file manually at http://getcomposer.org/installer. Place this file into your - project and then run: +Then, open your browser and access the ``http://localhost:8000`` URL to see the +Welcome page of Symfony: - .. code-block:: bash - - $ php installer - $ php composer.phar install +.. image:: /images/quick_tour/welcome.png + :align: center + :alt: Symfony Welcome Page -.. tip:: +Instead of the Welcome Page, you may see a blank page or an error page. +This is caused by a directory permission misconfiguration. There are several +possible solutions depending on your operating system. All of them are +explained in the :ref:`Setting up Permissions ` +section. - When running ``php composer.phar install`` or ``php composer.phar update``, - Composer will execute post install/update commands to clear the cache - and install assets. By default, the assets will be copied into your ``web`` - directory. +.. note:: - Instead of copying your Symfony assets, you can create symlinks if - your operating system supports it. To create symlinks, add an entry - in the ``extra`` node of your composer.json file with the key - ``symfony-assets-install`` and the value ``symlink``: + PHP's internal web server is available in PHP 5.4 or higher versions. If you + still use the legacy PHP 5.3 version, you'll have to configure a *virtual host* + in your web server. - .. code-block:: json +The ``server:run`` command is only suitable while developing the application. In +order to run Symfony applications on production servers, you'll have to configure +your `Apache`_ or `Nginx`_ web server as explained in +:doc:`/cookbook/configuration/web_server_configuration`. - "extra": { - "symfony-app-dir": "app", - "symfony-web-dir": "web", - "symfony-assets-install": "symlink" - } +When you are finished working on your Symfony application, you can stop the +server with the ``server:stop`` command: - When passing ``relative`` instead of ``symlink`` to symfony-assets-install, - the command will generate relative symlinks. +.. code-block:: bash -Configuration and Setup -~~~~~~~~~~~~~~~~~~~~~~~ + $ php app/console server:stop -At this point, all of the needed third-party libraries now live in the ``vendor/`` -directory. You also have a default application setup in ``app/`` and some -sample code inside the ``src/`` directory. +Checking Symfony Application Configuration and Setup +---------------------------------------------------- -Symfony comes with a visual server configuration tester to help make sure -your Web server and PHP are configured to use Symfony. Use the following URL -to check your configuration: +Symfony applications come with a visual server configuration tester to show if +your environment is ready to use Symfony. Access the following URL to check your +configuration: .. code-block:: text - http://localhost/config.php + http://localhost:8000/config.php If there are any issues, correct them now before moving on. @@ -218,13 +203,21 @@ If there are any issues, correct them now before moving on. .. sidebar:: Setting up Permissions - One common issue is that the ``app/cache`` and ``app/logs`` directories - must be writable both by the web server and the command line user. On - a UNIX system, if your web server user is different from your command - line user, you can run the following commands just once in your project - to ensure that permissions will be setup properly. + One common issue when installing Symfony is that the ``app/cache`` and + ``app/logs`` directories must be writable both by the web server and the + command line user. On a UNIX system, if your web server user is different + from your command line user, you can try one of the following solutions. - **1. Using ACL on a system that supports chmod +a** + **1. Use the same user for the CLI and the web server** + + In development environments, it is a common practice to use the same UNIX + user for the CLI and the web server because it avoids any of these permissions + issues when setting up new projects. This can be done by editing your web server + configuration (e.g. commonly httpd.conf or apache2.conf for Apache) and setting + its user to be the same as your CLI user (e.g. for Apache, update the ``User`` + and ``Group`` values). + + **2. Using ACL on a system that supports chmod +a** Many systems allow you to use the ``chmod +a`` command. Try this first, and if you get an error - try the next method. This uses a command to @@ -240,7 +233,7 @@ If there are any issues, correct them now before moving on. $ sudo chmod +a "`whoami` allow delete,write,append,file_inherit,directory_inherit" app/cache app/logs - **2. Using ACL on a system that does not support chmod +a** + **3. Using ACL on a system that does not support chmod +a** Some systems don't support ``chmod +a``, but do support another utility called ``setfacl``. You may need to `enable ACL support`_ on your partition @@ -250,19 +243,18 @@ If there are any issues, correct them now before moving on. .. code-block:: bash - $ HTTPDUSER=`ps aux | grep -E '[a]pache|[h]ttpd|[_]www|[w]ww-data|[n]ginx' | grep -v root | head -1 | cut -d\ -f1` - $ sudo setfacl -R -m u:"$HTTPDUSER":rwX -m u:`whoami`:rwX app/cache app/logs - $ sudo setfacl -dR -m u:"$HTTPDUSER":rwX -m u:`whoami`:rwX app/cache app/logs + $ HTTPDUSER=`ps aux | grep -E '[a]pache|[h]ttpd|[_]www|[w]ww-data|[n]ginx' | grep -v root | head -1 | cut -d\ -f1` + $ sudo setfacl -R -m u:"$HTTPDUSER":rwX -m u:`whoami`:rwX app/cache app/logs + $ sudo setfacl -dR -m u:"$HTTPDUSER":rwX -m u:`whoami`:rwX app/cache app/logs If this doesn't work, try adding ``-n`` option. - **3. Without using ACL** + **4. Without using ACL** - If you don't have access to changing the ACL of the directories, you will - need to change the umask so that the cache and log directories will - be group-writable or world-writable (depending if the web server user - and the command line user are in the same group or not). To achieve - this, put the following line at the beginning of the ``app/console``, + If none of the previous methods work for you, change the umask so that the + cache and log directories will be group-writable or world-writable (depending + if the web server user and the command line user are in the same group or not). + To achieve this, put the following line at the beginning of the ``app/console``, ``web/app.php`` and ``web/app_dev.php`` files:: umask(0002); // This will let the permissions be 0775 @@ -274,37 +266,95 @@ If there are any issues, correct them now before moving on. Note that using the ACL is recommended when you have access to them on your server because changing the umask is not thread-safe. - **4. Use the same user for the CLI and the web server** +.. _installation-updating-vendors: - In development environments, it is a common practice to use the same unix - user for the CLI and the web server because it avoids any of these permissions - issues when setting up new projects. This can be done by editing your web server - configuration (e.g. commonly httpd.conf or apache2.conf for Apache) and setting - its user to be the same as your CLI user (e.g. for Apache, update the User - and Group values). +Updating Symfony Applications +----------------------------- -When everything is fine, click on "Go to the Welcome page" to request your -first "real" Symfony webpage: +At this point, you've created a fully-functional Symfony application in which +you'll start to develop your own project. A Symfony application depends on +a number of external libraries. These are downloaded into the ``vendor/`` directory +and they are managed exclusively by Composer. -.. code-block:: text +Updating those third-party libraries frequently is a good practice to prevent bugs +and security vulnerabilities. Execute the ``update`` Composer command to update +them all at once: - http://localhost/app_dev.php/ +.. code-block:: bash -Symfony should welcome and congratulate you for your hard work so far! + $ cd my_project_name/ + $ composer update -.. image:: /images/quick_tour/welcome.png +Depending on the complexity of your project, this update process can take up to +several minutes to complete. .. tip:: - To get nice and short urls you should point the document root of your - webserver or virtual host to the ``Symfony/web/`` directory. Though - this is not required for development it is recommended at the time your - application goes into production as all system and configuration files - become inaccessible to clients then. For information on configuring - your specific web server document root, read - :doc:`/cookbook/configuration/web_server_configuration` - or consult the official documentation of your webserver: - `Apache`_ | `Nginx`_ . + Symfony provides a command to check whether your project's dependencies + contain any know security vulnerability: + + .. code-block:: bash + + $ php app/console security:check + + A good security practice is to execute this command regularly to be able to + update or replace compromised dependencies as soon as possible. + +.. _installing-a-symfony2-distribution: + +Installing a Symfony Distribution +--------------------------------- + +Symfony project packages "distributions", which are fully-functional applications +that include the Symfony core libraries, a selection of useful bundles, a +sensible directory structure and some default configuration. In fact, when you +created a Symfony application in the previous sections, you actually downloaded the +default distribution provided by Symfony, which is called *Symfony Standard Edition*. + +The *Symfony Standard Edition* is by far the most popular distribution and it's +also the best choice for developers starting with Symfony. However, the Symfony +Community has published other popular distributions that you may use in your +applications: + +* The `Symfony CMF Standard Edition`_ is the best distribution to get started + with the `Symfony CMF`_ project, which is a project that makes it easier for + developers to add CMS functionality to applications built with the Symfony + framework. +* The `Symfony REST Edition`_ shows how to build an application that provides a + RESTful API using the FOSRestBundle and several other related bundles. + +Using Source Control +-------------------- + +If you're using a version control system like `Git`_, you can safely commit all +your project's code. The reason is that Symfony applications already contain a +``.gitignore`` file specially prepared for Symfony. + +For specific instructions on how best to set up your project to be stored +in Git, see :doc:`/cookbook/workflow/new_project_git`. + +Checking out a versioned Symfony Application +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When using Composer to manage application's dependencies, it's recommended to +ignore the entire ``vendor/`` directory before committing its code to the +repository. This means that when checking out a Symfony application from a Git +repository, there will be no ``vendor/`` directory and the application won't +work out-of-the-box. + +In order to make it work, check out the Symfony application and then execute the +``install`` Composer command to download and install all the dependencies required +by the application: + +.. code-block:: bash + + $ cd my_project_name/ + $ composer install + +How does Composer know which specific dependencies to install? Because when a +Symfony application is committed to a repository, the ``composer.json`` and +``composer.lock`` files are also committed. These files tell Composer which +dependencies (and which specific versions) to install for the application. Beginning Development --------------------- @@ -326,40 +376,14 @@ a wide variety of articles about solving specific problems with Symfony. If you want to remove the sample code from your distribution, take a look at this cookbook article: ":doc:`/cookbook/bundles/remove`" -Using Source Control --------------------- - -If you're using a version control system like ``Git`` or ``Subversion``, you -can setup your version control system and begin committing your project to -it as normal. The Symfony Standard Edition *is* the starting point for your -new project. - -For specific instructions on how best to setup your project to be stored -in Git, see :doc:`/cookbook/workflow/new_project_git`. - -Ignoring the ``vendor/`` Directory -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -If you've downloaded the archive *without vendors*, you can safely ignore -the entire ``vendor/`` directory and not commit it to source control. With -``Git``, this is done by creating and adding the following to a ``.gitignore`` -file: - -.. code-block:: text - - /vendor/ - -Now, the vendor directory won't be committed to source control. This is fine -(actually, it's great!) because when someone else clones or checks out the -project, they can simply run the ``php composer.phar install`` script to -install all the necessary project dependencies. - -.. _`enable ACL support`: https://help.ubuntu.com/community/FilePermissionsACLs -.. _`http://symfony.com/download`: http://symfony.com/download -.. _`Git`: http://git-scm.com/ -.. _`GitHub Bootcamp`: http://help.github.com/set-up-git-redirect +.. _`explained in this post`: http://fabien.potencier.org/article/73/signing-project-releases .. _`Composer`: http://getcomposer.org/ -.. _`downloading Composer`: http://getcomposer.org/download/ +.. _`Composer download page`: https://getcomposer.org/download/ .. _`Apache`: http://httpd.apache.org/docs/current/mod/core.html#documentroot .. _`Nginx`: http://wiki.nginx.org/Symfony -.. _`Symfony Installation Page`: http://symfony.com/download +.. _`enable ACL support`: https://help.ubuntu.com/community/FilePermissionsACLs +.. _`Symfony CMF Standard Edition`: https://github.com/symfony-cmf/symfony-cmf-standard +.. _`Symfony CMF`: http://cmf.symfony.com/ +.. _`Symfony REST Edition`: https://github.com/gimler/symfony-rest-edition +.. _`FOSRestBundle`: https://github.com/FriendsOfSymfony/FOSRestBundle +.. _`Git`: http://git-scm.com/ diff --git a/book/internals.rst b/book/internals.rst index 1bd8db4699f..f0c1a86c2fd 100644 --- a/book/internals.rst +++ b/book/internals.rst @@ -208,26 +208,22 @@ processing must only occur on the master request). Events ~~~~~~ -.. versionadded:: 2.4 - The ``isMasterRequest()`` method was introduced in Symfony 2.4. - Prior, the ``getRequestType()`` method must be used. - Each event thrown by the Kernel is a subclass of :class:`Symfony\\Component\\HttpKernel\\Event\\KernelEvent`. This means that each event has access to the same basic information: -* :method:`Symfony\\Component\\HttpKernel\\Event\\KernelEvent::getRequestType` - - returns the *type* of the request (``HttpKernelInterface::MASTER_REQUEST`` - or ``HttpKernelInterface::SUB_REQUEST``); +:method:`Symfony\\Component\\HttpKernel\\Event\\KernelEvent::getRequestType` + Returns the *type* of the request (``HttpKernelInterface::MASTER_REQUEST`` or + ``HttpKernelInterface::SUB_REQUEST``). -* :method:`Symfony\\Component\\HttpKernel\\Event\\KernelEvent::isMasterRequest` - - checks if it is a master request; +:method:`Symfony\\Component\\HttpKernel\\Event\\KernelEvent::isMasterRequest` + Checks if it is a master request. -* :method:`Symfony\\Component\\HttpKernel\\Event\\KernelEvent::getKernel` - - returns the Kernel handling the request; +:method:`Symfony\\Component\\HttpKernel\\Event\\KernelEvent::getKernel` + Returns the Kernel handling the request. -* :method:`Symfony\\Component\\HttpKernel\\Event\\KernelEvent::getRequest` - - returns the current ``Request`` being handled. +:method:`Symfony\\Component\\HttpKernel\\Event\\KernelEvent::getRequest` + Returns the current ``Request`` being handled. ``isMasterRequest()`` ..................... @@ -354,18 +350,18 @@ The purpose of this event is to allow other systems to modify or replace the The FrameworkBundle registers several listeners: -* :class:`Symfony\\Component\\HttpKernel\\EventListener\\ProfilerListener`: - collects data for the current request; +:class:`Symfony\\Component\\HttpKernel\\EventListener\\ProfilerListener` + Collects data for the current request. -* :class:`Symfony\\Bundle\\WebProfilerBundle\\EventListener\\WebDebugToolbarListener`: - injects the Web Debug Toolbar; +:class:`Symfony\\Bundle\\WebProfilerBundle\\EventListener\\WebDebugToolbarListener` + Injects the Web Debug Toolbar. -* :class:`Symfony\\Component\\HttpKernel\\EventListener\\ResponseListener`: fixes the - Response ``Content-Type`` based on the request format; +:class:`Symfony\\Component\\HttpKernel\\EventListener\\ResponseListener` + Fixes the Response ``Content-Type`` based on the request format. -* :class:`Symfony\\Component\\HttpKernel\\EventListener\\EsiListener`: adds a - ``Surrogate-Control`` HTTP header when the Response needs to be parsed for - ESI tags. +:class:`Symfony\\Component\\HttpKernel\\EventListener\\EsiListener` + Adds a ``Surrogate-Control`` HTTP header when the Response needs to be parsed + for ESI tags. .. seealso:: @@ -379,7 +375,7 @@ The FrameworkBundle registers several listeners: *Event Class*: :class:`Symfony\\Component\\HttpKernel\\Event\\FinishRequestEvent` -The purpose of this event is to to handle tasks that should be performed after +The purpose of this event is to handle tasks that should be performed after the request has been handled but that do not need to modify the response. Event listeners for the ``kernel.finish_request`` event are called in both successful and exception cases. @@ -555,7 +551,8 @@ method to access tokens based on some criteria:: $tokens = $container->get('profiler')->find('127.0.0.1', '', 10, '', ''); // get the latest 10 tokens for requests that happened between 2 and 4 days ago - $tokens = $container->get('profiler')->find('', '', 10, '4 days ago', '2 days ago'); + $tokens = $container->get('profiler') + ->find('', '', 10, '4 days ago', '2 days ago'); If you want to manipulate profiling data on a different machine than the one where the information were generated, use the @@ -599,9 +596,12 @@ the configuration for the development environment: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:webprofiler="http://symfony.com/schema/dic/webprofiler" xmlns:framework="http://symfony.com/schema/dic/symfony" - xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd - http://symfony.com/schema/dic/webprofiler http://symfony.com/schema/dic/webprofiler/webprofiler-1.0.xsd - http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> + xsi:schemaLocation="http://symfony.com/schema/dic/services + http://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/webprofiler + http://symfony.com/schema/dic/webprofiler/webprofiler-1.0.xsd + http://symfony.com/schema/dic/symfony + http://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> @@ -661,7 +661,9 @@ If you enable the web profiler, you also need to mount the profiler routes: use Symfony\Component\Routing\RouteCollection; - $profiler = $loader->import('@WebProfilerBundle/Resources/config/routing/profiler.xml'); + $profiler = $loader->import( + '@WebProfilerBundle/Resources/config/routing/profiler.xml' + ); $profiler->addPrefix('/_profiler'); $collection = new RouteCollection(); diff --git a/book/map.rst.inc b/book/map.rst.inc index 573c8027524..0a1b3381c09 100644 --- a/book/map.rst.inc +++ b/book/map.rst.inc @@ -16,4 +16,3 @@ * :doc:`/book/service_container` * :doc:`/book/performance` * :doc:`/book/internals` -* :doc:`/book/stable_api` diff --git a/book/page_creation.rst b/book/page_creation.rst index f3acbfb120e..3d050051612 100644 --- a/book/page_creation.rst +++ b/book/page_creation.rst @@ -93,7 +93,7 @@ Before you begin: Create the Bundle ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Before you begin, you'll need to create a *bundle*. In Symfony, a :term:`bundle` -is like a plugin, except that all of the code in your application will live +is like a plugin, except that all the code in your application will live inside a bundle. A bundle is nothing more than a directory that houses everything related @@ -101,14 +101,14 @@ to a specific feature, including PHP classes, configuration, and even stylesheet and JavaScript files (see :ref:`page-creation-bundles`). Depending on the way you installed Symfony, you may already have a bundle called -``AcmeDemoBundle``. Browse the ``src/`` directory of your project and check +AcmeDemoBundle. Browse the ``src/`` directory of your project and check if there is a ``DemoBundle/`` directory inside an ``Acme/`` directory. If those directories already exist, skip the rest of this section and go directly to create the route. -To create a bundle called ``AcmeDemoBundle`` (a play bundle that you'll +To create a bundle called AcmeDemoBundle (a play bundle that you'll build in this chapter), run the following command and follow the on-screen -instructions (use all of the default options): +instructions (use all the default options): .. code-block:: bash @@ -122,7 +122,7 @@ the bundle is registered with the kernel:: public function registerBundles() { $bundles = array( - ..., + // ... new Acme\DemoBundle\AcmeDemoBundle(), ); // ... @@ -140,8 +140,8 @@ By default, the routing configuration file in a Symfony application is located at ``app/config/routing.yml``. Like all configuration in Symfony, you can also choose to use XML or PHP out of the box to configure routes. -If you look at the main routing file, you'll see that Symfony already added -an entry when you generated the ``AcmeDemoBundle``: +If you look at the main routing file, you'll see that Symfony already added an +entry when you generated the AcmeDemoBundle: .. configuration-block:: @@ -181,9 +181,10 @@ an entry when you generated the ``AcmeDemoBundle``: This entry is pretty basic: it tells Symfony to load routing configuration from the ``Resources/config/routing.yml`` (``routing.xml`` or ``routing.php`` -in the XML and PHP code example respectively) file that lives inside the ``AcmeDemoBundle``. -This means that you place routing configuration directly in ``app/config/routing.yml`` -or organize your routes throughout your application, and import them from here. +in the XML and PHP code example respectively) file that lives inside the +AcmeDemoBundle. This means that you place routing configuration directly in +``app/config/routing.yml`` or organize your routes throughout your application, +and import them from here. .. note:: @@ -255,7 +256,7 @@ that controller. The controller - ``AcmeDemoBundle:Random:index`` is the *logical* name of the controller, and it maps to the ``indexAction`` method of a PHP class called ``Acme\DemoBundle\Controller\RandomController``. Start by creating this -file inside your ``AcmeDemoBundle``:: +file inside your AcmeDemoBundle:: // src/Acme/DemoBundle/Controller/RandomController.php namespace Acme\DemoBundle\Controller; @@ -282,7 +283,9 @@ route is matched:: { public function indexAction($limit) { - return new Response('Number: '.rand(1, $limit).''); + return new Response( + 'Number: '.rand(1, $limit).'' + ); } } @@ -327,7 +330,7 @@ An optional, but common, third step in the process is to create a template. Optional Step 3: Create the Template ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Templates allow you to move all of the presentation (e.g. HTML code) into +Templates allow you to move all the presentation code (e.g. HTML) into a separate file and reuse different portions of the page layout. Instead of writing the HTML inside the controller, render a template instead: @@ -386,7 +389,7 @@ location using the following convention. **/path/to/BundleName**/Resources/views/**ControllerName**/**TemplateName** -In this case, ``AcmeDemoBundle`` is the bundle name, ``Random`` is the +In this case, AcmeDemoBundle is the bundle name, ``Random`` is the controller, and ``index.html.twig`` the template: .. configuration-block:: @@ -420,7 +423,7 @@ Step through the Twig template line-by-line: The parent template, ``::base.html.twig``, is missing both the **BundleName** and **ControllerName** portions of its name (hence the double colon (``::``) -at the beginning). This means that the template lives outside of the bundles +at the beginning). This means that the template lives outside of the bundle and in the ``app`` directory: .. configuration-block:: @@ -451,7 +454,8 @@ and in the ``app`` directory: <?php $view['slots']->output('title', 'Welcome!') ?> output('stylesheets') ?> - + output('_content') ?> @@ -488,13 +492,23 @@ you'll know where to find and put different types of files and why. Though entirely flexible, by default, each Symfony :term:`application` has the same basic and recommended directory structure: -* ``app/``: This directory contains the application configuration; +``app/`` + This directory contains the application configuration. + +``src/`` + All the project PHP code is stored under this directory. -* ``src/``: All the project PHP code is stored under this directory; +``vendor/`` + Any vendor libraries are placed here by convention. -* ``vendor/``: Any vendor libraries are placed here by convention; +``web/`` + This is the web root directory and contains any publicly accessible files. -* ``web/``: This is the web root directory and contains any publicly accessible files; +.. seealso:: + + You can easily override the default directory structure. See + :doc:`/cookbook/configuration/override_dir_structure` for more + information. .. _the-web-directory: @@ -554,11 +568,13 @@ needs to know about your application. You don't even need to worry about these methods when starting - Symfony fills them in for you with sensible defaults. -* ``registerBundles()``: Returns an array of all bundles needed to run the - application (see :ref:`page-creation-bundles`); +``registerBundles()`` + Returns an array of all bundles needed to run the application (see + :ref:`page-creation-bundles`). -* ``registerContainerConfiguration()``: Loads the main application configuration - resource file (see the `Application Configuration`_ section). +``registerContainerConfiguration()`` + Loads the main application configuration resource file (see the + `Application Configuration`_ section). In day-to-day development, you'll mostly use the ``app/`` directory to modify configuration and routing files in the ``app/config/`` directory (see @@ -595,7 +611,7 @@ You'll learn more about each of these directories in later chapters. The Source (``src``) Directory ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Put simply, the ``src/`` directory contains all of the actual code (PHP code, +Put simply, the ``src/`` directory contains all the actual code (PHP code, templates, configuration files, stylesheets, etc) that drives *your* application. When developing, the vast majority of your work will be done inside one or more bundles that you create in this directory. @@ -621,7 +637,7 @@ in your application and to optimize them the way you want. to the organization and best practices of :doc:`bundles `. A bundle is simply a structured set of files within a directory that implement -a single feature. You might create a ``BlogBundle``, a ``ForumBundle`` or +a single feature. You might create a BlogBundle, a ForumBundle or a bundle for user management (many of these exist already as open source bundles). Each directory contains everything related to that feature, including PHP files, templates, stylesheets, JavaScripts, tests and anything else. @@ -670,13 +686,13 @@ The Symfony Standard Edition comes with a handy task that creates a fully-functi bundle for you. Of course, creating a bundle by hand is pretty easy as well. To show you how simple the bundle system is, create a new bundle called -``AcmeTestBundle`` and enable it. +AcmeTestBundle and enable it. .. tip:: The ``Acme`` portion is just a dummy name that should be replaced by - some "vendor" name that represents you or your organization (e.g. ``ABCTestBundle`` - for some company named ``ABC``). + some "vendor" name that represents you or your organization (e.g. + ABCTestBundle for some company named ``ABC``). Start by creating a ``src/Acme/TestBundle/`` directory and adding a new file called ``AcmeTestBundle.php``:: @@ -692,9 +708,10 @@ called ``AcmeTestBundle.php``:: .. tip:: - The name ``AcmeTestBundle`` follows the standard :ref:`Bundle naming conventions `. - You could also choose to shorten the name of the bundle to simply ``TestBundle`` - by naming this class ``TestBundle`` (and naming the file ``TestBundle.php``). + The name AcmeTestBundle follows the standard + :ref:`Bundle naming conventions `. You could + also choose to shorten the name of the bundle to simply TestBundle by naming + this class TestBundle (and naming the file ``TestBundle.php``). This empty class is the only piece you need to create the new bundle. Though commonly empty, this class is powerful and can be used to customize the behavior @@ -706,8 +723,8 @@ Now that you've created the bundle, enable it via the ``AppKernel`` class:: public function registerBundles() { $bundles = array( - ..., - // register your bundles + // ... + // register your bundle new Acme\TestBundle\AcmeTestBundle(), ); // ... @@ -715,8 +732,7 @@ Now that you've created the bundle, enable it via the ``AppKernel`` class:: return $bundles; } -And while it doesn't do anything yet, ``AcmeTestBundle`` is now ready to -be used. +And while it doesn't do anything yet, AcmeTestBundle is now ready to be used. And as easy as this is, Symfony also provides a command-line interface for generating a basic bundle skeleton: @@ -740,26 +756,30 @@ Bundle Directory Structure The directory structure of a bundle is simple and flexible. By default, the bundle system follows a set of conventions that help to keep code consistent -between all Symfony bundles. Take a look at ``AcmeDemoBundle``, as it contains -some of the most common elements of a bundle: +between all Symfony bundles. Take a look at AcmeDemoBundle, as it contains some +of the most common elements of a bundle: -* ``Controller/`` contains the controllers of the bundle (e.g. ``RandomController.php``); +``Controller/`` + Contains the controllers of the bundle (e.g. ``RandomController.php``). -* ``DependencyInjection/`` holds certain dependency injection extension classes, - which may import service configuration, register compiler passes or more - (this directory is not necessary); +``DependencyInjection/`` + Holds certain dependency injection extension classes, which may import service + configuration, register compiler passes or more (this directory is not + necessary). -* ``Resources/config/`` houses configuration, including routing configuration - (e.g. ``routing.yml``); +``Resources/config/`` + Houses configuration, including routing configuration (e.g. ``routing.yml``). -* ``Resources/views/`` holds templates organized by controller name (e.g. - ``Hello/index.html.twig``); +``Resources/views/`` + Holds templates organized by controller name (e.g. ``Hello/index.html.twig``). -* ``Resources/public/`` contains web assets (images, stylesheets, etc) and is - copied or symbolically linked into the project ``web/`` directory via - the ``assets:install`` console command; +``Resources/public/`` + Contains web assets (images, stylesheets, etc) and is copied or symbolically + linked into the project ``web/`` directory via the ``assets:install`` console + command. -* ``Tests/`` holds all tests for the bundle. +``Tests/`` + Holds all tests for the bundle. A bundle can be as small or large as the feature it implements. It contains only the files you need and nothing else. @@ -772,7 +792,7 @@ bundle. Application Configuration ------------------------- -An application consists of a collection of bundles representing all of the +An application consists of a collection of bundles representing all the features and capabilities of your application. Each bundle can be customized via configuration files written in YAML, XML or PHP. By default, the main configuration file lives in the ``app/config/`` directory and is called @@ -808,9 +828,12 @@ format you prefer: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:framework="http://symfony.com/schema/dic/symfony" xmlns:twig="http://symfony.com/schema/dic/twig" - xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd - http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd - http://symfony.com/schema/dic/twig http://symfony.com/schema/dic/twig/twig-1.0.xsd"> + xsi:schemaLocation="http://symfony.com/schema/dic/services + http://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/symfony + http://symfony.com/schema/dic/symfony/symfony-1.0.xsd + http://symfony.com/schema/dic/twig + http://symfony.com/schema/dic/twig/twig-1.0.xsd"> @@ -897,9 +920,8 @@ The extension alias (configuration key) can also be used: .. note:: - See the cookbook article: - :doc:`How to expose a Semantic Configuration for a Bundle ` - for information on adding configuration for your own bundle. + See the cookbook article: :doc:`/cookbook/bundles/extension` for + information on adding configuration for your own bundle. .. index:: single: Environments; Introduction @@ -1002,8 +1024,10 @@ the configuration file for the ``dev`` environment. + xsi:schemaLocation="http://symfony.com/schema/dic/services + http://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/symfony + http://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> @@ -1023,7 +1047,7 @@ the configuration file for the ``dev`` environment. $loader->import('config.php'); $container->loadFromExtension('framework', array( - 'router' => array( + 'router' => array( 'resource' => '%kernel.root_dir%/config/routing_dev.php', ), 'profiler' => array('only-exceptions' => false), diff --git a/book/performance.rst b/book/performance.rst index b2e21a42610..e3f296484ad 100644 --- a/book/performance.rst +++ b/book/performance.rst @@ -60,7 +60,7 @@ command line, and might become part of your deploy process: .. code-block:: bash - $ php composer.phar dump-autoload --optimize + $ composer dump-autoload --optimize Internally, this builds the big class map array in ``vendor/composer/autoload_classmap.php``. @@ -128,8 +128,7 @@ Note that there are two disadvantages when using a bootstrap file: * when debugging, one will need to place break points inside the bootstrap file. If you're using the Symfony Standard Edition, the bootstrap file is automatically -rebuilt after updating the vendor libraries via the ``php composer.phar install`` -command. +rebuilt after updating the vendor libraries via the ``composer install`` command. Bootstrap Files and Byte Code Caches ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/book/propel.rst b/book/propel.rst index 79377a03526..9ff589afb04 100644 --- a/book/propel.rst +++ b/book/propel.rst @@ -18,7 +18,7 @@ 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: + AcmeStoreBundle via: .. code-block:: bash @@ -86,7 +86,7 @@ generated by Propel contain some business logic. Suppose you're building an application where products need to be displayed. First, create a ``schema.xml`` file inside the ``Resources/config`` directory -of your ``AcmeStoreBundle``: +of your AcmeStoreBundle: .. code-block:: xml @@ -129,7 +129,7 @@ After creating your ``schema.xml``, generate your model from it by running: $ php app/console propel:model:build This generates each model class to quickly develop your application in the -``Model/`` directory of the ``AcmeStoreBundle`` bundle. +``Model/`` directory of the AcmeStoreBundle bundle. Creating the Database Tables/Schema ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -467,14 +467,22 @@ To add a hook, just add a new method to the object class:: Propel provides the following hooks: -* ``preInsert()`` code executed before insertion of a new object -* ``postInsert()`` code executed after insertion of a new object -* ``preUpdate()`` code executed before update of an existing object -* ``postUpdate()`` code executed after update of an existing object -* ``preSave()`` code executed before saving an object (new or existing) -* ``postSave()`` code executed after saving an object (new or existing) -* ``preDelete()`` code executed before deleting an object -* ``postDelete()`` code executed after deleting an object +``preInsert()`` + Code executed before insertion of a new object. +``postInsert()`` + Code executed after insertion of a new object. +``preUpdate()`` + Code executed before update of an existing object. +``postUpdate()`` + Code executed after update of an existing object. +``preSave()`` + Code executed before saving an object (new or existing). +``postSave()`` + Code executed after saving an object (new or existing). +``preDelete()`` + Code executed before deleting an object. +``postDelete()`` + Code executed after deleting an object. Behaviors --------- diff --git a/book/routing.rst b/book/routing.rst index d8846f49633..e7cd6c8e5ba 100644 --- a/book/routing.rst +++ b/book/routing.rst @@ -34,12 +34,31 @@ The route is simple: .. configuration-block:: + .. code-block:: php-annotations + + // src/AppBundle/Controller/BlogController.php + namespace AppBundle\Controller; + + use Symfony\Bundle\FrameworkBundle\Controller\Controller; + use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; + + class BlogController extends Controller + { + /** + * @Route("/blog/{slug}") + */ + public function showAction($slug) + { + // ... + } + } + .. code-block:: yaml # app/config/routing.yml blog_show: path: /blog/{slug} - defaults: { _controller: AcmeBlogBundle:Blog:show } + defaults: { _controller: AppBundle:Blog:show } .. code-block:: xml @@ -51,7 +70,7 @@ The route is simple: http://symfony.com/schema/routing/routing-1.0.xsd"> - AcmeBlogBundle:Blog:show + AppBundle:Blog:show @@ -63,15 +82,11 @@ The route is simple: $collection = new RouteCollection(); $collection->add('blog_show', new Route('/blog/{slug}', array( - '_controller' => 'AcmeBlogBundle:Blog:show', + '_controller' => 'AppBundle:Blog:show', ))); return $collection; -.. versionadded:: 2.2 - The ``path`` option was introduced in Symfony 2.2, ``pattern`` is used - in older versions. - The path defined by the ``blog_show`` route acts like ``/blog/*`` where the wildcard is given the name ``slug``. For the URL ``/blog/my-blog-post``, the ``slug`` variable gets a value of ``my-blog-post``, which is available @@ -79,28 +94,14 @@ for you to use in your controller (keep reading). The ``blog_show`` is the internal name of the route, which doesn't have any meaning yet and just needs to be unique. Later, you'll use it to generate URLs. -The ``_controller`` parameter is a special key that tells Symfony which controller -should be executed when a URL matches this route. The ``_controller`` string -is called the :ref:`logical name `. It follows a -pattern that points to a specific PHP class and method:: - - // src/Acme/BlogBundle/Controller/BlogController.php - namespace Acme\BlogBundle\Controller; - - use Symfony\Bundle\FrameworkBundle\Controller\Controller; - - class BlogController extends Controller - { - public function showAction($slug) - { - // use the $slug variable to query the database - $blog = ...; - - return $this->render('AcmeBlogBundle:Blog:show.html.twig', array( - 'blog' => $blog, - )); - } - } +If you don't want to use annotations, because you don't like them or because +you don't want to depend on the SensioFrameworkExtraBundle, you can also use +Yaml, XML or PHP. In these formats, the ``_controller`` parameter is a special +key that tells Symfony which controller should be executed when a URL matches +this route. The ``_controller`` string is called the +:ref:`logical name `. It follows a pattern that +points to a specific PHP class and method, in this case the +``AppBundle\Controller\BlogController::showAction`` method. Congratulations! You've just created your first route and connected it to a controller. Now, when you visit ``/blog/my-post``, the ``showAction`` controller @@ -163,7 +164,7 @@ file: # app/config/config.yml framework: # ... - router: { resource: "%kernel.root_dir%/config/routing.yml" } + router: { resource: "%kernel.root_dir%/config/routing.yml" } .. code-block:: xml @@ -172,8 +173,10 @@ file: + xsi:schemaLocation="http://symfony.com/schema/dic/services + http://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/symfony + http://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> @@ -208,12 +211,28 @@ A basic route consists of just two parts: the ``path`` to match and a .. configuration-block:: + .. code-block:: php-annotations + + // src/AppBundle/Controller/MainController.php + + // ... + class MainController extends Controller + { + /** + * @Route("/") + */ + public function homepageAction() + { + // ... + } + } + .. code-block:: yaml # app/config/routing.yml _welcome: path: / - defaults: { _controller: AcmeDemoBundle:Main:homepage } + defaults: { _controller: AppBundle:Main:homepage } .. code-block:: xml @@ -225,7 +244,7 @@ A basic route consists of just two parts: the ``path`` to match and a http://symfony.com/schema/routing/routing-1.0.xsd"> - AcmeDemoBundle:Main:homepage + AppBundle:Main:homepage @@ -238,15 +257,15 @@ A basic route consists of just two parts: the ``path`` to match and a $collection = new RouteCollection(); $collection->add('_welcome', new Route('/', array( - '_controller' => 'AcmeDemoBundle:Main:homepage', + '_controller' => 'AppBundle:Main:homepage', ))); return $collection; -This route matches the homepage (``/``) and maps it to the ``AcmeDemoBundle:Main:homepage`` -controller. The ``_controller`` string is translated by Symfony into an -actual PHP function and executed. That process will be explained shortly -in the :ref:`controller-string-syntax` section. +This route matches the homepage (``/``) and maps it to the +``AppBundle:Main:homepage`` controller. The ``_controller`` string is +translated by Symfony into an actual PHP function and executed. That process +will be explained shortly in the :ref:`controller-string-syntax` section. .. index:: single: Routing; Placeholders @@ -259,12 +278,28 @@ routes will contain one or more named "wildcard" placeholders: .. configuration-block:: + .. code-block:: php-annotations + + // src/AppBundle/Controller/BlogController.php + + // ... + class BlogController extends Controller + { + /** + * @Route("/blog/{slug}") + */ + public function showAction($slug) + { + // ... + } + } + .. code-block:: yaml # app/config/routing.yml blog_show: path: /blog/{slug} - defaults: { _controller: AcmeBlogBundle:Blog:show } + defaults: { _controller: AppBundle:Blog:show } .. code-block:: xml @@ -276,7 +311,7 @@ routes will contain one or more named "wildcard" placeholders: http://symfony.com/schema/routing/routing-1.0.xsd"> - AcmeBlogBundle:Blog:show + AppBundle:Blog:show @@ -288,7 +323,7 @@ routes will contain one or more named "wildcard" placeholders: $collection = new RouteCollection(); $collection->add('blog_show', new Route('/blog/{slug}', array( - '_controller' => 'AcmeBlogBundle:Blog:show', + '_controller' => 'AppBundle:Blog:show', ))); return $collection; @@ -311,12 +346,30 @@ the available blog posts for this imaginary blog application: .. configuration-block:: + .. code-block:: php-annotations + + // src/AppBundle/Controller/BlogController.php + + // ... + class BlogController extends Controller + { + // ... + + /** + * @Route("/blog") + */ + public function indexAction() + { + // ... + } + } + .. code-block:: yaml # app/config/routing.yml blog: path: /blog - defaults: { _controller: AcmeBlogBundle:Blog:index } + defaults: { _controller: AppBundle:Blog:index } .. code-block:: xml @@ -328,7 +381,7 @@ the available blog posts for this imaginary blog application: http://symfony.com/schema/routing/routing-1.0.xsd"> - AcmeBlogBundle:Blog:index + AppBundle:Blog:index @@ -340,7 +393,7 @@ the available blog posts for this imaginary blog application: $collection = new RouteCollection(); $collection->add('blog', new Route('/blog', array( - '_controller' => 'AcmeBlogBundle:Blog:index', + '_controller' => 'AppBundle:Blog:index', ))); return $collection; @@ -352,12 +405,26 @@ entries? Update the route to have a new ``{page}`` placeholder: .. configuration-block:: + .. code-block:: php-annotations + + // src/AppBundle/Controller/BlogController.php + + // ... + + /** + * @Route("/blog/{page}") + */ + public function indexAction($page) + { + // ... + } + .. code-block:: yaml # app/config/routing.yml blog: path: /blog/{page} - defaults: { _controller: AcmeBlogBundle:Blog:index } + defaults: { _controller: AppBundle:Blog:index } .. code-block:: xml @@ -369,7 +436,7 @@ entries? Update the route to have a new ``{page}`` placeholder: http://symfony.com/schema/routing/routing-1.0.xsd"> - AcmeBlogBundle:Blog:index + AppBundle:Blog:index @@ -381,7 +448,7 @@ entries? Update the route to have a new ``{page}`` placeholder: $collection = new RouteCollection(); $collection->add('blog', new Route('/blog/{page}', array( - '_controller' => 'AcmeBlogBundle:Blog:index', + '_controller' => 'AppBundle:Blog:index', ))); return $collection; @@ -398,12 +465,26 @@ This is done by including it in the ``defaults`` collection: .. configuration-block:: + .. code-block:: php-annotations + + // src/AppBundle/Controller/BlogController.php + + // ... + + /** + * @Route("/blog/{page}", defaults={"page" = 1}) + */ + public function indexAction($page) + { + // ... + } + .. code-block:: yaml # app/config/routing.yml blog: path: /blog/{page} - defaults: { _controller: AcmeBlogBundle:Blog:index, page: 1 } + defaults: { _controller: AppBundle:Blog:index, page: 1 } .. code-block:: xml @@ -415,7 +496,7 @@ This is done by including it in the ``defaults`` collection: http://symfony.com/schema/routing/routing-1.0.xsd"> - AcmeBlogBundle:Blog:index + AppBundle:Blog:index 1 @@ -428,7 +509,7 @@ This is done by including it in the ``defaults`` collection: $collection = new RouteCollection(); $collection->add('blog', new Route('/blog/{page}', array( - '_controller' => 'AcmeBlogBundle:Blog:index', + '_controller' => 'AppBundle:Blog:index', 'page' => 1, ))); @@ -439,22 +520,20 @@ longer required. The URL ``/blog`` will match this route and the value of the ``page`` parameter will be set to ``1``. The URL ``/blog/2`` will also match, giving the ``page`` parameter a value of ``2``. Perfect. -+--------------------+-------+-----------------------+ -| URL | route | parameters | -+====================+=======+=======================+ -| /blog | blog | {page} = 1 | -+--------------------+-------+-----------------------+ -| /blog/1 | blog | {page} = 1 | -+--------------------+-------+-----------------------+ -| /blog/2 | blog | {page} = 2 | -+--------------------+-------+-----------------------+ +=========== ======== ================== +URL Route Parameters +=========== ======== ================== +``/blog`` ``blog`` ``{page}`` = ``1`` +``/blog/1`` ``blog`` ``{page}`` = ``1`` +``/blog/2`` ``blog`` ``{page}`` = ``2`` +=========== ======== ================== .. caution:: - Of course, you can have more than one optional placeholder (e.g. ``/blog/{slug}/{page}``), - but everything after an optional placeholder must be optional. For example, - ``/{page}/blog`` is a valid path, but ``page`` will always be required - (i.e. simply ``/blog`` will not match this route). + Of course, you can have more than one optional placeholder (e.g. + ``/blog/{slug}/{page}``), but everything after an optional placeholder must + be optional. For example, ``/{page}/blog`` is a valid path, but ``page`` + will always be required (i.e. simply ``/blog`` will not match this route). .. tip:: @@ -471,16 +550,40 @@ Take a quick look at the routes that have been created so far: .. configuration-block:: + .. code-block:: php-annotations + + // src/AppBundle/Controller/BlogController.php + + // ... + class BlogController extends Controller + { + /** + * @Route("/blog/{page}", defaults={"page" = 1}) + */ + public function indexAction($page) + { + // ... + } + + /** + * @Route("/blog/{slug}") + */ + public function showAction($slug) + { + // ... + } + } + .. code-block:: yaml # app/config/routing.yml blog: path: /blog/{page} - defaults: { _controller: AcmeBlogBundle:Blog:index, page: 1 } + defaults: { _controller: AppBundle:Blog:index, page: 1 } blog_show: path: /blog/{slug} - defaults: { _controller: AcmeBlogBundle:Blog:show } + defaults: { _controller: AppBundle:Blog:show } .. code-block:: xml @@ -492,12 +595,12 @@ Take a quick look at the routes that have been created so far: http://symfony.com/schema/routing/routing-1.0.xsd"> - AcmeBlogBundle:Blog:index + AppBundle:Blog:index 1 - AcmeBlogBundle:Blog:show + AppBundle:Blog:show @@ -509,12 +612,12 @@ Take a quick look at the routes that have been created so far: $collection = new RouteCollection(); $collection->add('blog', new Route('/blog/{page}', array( - '_controller' => 'AcmeBlogBundle:Blog:index', + '_controller' => 'AppBundle:Blog:index', 'page' => 1, ))); $collection->add('blog_show', new Route('/blog/{show}', array( - '_controller' => 'AcmeBlogBundle:Blog:show', + '_controller' => 'AppBundle:Blog:show', ))); return $collection; @@ -526,13 +629,12 @@ will *never* be matched. Instead, a URL like ``/blog/my-blog-post`` will match the first route (``blog``) and return a nonsense value of ``my-blog-post`` to the ``{page}`` parameter. -+--------------------+-------+-----------------------+ -| URL | route | parameters | -+====================+=======+=======================+ -| /blog/2 | blog | {page} = 2 | -+--------------------+-------+-----------------------+ -| /blog/my-blog-post | blog | {page} = my-blog-post | -+--------------------+-------+-----------------------+ +====================== ======== =============================== +URL Route Parameters +====================== ======== =============================== +``/blog/2`` ``blog`` ``{page}`` = ``2`` +``/blog/my-blog-post`` ``blog`` ``{page}`` = ``"my-blog-post"`` +====================== ======== =============================== The answer to the problem is to add route *requirements* or route *conditions* (see :ref:`book-routing-conditions`). The routes in this example would work @@ -542,12 +644,28 @@ be added for each parameter. For example: .. configuration-block:: + .. code-block:: php-annotations + + // src/AppBundle/Controller/BlogController.php + + // ... + + /** + * @Route("/blog/{page}", defaults={"page": 1}, requirements={ + * "page": "\d+" + * }) + */ + public function indexAction($page) + { + // ... + } + .. code-block:: yaml # app/config/routing.yml blog: path: /blog/{page} - defaults: { _controller: AcmeBlogBundle:Blog:index, page: 1 } + defaults: { _controller: AppBundle:Blog:index, page: 1 } requirements: page: \d+ @@ -561,7 +679,7 @@ be added for each parameter. For example: http://symfony.com/schema/routing/routing-1.0.xsd"> - AcmeBlogBundle:Blog:index + AppBundle:Blog:index 1 \d+ @@ -575,7 +693,7 @@ be added for each parameter. For example: $collection = new RouteCollection(); $collection->add('blog', new Route('/blog/{page}', array( - '_controller' => 'AcmeBlogBundle:Blog:index', + '_controller' => 'AppBundle:Blog:index', 'page' => 1, ), array( 'page' => '\d+', @@ -592,15 +710,13 @@ is *not* a number). As a result, a URL like ``/blog/my-blog-post`` will now properly match the ``blog_show`` route. -+----------------------+-----------+-------------------------+ -| URL | route | parameters | -+======================+===========+=========================+ -| /blog/2 | blog | {page} = 2 | -+----------------------+-----------+-------------------------+ -| /blog/my-blog-post | blog_show | {slug} = my-blog-post | -+----------------------+-----------+-------------------------+ -| /blog/2-my-blog-post | blog_show | {slug} = 2-my-blog-post | -+----------------------+-----------+-------------------------+ +======================== ============= =============================== +URL Route Parameters +======================== ============= =============================== +``/blog/2`` ``blog`` ``{page}`` = ``2`` +``/blog/my-blog-post`` ``blog_show`` ``{slug}`` = ``my-blog-post`` +``/blog/2-my-blog-post`` ``blog_show`` ``{slug}`` = ``2-my-blog-post`` +======================== ============= =============================== .. sidebar:: Earlier Routes always Win @@ -617,14 +733,31 @@ URL: .. configuration-block:: + .. code-block:: php-annotations + + // src/AppBundle/Controller/MainController.php + + // ... + class MainController extends Controller + { + /** + * @Route("/{_locale}", defaults={"_locale": "en"}, requirements={ + * "_locale": "en|fr" + * }) + */ + public function homepageAction($_locale) + { + } + } + .. code-block:: yaml # app/config/routing.yml homepage: - path: /{culture} - defaults: { _controller: AcmeDemoBundle:Main:homepage, culture: en } + path: /{_locale} + defaults: { _controller: AppBundle:Main:homepage, _locale: en } requirements: - culture: en|fr + _locale: en|fr .. code-block:: xml @@ -635,10 +768,10 @@ URL: xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd"> - - AcmeDemoBundle:Main:homepage - en - en|fr + + AppBundle:Main:homepage + en + en|fr @@ -649,27 +782,26 @@ URL: use Symfony\Component\Routing\Route; $collection = new RouteCollection(); - $collection->add('homepage', new Route('/{culture}', array( - '_controller' => 'AcmeDemoBundle:Main:homepage', - 'culture' => 'en', + $collection->add('homepage', new Route('/{_locale}', array( + '_controller' => 'AppBundle:Main:homepage', + '_locale' => 'en', ), array( - 'culture' => 'en|fr', + '_locale' => 'en|fr', ))); return $collection; -For incoming requests, the ``{culture}`` portion of the URL is matched against +For incoming requests, the ``{_locale}`` portion of the URL is matched against the regular expression ``(en|fr)``. -+-----+--------------------------+ -| / | {culture} = en | -+-----+--------------------------+ -| /en | {culture} = en | -+-----+--------------------------+ -| /fr | {culture} = fr | -+-----+--------------------------+ -| /es | *won't match this route* | -+-----+--------------------------+ +======= ======================== +Path Parameters +======= ======================== +``/`` ``{_locale}`` = ``"en"`` +``/en`` ``{_locale}`` = ``"en"`` +``/fr`` ``{_locale}`` = ``"fr"`` +``/es`` *won't match this route* +======= ======================== .. index:: single: Routing; Method requirement @@ -685,17 +817,46 @@ be accomplished with the following route configuration: .. configuration-block:: + .. code-block:: php-annotations + + // src/AppBundle/Controller/MainController.php + namespace AppBundle\Controller; + + use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method; + // ... + + class MainController extends Controller + { + /** + * @Route("/contact") + * @Method("GET") + */ + public function contactAction() + { + // ... display contact form + } + + /** + * @Route("/contact") + * @Method("POST") + */ + public function processContactAction() + { + // ... process contact form + } + } + .. code-block:: yaml # app/config/routing.yml contact: path: /contact - defaults: { _controller: AcmeDemoBundle:Main:contact } + defaults: { _controller: AppBundle:Main:contact } methods: [GET] contact_process: path: /contact - defaults: { _controller: AcmeDemoBundle:Main:contactProcess } + defaults: { _controller: AppBundle:Main:processContact } methods: [POST] .. code-block:: xml @@ -708,11 +869,11 @@ be accomplished with the following route configuration: http://symfony.com/schema/routing/routing-1.0.xsd"> - AcmeDemoBundle:Main:contact + AppBundle:Main:contact - AcmeDemoBundle:Main:contactProcess + AppBundle:Main:processContact @@ -724,19 +885,15 @@ be accomplished with the following route configuration: $collection = new RouteCollection(); $collection->add('contact', new Route('/contact', array( - '_controller' => 'AcmeDemoBundle:Main:contact', + '_controller' => 'AppBundle:Main:contact', ), array(), array(), '', array(), array('GET'))); $collection->add('contact_process', new Route('/contact', array( - '_controller' => 'AcmeDemoBundle:Main:contactProcess', + '_controller' => 'AppBundle:Main:processContact', ), array(), array(), '', array(), array('POST'))); return $collection; -.. versionadded:: 2.2 - The ``methods`` option was introduced in Symfony 2.2. Use the ``_method`` - requirement in older versions. - Despite the fact that these two routes have identical paths (``/contact``), the first route will match only GET requests and the second route will match only POST requests. This means that you can display the form and submit the @@ -749,9 +906,6 @@ form via the same URL, while using distinct controllers for the two actions. Adding a Host Requirement ~~~~~~~~~~~~~~~~~~~~~~~~~ -.. versionadded:: 2.2 - Host matching support was introduced in Symfony 2.2 - You can also match on the HTTP *host* of the incoming request. For more information, see :doc:`/components/routing/hostname_pattern` in the Routing component documentation. @@ -761,9 +915,6 @@ component documentation. Completely Customized Route Matching with Conditions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. versionadded:: 2.4 - Route conditions were introduced in Symfony 2.4. - As you've seen, a route can be made to match only certain routing wildcards (via regular expressions), HTTP methods, or host names. But the routing system can be extended to have an almost infinite flexibility using ``conditions``: @@ -819,10 +970,12 @@ header matches ``firefox``. You can do any complex logic you need in the expression by leveraging two variables that are passed into the expression: -* ``context``: An instance of :class:`Symfony\\Component\\Routing\\RequestContext`, - which holds the most fundamental information about the route being matched; -* ``request``: The Symfony :class:`Symfony\\Component\\HttpFoundation\\Request` - object (see :ref:`component-http-foundation-request`). +``context`` + An instance of :class:`Symfony\\Component\\Routing\\RequestContext`, which + holds the most fundamental information about the route being matched. +``request`` + The Symfony :class:`Symfony\\Component\\HttpFoundation\\Request` object + (see :ref:`component-http-foundation-request`). .. caution:: @@ -858,14 +1011,37 @@ routing system can be: .. configuration-block:: + .. code-block:: php-annotations + + // src/AppBundle/Controller/ArticleController.php + + // ... + class ArticleController extends Controller + { + /** + * @Route( + * "/articles/{_locale}/{year}/{title}.{_format}", + * defaults={"_format": "html"}, + * requirements={ + * "_locale": "en|fr", + * "_format": "html|rss", + * "year": "\d+" + * } + * ) + */ + public function showAction($_locale, $year, $title) + { + } + } + .. code-block:: yaml # app/config/routing.yml article_show: - path: /articles/{culture}/{year}/{title}.{_format} - defaults: { _controller: AcmeDemoBundle:Article:show, _format: html } + path: /articles/{_locale}/{year}/{title}.{_format} + defaults: { _controller: AppBundle:Article:show, _format: html } requirements: - culture: en|fr + _locale: en|fr _format: html|rss year: \d+ @@ -879,11 +1055,11 @@ routing system can be: http://symfony.com/schema/routing/routing-1.0.xsd"> + path="/articles/{_locale}/{year}/{title}.{_format}"> - AcmeDemoBundle:Article:show + AppBundle:Article:show html - en|fr + en|fr html|rss \d+ @@ -899,11 +1075,11 @@ routing system can be: $collection = new RouteCollection(); $collection->add( 'article_show', - new Route('/articles/{culture}/{year}/{title}.{_format}', array( - '_controller' => 'AcmeDemoBundle:Article:show', + new Route('/articles/{_locale}/{year}/{title}.{_format}', array( + '_controller' => 'AppBundle:Article:show', '_format' => 'html', ), array( - 'culture' => 'en|fr', + '_locale' => 'en|fr', '_format' => 'html|rss', 'year' => '\d+', )) @@ -911,7 +1087,7 @@ routing system can be: return $collection; -As you've seen, this route will only match if the ``{culture}`` portion of +As you've seen, this route will only match if the ``{_locale}`` portion of the URL is either ``en`` or ``fr`` and if the ``{year}`` is a number. This route also shows how you can use a dot between placeholders instead of a slash. URLs matching this route might look like: @@ -927,7 +1103,7 @@ a slash. URLs matching this route might look like: This example also highlights the special ``_format`` routing parameter. When using this parameter, the matched value becomes the "request format" of the ``Request`` object. Ultimately, the request format is used for such - things such as setting the ``Content-Type`` of the response (e.g. a ``json`` + things as setting the ``Content-Type`` of the response (e.g. a ``json`` request format translates into a ``Content-Type`` of ``application/json``). It can also be used in the controller to render a different template for each value of ``_format``. The ``_format`` parameter is a very powerful way @@ -946,12 +1122,15 @@ As you've seen, each routing parameter or default value is eventually available as an argument in the controller method. Additionally, there are three parameters that are special: each adds a unique piece of functionality inside your application: -* ``_controller``: As you've seen, this parameter is used to determine which - controller is executed when the route is matched; +``_controller`` + As you've seen, this parameter is used to determine which controller is + executed when the route is matched. -* ``_format``: Used to set the request format (:ref:`read more `); +``_format`` + Used to set the request format (:ref:`read more `). -* ``_locale``: Used to set the locale on the request (:ref:`read more `). +``_locale`` + Used to set the locale on the request (:ref:`read more `). .. index:: single: Routing; Controllers @@ -970,18 +1149,18 @@ each separated by a colon: **bundle**:**controller**:**action** -For example, a ``_controller`` value of ``AcmeBlogBundle:Blog:show`` means: +For example, a ``_controller`` value of ``AppBundle:Blog:show`` means: -+----------------+------------------+-------------+ -| Bundle | Controller Class | Method Name | -+================+==================+=============+ -| AcmeBlogBundle | BlogController | showAction | -+----------------+------------------+-------------+ +========= ================== ============== +Bundle Controller Class Method Name +========= ================== ============== +AppBundle ``BlogController`` ``showAction`` +========= ================== ============== The controller might look like this:: - // src/Acme/BlogBundle/Controller/BlogController.php - namespace Acme\BlogBundle\Controller; + // src/AppBundle/Controller/BlogController.php + namespace AppBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; @@ -997,7 +1176,7 @@ Notice that Symfony adds the string ``Controller`` to the class name (``Blog`` => ``BlogController``) and ``Action`` to the method name (``show`` => ``showAction``). You could also refer to this controller using its fully-qualified class name -and method: ``Acme\BlogBundle\Controller\BlogController::showAction``. +and method: ``AppBundle\Controller\BlogController::showAction``. But if you follow some simple conventions, the logical name is more concise and allows more flexibility. @@ -1016,7 +1195,7 @@ each is made available as an argument to the controller method:: public function showAction($slug) { - // ... + // ... } In reality, the entire ``defaults`` collection is merged with the parameter @@ -1028,11 +1207,12 @@ for a route parameter of that name and assigns its value to that argument. In the advanced example above, any combination (in any order) of the following variables could be used as arguments to the ``showAction()`` method: -* ``$culture`` +* ``$_locale`` * ``$year`` * ``$title`` * ``$_format`` * ``$_controller`` +* ``$_route`` Since the placeholders and ``defaults`` collection are merged together, even the ``$_controller`` variable is available. For a more detailed discussion, @@ -1040,8 +1220,8 @@ see :ref:`route-parameters-controller-arguments`. .. tip:: - You can also use a special ``$_route`` variable, which is set to the - name of the route that was matched. + The special ``$_route`` variable is set to the name of the route that was + matched. You can even add extra information to your route definition and access it within your controller. For more information on this topic, @@ -1055,18 +1235,20 @@ see :doc:`/cookbook/routing/extra_information`. Including External Routing Resources ------------------------------------ -All routes are loaded via a single configuration file - usually ``app/config/routing.yml`` -(see `Creating Routes`_ above). Commonly, however, you'll want to load routes -from other places, like a routing file that lives inside a bundle. This can -be done by "importing" that file: +All routes are loaded via a single configuration file - usually +``app/config/routing.yml`` (see `Creating Routes`_ above). However, if you use +routing annotations, you'll need to point the router to the controllers with +the annotations. This can be done by "importing" directories into the routing +configuration: .. configuration-block:: .. code-block:: yaml # app/config/routing.yml - acme_hello: - resource: "@AcmeHelloBundle/Resources/config/routing.yml" + app: + resource: "@AppBundle/Controller/" + type: annotation # required to enable the Annotation reader for this resource .. code-block:: xml @@ -1077,7 +1259,8 @@ be done by "importing" that file: xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd"> - + + .. code-block:: php @@ -1087,75 +1270,76 @@ be done by "importing" that file: $collection = new RouteCollection(); $collection->addCollection( - $loader->import("@AcmeHelloBundle/Resources/config/routing.php") + // second argument is the type, which is required to enable + // the annotation reader for this resource + $loader->import("@AppBundle/Controller/", "annotation") ); return $collection; .. note:: - When importing resources from YAML, the key (e.g. ``acme_hello``) is meaningless. + When importing resources from YAML, the key (e.g. ``app``) is meaningless. Just be sure that it's unique so no other lines override it. The ``resource`` key loads the given routing resource. In this example the -resource is the full path to a file, where the ``@AcmeHelloBundle`` shortcut -syntax resolves to the path of that bundle. The imported file might look -like this: +resource is a directory, where the ``@AppBundle`` shortcut syntax resolves to +the full path of the AppBundle. When pointing to a directory, all files in that +directory are parsed and put into the routing. -.. configuration-block:: +.. note:: - .. code-block:: yaml + You can also include other routing configuration files, this is often used + to import the routing of third party bundles: - # src/Acme/HelloBundle/Resources/config/routing.yml - acme_hello: - path: /hello/{name} - defaults: { _controller: AcmeHelloBundle:Hello:index } + .. configuration-block:: - .. code-block:: xml + .. code-block:: yaml - - - + # app/config/routing.yml + app: + resource: "@AcmeOtherBundle/Resources/config/routing.yml" - - AcmeHelloBundle:Hello:index - - + .. code-block:: xml - .. code-block:: php + + + - // src/Acme/HelloBundle/Resources/config/routing.php - use Symfony\Component\Routing\RouteCollection; - use Symfony\Component\Routing\Route; + + - $collection = new RouteCollection(); - $collection->add('acme_hello', new Route('/hello/{name}', array( - '_controller' => 'AcmeHelloBundle:Hello:index', - ))); + .. code-block:: php - return $collection; + // app/config/routing.php + use Symfony\Component\Routing\RouteCollection; + + $collection = new RouteCollection(); + $collection->addCollection( + $loader->import("@AcmeOtherBundle/Resources/config/routing.php") + ); -The routes from this file are parsed and loaded in the same way as the main -routing file. + return $collection; Prefixing Imported Routes ~~~~~~~~~~~~~~~~~~~~~~~~~ You can also choose to provide a "prefix" for the imported routes. For example, -suppose you want the ``acme_hello`` route to have a final path of ``/admin/hello/{name}`` -instead of simply ``/hello/{name}``: +suppose you want to prefix all routes in the AppBundle with ``/site`` (e.g. +``/site/blog/{slug}`` instead of ``/blog/{slug}``): .. configuration-block:: .. code-block:: yaml # app/config/routing.yml - acme_hello: - resource: "@AcmeHelloBundle/Resources/config/routing.yml" - prefix: /admin + app: + resource: "@AppBundle/Controller/" + type: annotation + prefix: /site .. code-block:: xml @@ -1167,8 +1351,9 @@ instead of simply ``/hello/{name}``: http://symfony.com/schema/routing/routing-1.0.xsd"> + resource="@AppBundle/Controller/" + type="annotation" + prefix="/site" /> .. code-block:: php @@ -1176,29 +1361,20 @@ instead of simply ``/hello/{name}``: // app/config/routing.php use Symfony\Component\Routing\RouteCollection; - $acmeHello = $loader->import('@AcmeHelloBundle/Resources/config/routing.php'); - $acmeHello->addPrefix('/admin'); + $app = $loader->import('@AppBundle/Controller/', 'annotation'); + $app->addPrefix('/site'); $collection = new RouteCollection(); - $collection->addCollection($acmeHello); + $collection->addCollection($app); return $collection; -The string ``/admin`` will now be prepended to the path of each route loaded -from the new routing resource. - -.. tip:: - - You can also define routes using annotations. See the - :doc:`FrameworkExtraBundle documentation ` - to see how. +The path of each route being loaded from the new routing resource will now +be prefixed with the string ``/site``. Adding a Host Requirement to Imported Routes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. versionadded:: 2.2 - Host matching support was introduced in Symfony 2.2 - You can set the host regex on imported routes. For more information, see :ref:`component-routing-host-imported`. @@ -1210,12 +1386,15 @@ Visualizing & Debugging Routes While adding and customizing routes, it's helpful to be able to visualize and get detailed information about your routes. A great way to see every route -in your application is via the ``router:debug`` console command. Execute +in your application is via the ``debug:router`` console command. Execute the command by running the following from the root of your project. .. code-block:: bash - $ php app/console router:debug + $ php app/console debug:router + +.. versionadded:: 2.6 + Prior to Symfony 2.6, this command was called ``router:debug``. This command will print a helpful list of *all* the configured routes in your application: @@ -1225,7 +1404,7 @@ your application: homepage ANY / contact GET /contact contact_process POST /contact - article_show ANY /articles/{culture}/{year}/{title}.{_format} + article_show ANY /articles/{_locale}/{year}/{title}.{_format} blog ANY /blog/{page} blog_show ANY /blog/{slug} @@ -1234,7 +1413,7 @@ the route name after the command: .. code-block:: bash - $ php app/console router:debug article_show + $ php app/console debug:router article_show Likewise, if you want to test whether a URL matches a given route, you can use the ``router:match`` console command: @@ -1265,10 +1444,12 @@ system. Take the ``blog_show`` example route from earlier:: $params = $this->get('router')->match('/blog/my-blog-post'); // array( // 'slug' => 'my-blog-post', - // '_controller' => 'AcmeBlogBundle:Blog:show', + // '_controller' => 'AppBundle:Blog:show', // ) - $uri = $this->get('router')->generate('blog_show', array('slug' => 'my-blog-post')); + $uri = $this->get('router')->generate('blog_show', array( + 'slug' => 'my-blog-post' + )); // /blog/my-blog-post To generate a URL, you need to specify the name of the route (e.g. ``blog_show``) @@ -1336,7 +1517,10 @@ Generating URLs with Query Strings The ``generate`` method takes an array of wildcard values to generate the URI. But if you pass extra ones, they will be added to the URI as a query string:: - $this->get('router')->generate('blog', array('page' => 2, 'category' => 'Symfony')); + $this->get('router')->generate('blog', array( + 'page' => 2, + 'category' => 'Symfony' + )); // /blog/2?category=Symfony Generating URLs from a Template @@ -1377,7 +1561,7 @@ method:: From a template, in Twig, simply use the ``url()`` function (which generates an absolute URL) rather than the ``path()`` function (which generates a relative URL). In PHP, pass ``true`` -to ``generateUrl()``: +to ``generate()``: .. configuration-block:: diff --git a/book/security.rst b/book/security.rst index 5996d8bdf56..4193eed7e1b 100644 --- a/book/security.rst +++ b/book/security.rst @@ -4,38 +4,32 @@ Security ======== -Security is a two-step process whose goal is to prevent a user from accessing -a resource that they should not have access to. +Symfony's security system is incredibly powerful, but it can also be confusing +to set up. In this chapter, you'll learn how to set up your application's security +step-by-step, from configuring your firewall and how you load users to denying +access and fetching the User object. Depending on what you need, sometimes +the initial setup can be tough. But once it's done, Symfony's security system +is both flexible and (hopefully) fun to work with. -In the first step of the process, the security system identifies who the user -is by requiring the user to submit some sort of identification. This is called -**authentication**, and it means that the system is trying to find out who -you are. +Since there's a lot to talk about, this chapter is organized into a few big +sections: -Once the system knows who you are, the next step is to determine if you should -have access to a given resource. This part of the process is called **authorization**, -and it means that the system is checking to see if you have privileges to -perform a certain action. +1) Initial ``security.yml`` setup (*authentication*); -.. image:: /images/book/security_authentication_authorization.png - :align: center +2) Denying access to your app (*authorization*); -Since the best way to learn is to see an example, just imagine that you want -to secure your application with HTTP Basic authentication. +3) Fetching the current User object -.. note:: +These are followed by a number of small (but still captivating) sections, +like :ref:`logging out ` and :ref:`encoding user passwords `. - :doc:`Symfony's security component ` is - available as a standalone PHP library for use inside any PHP project. +.. _book-security-firewalls: -Basic Example: HTTP Authentication ----------------------------------- +1) Initial security.yml Setup (Authentication) +---------------------------------------------- -The Security component can be configured via your application configuration. -In fact, most standard security setups are just a matter of using the right -configuration. The following configuration tells Symfony to secure any URL -matching ``/admin/*`` and to ask the user for credentials using basic HTTP -authentication (i.e. the old-school username/password box): +The security system is configured in ``app/config/security.yml``. The default +configuration looks like this: .. configuration-block:: @@ -43,27 +37,17 @@ authentication (i.e. the old-school username/password box): # app/config/security.yml security: - firewalls: - secured_area: - pattern: ^/ - anonymous: ~ - http_basic: - realm: "Secured Demo Area" - - access_control: - - { path: ^/admin/, roles: ROLE_ADMIN } - # Include the following line to also secure the /admin path itself - # - { path: ^/admin$, roles: ROLE_ADMIN } - providers: in_memory: - memory: - users: - ryan: { password: ryanpass, roles: 'ROLE_USER' } - admin: { password: kitten, roles: 'ROLE_ADMIN' } + memory: ~ - encoders: - Symfony\Component\Security\Core\User\User: plaintext + firewalls: + dev: + pattern: ^/(_(profiler|wdt)|css|images|js)/ + security: false + + default: + anonymous: ~ .. code-block:: xml @@ -72,29 +56,21 @@ authentication (i.e. the old-school username/password box): + xsi:schemaLocation="http://symfony.com/schema/dic/services + http://symfony.com/schema/dic/services/services-1.0.xsd"> - - - - - - - - - - - - - - - + - + + + + + @@ -102,206 +78,59 @@ authentication (i.e. the old-school username/password box): // app/config/security.php $container->loadFromExtension('security', array( - 'firewalls' => array( - 'secured_area' => array( - 'pattern' => '^/', - 'anonymous' => array(), - 'http_basic' => array( - 'realm' => 'Secured Demo Area', - ), - ), - ), - 'access_control' => array( - array('path' => '^/admin/', 'role' => 'ROLE_ADMIN'), - // Include the following line to also secure the /admin path itself - // array('path' => '^/admin$', 'role' => 'ROLE_ADMIN'), - ), 'providers' => array( 'in_memory' => array( - 'memory' => array( - 'users' => array( - 'ryan' => array( - 'password' => 'ryanpass', - 'roles' => 'ROLE_USER', - ), - 'admin' => array( - 'password' => 'kitten', - 'roles' => 'ROLE_ADMIN', - ), - ), - ), + 'memory' => array(), ), ), - 'encoders' => array( - 'Symfony\Component\Security\Core\User\User' => 'plaintext', + 'firewalls' => array( + 'dev' => array( + 'pattern' => '^/(_(profiler|wdt)|css|images|js)/', + 'security' => false, + ), + 'default' => array( + 'anonymous' => null, + ), ), )); -.. tip:: - - A standard Symfony distribution separates the security configuration - into a separate file (e.g. ``app/config/security.yml``). If you don't - have a separate security file, you can put the configuration directly - into your main config file (e.g. ``app/config/config.yml``). - -The end result of this configuration is a fully-functional security system -that looks like the following: - -* There are two users in the system (``ryan`` and ``admin``); -* Users authenticate themselves via the basic HTTP authentication prompt; -* Any URL matching ``/admin/*`` is secured, and only the ``admin`` user - can access it; -* All URLs *not* matching ``/admin/*`` are accessible by all users (and the - user is never prompted to log in). - -Read this short summary about how security works and how each part of the -configuration comes into play. - -How Security Works: Authentication and Authorization ----------------------------------------------------- - -Symfony's security system works by determining who a user is (i.e. authentication) -and then checking to see if that user should have access to a specific resource -or URL. - -.. _book-security-firewalls: - -Firewalls (Authentication) -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -When a user makes a request to a URL that's protected by a firewall, the -security system is activated. The job of the firewall is to determine whether -or not the user needs to be authenticated, and if they do, to send a response -back to the user initiating the authentication process. - -A firewall is activated when the URL of an incoming request matches the configured -firewall's regular expression ``pattern`` config value. In this example, the -``pattern`` (``^/``) will match *every* incoming request. The fact that the -firewall is activated does *not* mean, however, that the HTTP authentication -username and password box is displayed for every URL. For example, any user -can access ``/foo`` without being prompted to authenticate. +The ``firewalls`` key is the *heart* of your security configuration. The +``dev`` firewall isn't important, it just makes sure that Symfony's development +tools - which live under URLs like ``/_profiler`` and ``/_wdt`` aren't blocked +by your security. .. tip:: - You can also match a request against other details of the request (e.g. host). - For more information and examples read :doc:`/cookbook/security/firewall_restriction`. + You can also match a request against other details of the request (e.g. host). For more + information and examples read :doc:`/cookbook/security/firewall_restriction`. -.. image:: /images/book/security_anonymous_user_access.png - :align: center - -This works first because the firewall allows *anonymous users* via the ``anonymous`` -configuration parameter. In other words, the firewall doesn't require the -user to fully authenticate immediately. And because no special ``role`` is -needed to access ``/foo`` (under the ``access_control`` section), the request -can be fulfilled without ever asking the user to authenticate. - -If you remove the ``anonymous`` key, the firewall will *always* make a user -fully authenticate immediately. - -Access Controls (Authorization) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -If a user requests ``/admin/foo``, however, the process behaves differently. -This is because of the ``access_control`` configuration section that says -that any URL matching the regular expression pattern ``^/admin`` (i.e. ``/admin`` -or anything matching ``/admin/*``) requires the ``ROLE_ADMIN`` role. Roles -are the basis for most authorization: a user can access ``/admin/foo`` only -if it has the ``ROLE_ADMIN`` role. - -.. image:: /images/book/security_anonymous_user_denied_authorization.png - :align: center - -Like before, when the user originally makes the request, the firewall doesn't -ask for any identification. However, as soon as the access control layer -denies the user access (because the anonymous user doesn't have the ``ROLE_ADMIN`` -role), the firewall jumps into action and initiates the authentication process. -The authentication process depends on the authentication mechanism you're -using. For example, if you're using the form login authentication method, -the user will be redirected to the login page. If you're using HTTP authentication, -the user will be sent an HTTP 401 response so that the user sees the username -and password box. +All other URLs will be handled by the ``default`` firewall (no ``pattern`` +key means it matches *all* URLs). You can think of the firewall like your +security system, and so it usually makes sense to have just one main firewall. +But this does *not* mean that every URL requires authentication - the ``anonymous`` +key takes care of this. In fact, if you go to the homepage right now, you'll +have access and you'll see that you're "authenticated" as ``anon.``. Don't +be fooled by the "Yes" next to Authenticated, you're just an anonymous user: -The user now has the opportunity to submit its credentials back to the application. -If the credentials are valid, the original request can be re-tried. - -.. image:: /images/book/security_ryan_no_role_admin_access.png +.. image:: /images/book/security_anonymous_wdt.png :align: center -In this example, the user ``ryan`` successfully authenticates with the firewall. -But since ``ryan`` doesn't have the ``ROLE_ADMIN`` role, they're still denied -access to ``/admin/foo``. Ultimately, this means that the user will see some -sort of message indicating that access has been denied. +You'll learn later how to deny access to certain URLs or controllers. .. tip:: - When Symfony denies the user access, the user sees an error screen and - receives a 403 HTTP status code (``Forbidden``). You can customize the - access denied error screen by following the directions in the - :ref:`Error Pages ` cookbook entry - to customize the 403 error page. + Security is *highly* configurable and there's a + :doc:`Security Configuration Reference ` + that shows all of the options with some extra explanation. -Finally, if the ``admin`` user requests ``/admin/foo``, a similar process -takes place, except now, after being authenticated, the access control layer -will let the request pass through: +A) Configuring how your Users will Authenticate +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. image:: /images/book/security_admin_role_access.png - :align: center +The main job of a firewall is to configure *how* your users will authenticate. +Will they use a login form? Http Basic? An API token? All of the above? -The request flow when a user requests a protected resource is straightforward, -but incredibly flexible. As you'll see later, authentication can be handled -in any number of ways, including via a form login, X.509 certificate, or by -authenticating the user via Twitter. Regardless of the authentication method, -the request flow is always the same: - -#. A user accesses a protected resource; -#. The application redirects the user to the login form; -#. The user submits its credentials (e.g. username/password); -#. The firewall authenticates the user; -#. The authenticated user re-tries the original request. - -.. note:: - - The *exact* process actually depends a little bit on which authentication - mechanism you're using. For example, when using form login, the user - submits its credentials to one URL that processes the form (e.g. ``/login_check``) - and then is redirected back to the originally requested URL (e.g. ``/admin/foo``). - But with HTTP authentication, the user submits its credentials directly - to the original URL (e.g. ``/admin/foo``) and then the page is returned - to the user in that same request (i.e. no redirect). - - These types of idiosyncrasies shouldn't cause you any problems, but they're - good to keep in mind. - -.. tip:: - - You'll also learn later how *anything* can be secured in Symfony, including - specific controllers, objects, or even PHP methods. - -.. _book-security-form-login: - -Using a Traditional Login Form ------------------------------- - -.. tip:: - - In this section, you'll learn how to create a basic login form that continues - to use the hard-coded users that are defined in the ``security.yml`` file. - - To load users from the database, please read :doc:`/cookbook/security/entity_provider`. - By reading that article and this section, you can create a full login form - system that loads users from the database. - -So far, you've seen how to blanket your application beneath a firewall and -then protect access to certain areas with roles. By using HTTP Authentication, -you can effortlessly tap into the native username/password box offered by -all browsers. However, Symfony supports many authentication mechanisms out -of the box. For details on all of them, see the -:doc:`Security Configuration Reference `. - -In this section, you'll enhance this process by allowing the user to authenticate -via a traditional HTML login form. - -First, enable form login under your firewall: +Let's start with Http Basic (the old-school pop-up) and work up from there. +To activate this, add the ``http_basic`` key under your firewall: .. configuration-block:: @@ -309,546 +138,13 @@ First, enable form login under your firewall: # app/config/security.yml security: - firewalls: - secured_area: - pattern: ^/ - anonymous: ~ - form_login: - login_path: login - check_path: login_check - - .. code-block:: xml - - - - - - - - - - - - - - .. code-block:: php - - // app/config/security.php - $container->loadFromExtension('security', array( - 'firewalls' => array( - 'secured_area' => array( - 'pattern' => '^/', - 'anonymous' => array(), - 'form_login' => array( - 'login_path' => 'login', - 'check_path' => 'login_check', - ), - ), - ), - )); - -.. tip:: - - If you don't need to customize your ``login_path`` or ``check_path`` - values (the values used here are the default values), you can shorten - your configuration: - - .. configuration-block:: - - .. code-block:: yaml - - form_login: ~ - - .. code-block:: xml - - - - .. code-block:: php - - 'form_login' => array(), - -Now, when the security system initiates the authentication process, it will -redirect the user to the login form (``/login`` by default). Implementing this -login form visually is your job. First, create the two routes you used in the -security configuration: the ``login`` route will display the login form (i.e. -``/login``) and the ``login_check`` route will handle the login form -submission (i.e. ``/login_check``): - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/routing.yml - login: - path: /login - defaults: { _controller: AcmeSecurityBundle:Security:login } - login_check: - path: /login_check - - .. code-block:: xml - - - - - - - AcmeSecurityBundle:Security:login - - - - - - .. code-block:: php - - // app/config/routing.php - use Symfony\Component\Routing\RouteCollection; - use Symfony\Component\Routing\Route; - - $collection = new RouteCollection(); - $collection->add('login', new Route('/login', array( - '_controller' => 'AcmeDemoBundle:Security:login', - ))); - $collection->add('login_check', new Route('/login_check', array())); - - return $collection; - -.. note:: - - You will *not* need to implement a controller for the ``/login_check`` - URL as the firewall will automatically catch and process any form submitted - to this URL. However, you *must* have a route (as shown here) for this - URL, as well as one for your logout path (see :ref:`book-security-logging-out`). - -Notice that the name of the ``login`` route matches the ``login_path`` config -value, as that's where the security system will redirect users that need -to login. - -Next, create the controller that will display the login form:: - - // src/Acme/SecurityBundle/Controller/SecurityController.php; - namespace Acme\SecurityBundle\Controller; - - use Symfony\Bundle\FrameworkBundle\Controller\Controller; - use Symfony\Component\HttpFoundation\Request; - use Symfony\Component\Security\Core\SecurityContextInterface; - - class SecurityController extends Controller - { - public function loginAction(Request $request) - { - $session = $request->getSession(); - - // get the login error if there is one - if ($request->attributes->has(SecurityContextInterface::AUTHENTICATION_ERROR)) { - $error = $request->attributes->get( - SecurityContextInterface::AUTHENTICATION_ERROR - ); - } elseif (null !== $session && $session->has(SecurityContextInterface::AUTHENTICATION_ERROR)) { - $error = $session->get(SecurityContextInterface::AUTHENTICATION_ERROR); - $session->remove(SecurityContextInterface::AUTHENTICATION_ERROR); - } else { - $error = ''; - } - - // last username entered by the user - $lastUsername = (null === $session) ? '' : $session->get(SecurityContextInterface::LAST_USERNAME); - - return $this->render( - 'AcmeSecurityBundle:Security:login.html.twig', - array( - // last username entered by the user - 'last_username' => $lastUsername, - 'error' => $error, - ) - ); - } - } - -Don't let this controller confuse you. As you'll see in a moment, when the -user submits the form, the security system automatically handles the form -submission for you. If the user had submitted an invalid username or password, -this controller reads the form submission error from the security system so -that it can be displayed back to the user. - -In other words, your job is to display the login form and any login errors -that may have occurred, but the security system itself takes care of checking -the submitted username and password and authenticating the user. - -Finally, create the corresponding template: - -.. configuration-block:: - - .. code-block:: html+jinja - - {# src/Acme/SecurityBundle/Resources/views/Security/login.html.twig #} - {% if error %} -
{{ error.message }}
- {% endif %} - - - - - - - - - {# - If you want to control the URL the user - is redirected to on success (more details below) - - #} - - - - - .. code-block:: html+php - - - -
getMessage() ?>
- - -
- - - - - - - - - -
- -.. caution:: - - This login form is currently not protected against CSRF attacks. Read - :doc:`/cookbook/security/csrf_in_login_form` on how to protect your login form. - -.. tip:: - - The ``error`` variable passed into the template is an instance of - :class:`Symfony\\Component\\Security\\Core\\Exception\\AuthenticationException`. - It may contain more information - or even sensitive information - about - the authentication failure, so use it wisely! - -The form has very few requirements. First, by submitting the form to ``/login_check`` -(via the ``login_check`` route), the security system will intercept the form -submission and process the form for you automatically. Second, the security -system expects the submitted fields to be called ``_username`` and ``_password`` -(these field names can be :ref:`configured `). - -And that's it! When you submit the form, the security system will automatically -check the user's credentials and either authenticate the user or send the -user back to the login form where the error can be displayed. - -To review the whole process: - -#. The user tries to access a resource that is protected; -#. The firewall initiates the authentication process by redirecting the - user to the login form (``/login``); -#. The ``/login`` page renders login form via the route and controller created - in this example; -#. The user submits the login form to ``/login_check``; -#. The security system intercepts the request, checks the user's submitted - credentials, authenticates the user if they are correct, and sends the - user back to the login form if they are not. - -By default, if the submitted credentials are correct, the user will be redirected -to the original page that was requested (e.g. ``/admin/foo``). If the user -originally went straight to the login page, he'll be redirected to the homepage. -This can be highly customized, allowing you to, for example, redirect the -user to a specific URL. - -For more details on this and how to customize the form login process in general, -see :doc:`/cookbook/security/form_login`. - -.. _book-security-common-pitfalls: - -.. sidebar:: Avoid common Pitfalls - - When setting up your login form, watch out for a few common pitfalls. - - **1. Create the correct routes** - - First, be sure that you've defined the ``login`` and ``login_check`` - routes correctly and that they correspond to the ``login_path`` and - ``check_path`` config values. A misconfiguration here can mean that you're - redirected to a 404 page instead of the login page, or that submitting - the login form does nothing (you just see the login form over and over - again). - - **2. Be sure the login page isn't secure** - - Also, be sure that the login page does *not* require any roles to be - viewed. For example, the following configuration - which requires the - ``ROLE_ADMIN`` role for all URLs (including the ``/login`` URL), will - cause a redirect loop: - - .. configuration-block:: - - .. code-block:: yaml - - # app/config/security.yml - - # ... - access_control: - - { path: ^/, roles: ROLE_ADMIN } - - .. code-block:: xml - - - - - - - - - .. code-block:: php - - // app/config/security.php - - // ... - 'access_control' => array( - array('path' => '^/', 'role' => 'ROLE_ADMIN'), - ), - - Removing the access control on the ``/login`` URL fixes the problem: - - .. configuration-block:: - - .. code-block:: yaml - - # app/config/security.yml - # ... - access_control: - - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY } - - { path: ^/, roles: ROLE_ADMIN } - - .. code-block:: xml - - - - - - - - - .. code-block:: php - - // app/config/security.php - - // ... - 'access_control' => array( - array('path' => '^/login', 'role' => 'IS_AUTHENTICATED_ANONYMOUSLY'), - array('path' => '^/', 'role' => 'ROLE_ADMIN'), - ), - - Also, if your firewall does *not* allow for anonymous users, you'll need - to create a special firewall that allows anonymous users for the login - page: - - .. configuration-block:: - - .. code-block:: yaml - - # app/config/security.yml - - # ... firewalls: - login_firewall: - pattern: ^/login$ + # ... + default: anonymous: ~ - secured_area: - pattern: ^/ - form_login: ~ - - .. code-block:: xml - - - - - - - - - - - - .. code-block:: php - - // app/config/security.php - - // ... - 'firewalls' => array( - 'login_firewall' => array( - 'pattern' => '^/login$', - 'anonymous' => array(), - ), - 'secured_area' => array( - 'pattern' => '^/', - 'form_login' => array(), - ), - ), - - **3. Be sure /login_check is behind a firewall** - - Next, make sure that your ``check_path`` URL (e.g. ``/login_check``) - is behind the firewall you're using for your form login (in this example, - the single firewall matches *all* URLs, including ``/login_check``). If - ``/login_check`` doesn't match any firewall, you'll receive a ``Unable - to find the controller for path "/login_check"`` exception. - - **4. Multiple firewalls don't share security context** - - If you're using multiple firewalls and you authenticate against one firewall, - you will *not* be authenticated against any other firewalls automatically. - Different firewalls are like different security systems. To do this you have - to explicitly specify the same :ref:`reference-security-firewall-context` - for different firewalls. But usually for most applications, having one - main firewall is enough. - - **5. Routing error pages are not covered by firewalls** - - As Routing is done *before* security, Routing error pages are not covered - by any firewall. This means you can't check for security or even access - the user object on these pages. See :doc:`/cookbook/controller/error_pages` - for more details. - -Authorization -------------- - -The first step in security is always authentication. Once the user has been -authenticated, authorization begins. Authorization provides a standard and -powerful way to decide if a user can access any resource (a URL, a model -object, a method call, ...). This works by assigning specific roles to each -user, and then requiring different roles for different resources. - -The process of authorization has two different sides: - -#. The user has a specific set of roles; -#. A resource requires a specific role in order to be accessed. - -In this section, you'll focus on how to secure different resources (e.g. URLs, -method calls, etc) with different roles. Later, you'll learn more about how -roles are created and assigned to users. - -Securing specific URL Patterns -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The most basic way to secure part of your application is to secure an entire -URL pattern. You've seen this already in the first example of this chapter, -where anything matching the regular expression pattern ``^/admin`` requires -the ``ROLE_ADMIN`` role. - -You can define as many URL patterns as you need - each is a regular expression. - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/security.yml - security: - # ... - access_control: - - { path: ^/admin/users, roles: ROLE_SUPER_ADMIN } - - { path: ^/admin, roles: ROLE_ADMIN } - - .. code-block:: xml - - - - - - - - - - - - - - - .. code-block:: php - - // app/config/security.php - $container->loadFromExtension('security', array( - // ... - 'access_control' => array( - array('path' => '^/admin/users', 'role' => 'ROLE_SUPER_ADMIN'), - array('path' => '^/admin', 'role' => 'ROLE_ADMIN'), - ), - )); - -.. tip:: - - Prepending the path with ``^`` ensures that only URLs *beginning* with - the pattern are matched. For example, a path of simply ``/admin`` (without - the ``^``) would correctly match ``/admin/foo`` but would also match URLs - like ``/foo/admin``. - -.. _security-book-access-control-explanation: - -Understanding how ``access_control`` Works -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -For each incoming request, Symfony checks each ``access_control`` entry -to find *one* that matches the current request. As soon as it finds a matching -``access_control`` entry, it stops - only the **first** matching ``access_control`` -is used to enforce access. - -Each ``access_control`` has several options that configure two different -things: - -#. :ref:`should the incoming request match this access control entry ` -#. :ref:`once it matches, should some sort of access restriction be enforced `: - -.. _security-book-access-control-matching-options: - -1. Matching Options -................... - -Symfony creates an instance of :class:`Symfony\\Component\\HttpFoundation\\RequestMatcher` -for each ``access_control`` entry, which determines whether or not a given -access control should be used on this request. The following ``access_control`` -options are used for matching: - -* ``path`` -* ``ip`` or ``ips`` -* ``host`` -* ``methods`` - -Take the following ``access_control`` entries as an example: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/security.yml - security: - # ... - access_control: - - { path: ^/admin, roles: ROLE_USER_IP, ip: 127.0.0.1 } - - { path: ^/admin, roles: ROLE_USER_HOST, host: symfony\.com$ } - - { path: ^/admin, roles: ROLE_USER_METHOD, methods: [POST, PUT] } - - { path: ^/admin, roles: ROLE_USER } + http_basic: ~ .. code-block:: xml @@ -862,160 +158,11 @@ Take the following ``access_control`` entries as an example: - - - - - - - - - - .. code-block:: php - - // app/config/security.php - $container->loadFromExtension('security', array( - // ... - 'access_control' => array( - array( - 'path' => '^/admin', - 'role' => 'ROLE_USER_IP', - 'ip' => '127.0.0.1', - ), - array( - 'path' => '^/admin', - 'role' => 'ROLE_USER_HOST', - 'host' => 'symfony\.com$', - ), - array( - 'path' => '^/admin', - 'role' => 'ROLE_USER_METHOD', - 'method' => 'POST, PUT', - ), - array( - 'path' => '^/admin', - 'role' => 'ROLE_USER', - ), - ), - )); - -For each incoming request, Symfony will decide which ``access_control`` -to use based on the URI, the client's IP address, the incoming host name, -and the request method. Remember, the first rule that matches is used, and -if ``ip``, ``host`` or ``method`` are not specified for an entry, that ``access_control`` -will match any ``ip``, ``host`` or ``method``: - -+-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+ -| URI | IP | HOST | METHOD | ``access_control`` | Why? | -+=================+=============+=============+============+================================+=============================================================+ -| ``/admin/user`` | 127.0.0.1 | example.com | GET | rule #1 (``ROLE_USER_IP``) | The URI matches ``path`` and the IP matches ``ip``. | -+-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+ -| ``/admin/user`` | 127.0.0.1 | symfony.com | GET | rule #1 (``ROLE_USER_IP``) | The ``path`` and ``ip`` still match. This would also match | -| | | | | | the ``ROLE_USER_HOST`` entry, but *only* the **first** | -| | | | | | ``access_control`` match is used. | -+-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+ -| ``/admin/user`` | 168.0.0.1 | symfony.com | GET | rule #2 (``ROLE_USER_HOST``) | The ``ip`` doesn't match the first rule, so the second | -| | | | | | rule (which matches) is used. | -+-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+ -| ``/admin/user`` | 168.0.0.1 | symfony.com | POST | rule #2 (``ROLE_USER_HOST``) | The second rule still matches. This would also match the | -| | | | | | third rule (``ROLE_USER_METHOD``), but only the **first** | -| | | | | | matched ``access_control`` is used. | -+-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+ -| ``/admin/user`` | 168.0.0.1 | example.com | POST | rule #3 (``ROLE_USER_METHOD``) | The ``ip`` and ``host`` don't match the first two entries, | -| | | | | | but the third - ``ROLE_USER_METHOD`` - matches and is used. | -+-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+ -| ``/admin/user`` | 168.0.0.1 | example.com | GET | rule #4 (``ROLE_USER``) | The ``ip``, ``host`` and ``method`` prevent the first | -| | | | | | three entries from matching. But since the URI matches the | -| | | | | | ``path`` pattern of the ``ROLE_USER`` entry, it is used. | -+-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+ -| ``/foo`` | 127.0.0.1 | symfony.com | POST | matches no entries | This doesn't match any ``access_control`` rules, since its | -| | | | | | URI doesn't match any of the ``path`` values. | -+-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+ - -.. _security-book-access-control-enforcement-options: - -2. Access Enforcement -..................... - -Once Symfony has decided which ``access_control`` entry matches (if any), -it then *enforces* access restrictions based on the ``roles``, ``allow_if`` and ``requires_channel`` -options: - -* ``role`` If the user does not have the given role(s), then access is denied - (internally, an :class:`Symfony\\Component\\Security\\Core\\Exception\\AccessDeniedException` - is thrown); - -* ``allow_if`` If the expression returns false, then access is denied; - -* ``requires_channel`` If the incoming request's channel (e.g. ``http``) - does not match this value (e.g. ``https``), the user will be redirected - (e.g. redirected from ``http`` to ``https``, or vice versa). - -.. tip:: - - If access is denied, the system will try to authenticate the user if not - already (e.g. redirect the user to the login page). If the user is already - logged in, the 403 "access denied" error page will be shown. See - :doc:`/cookbook/controller/error_pages` for more information. - -.. _book-security-securing-ip: - -Securing by IP -~~~~~~~~~~~~~~ - -Certain situations may arise when you may need to restrict access to a given -path based on IP. This is particularly relevant in the case of -:ref:`Edge Side Includes ` (ESI), for example. When ESI is -enabled, it's recommended to secure access to ESI URLs. Indeed, some ESI may -contain some private content like the current logged in user's information. To -prevent any direct access to these resources from a web browser (by guessing the -ESI URL pattern), the ESI route **must** be secured to be only visible from -the trusted reverse proxy cache. - -.. versionadded:: 2.3 - Version 2.3 allows multiple IP addresses in a single rule with the ``ips: [a, b]`` - construct. Prior to 2.3, users should create one rule per IP address to match and - use the ``ip`` key instead of ``ips``. - -.. caution:: - - As you'll read in the explanation below the example, the ``ip`` option - does not restrict to a specific IP address. Instead, using the ``ip`` - key means that the ``access_control`` entry will only match this IP address, - and users accessing it from a different IP address will continue down - the ``access_control`` list. - -Here is an example of how you might secure all ESI routes that start with a -given prefix, ``/esi``, from outside access: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/security.yml - security: - # ... - access_control: - - { path: ^/esi, roles: IS_AUTHENTICATED_ANONYMOUSLY, ips: [127.0.0.1, ::1] } - - { path: ^/esi, roles: ROLE_NO_ACCESS } - - .. code-block:: xml - - - - - - - - - - + + + + @@ -1024,101 +171,38 @@ given prefix, ``/esi``, from outside access: // app/config/security.php $container->loadFromExtension('security', array( // ... - 'access_control' => array( - array( - 'path' => '^/esi', - 'role' => 'IS_AUTHENTICATED_ANONYMOUSLY', - 'ips' => '127.0.0.1, ::1' - ), - array( - 'path' => '^/esi', - 'role' => 'ROLE_NO_ACCESS' + 'firewalls' => array( + // ... + 'default' => array( + 'anonymous' => null, + 'http_basic' => null, ), ), )); -Here is how it works when the path is ``/esi/something`` coming from the -``10.0.0.1`` IP: - -* The first access control rule is ignored as the ``path`` matches but the - ``ip`` does not match either of the IPs listed; - -* The second access control rule is enabled (the only restriction being the - ``path`` and it matches): as the user cannot have the ``ROLE_NO_ACCESS`` - role as it's not defined, access is denied (the ``ROLE_NO_ACCESS`` role can - be anything that does not match an existing role, it just serves as a trick - to always deny access). - -Now, if the same request comes from ``127.0.0.1`` or ``::1`` (the IPv6 loopback -address): - -* Now, the first access control rule is enabled as both the ``path`` and the - ``ip`` match: access is allowed as the user always has the - ``IS_AUTHENTICATED_ANONYMOUSLY`` role. +Simple! To try this, you need to require the user to be logged in to see +a page. To make things interesting, create a new page at ``/admin``. For +example, if you use annotations, create something like this:: -* The second access rule is not examined as the first rule matched. - -.. _book-security-allow-if: - -Securing by an Expression -~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. versionadded:: 2.4 - The ``allow_if`` functionality was introduced in Symfony 2.4. - -Once an ``access_control`` entry is matched, you can deny access via the -``roles`` key or use more complex logic with an expression in the ``allow_if`` -key: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/security.yml - security: - # ... - access_control: - - - path: ^/_internal/secure - allow_if: "'127.0.0.1' == request.getClientIp() or has_role('ROLE_ADMIN')" - - .. code-block:: xml - - - - - - .. code-block:: php - - 'access_control' => array( - array( - 'path' => '^/_internal/secure', - 'allow_if' => '"127.0.0.1" == request.getClientIp() or has_role("ROLE_ADMIN")', - ), - ), - -In this case, when the user tries to access any URL starting with ``/_internal/secure``, -they will only be granted access if the IP address is ``127.0.0.1`` or if -the user has the ``ROLE_ADMIN`` role. - -Inside the expression, you have access to a number of different variables -and functions including ``request``, which is the Symfony -:class:`Symfony\\Component\\HttpFoundation\\Request` object (see -:ref:`component-http-foundation-request`). - -For a list of the other functions and variables, see -:ref:`functions and variables `. + // src/AppBundle/Controller/DefaultController.php + // ... -.. _book-security-securing-channel: + use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; + use Symfony\Component\HttpFoundation\Response; -Forcing a Channel (http, https) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + class DefaultController extends Controller + { + /** + * @Route("/admin") + */ + public function adminAction() + { + return new Response('Admin page!'); + } + } -You can also require a user to access a URL via SSL; just use the -``requires_channel`` argument in any ``access_control`` entries. If this -``access_control`` is matched and the request is using the ``http`` channel, -the user will be redirected to ``https``: +Next, add an ``access_control`` entry to ``security.yml`` that requires the +user to be logged in to access this URL: .. configuration-block:: @@ -1127,8 +211,12 @@ the user will be redirected to ``https``: # app/config/security.yml security: # ... + firewalls: + # ... + access_control: - - { path: ^/cart/checkout, roles: IS_AUTHENTICATED_ANONYMOUSLY, requires_channel: https } + # require ROLE_ADMIN for /admin* + - { path: ^/admin, roles: ROLE_ADMIN } .. code-block:: xml @@ -1140,119 +228,72 @@ the user will be redirected to ``https``: xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> - - - + + + + + + + + + + + + .. code-block:: php // app/config/security.php $container->loadFromExtension('security', array( - 'access_control' => array( - array( - 'path' => '^/cart/checkout', - 'role' => 'IS_AUTHENTICATED_ANONYMOUSLY', - 'requires_channel' => 'https', + // ... + 'firewalls' => array( + // ... + 'default' => array( + // ... ), ), + 'access_control' => array( + // require ROLE_ADMIN for /admin* + array('path' => '^/admin', 'role' => 'ROLE_ADMIN'), + ), )); -.. _book-security-securing-controller: - -Securing a Controller -~~~~~~~~~~~~~~~~~~~~~ - -Protecting your application based on URL patterns is easy, but may not be -fine-grained enough in certain cases. When necessary, you can easily force -authorization from inside a controller:: - - // ... - use Symfony\Component\Security\Core\Exception\AccessDeniedException; - - public function helloAction($name) - { - if (false === $this->get('security.context')->isGranted('ROLE_ADMIN')) { - throw new AccessDeniedException(); - } - - // ... - } - -.. _book-security-securing-controller-annotations: - -Thanks to the SensioFrameworkExtraBundle, you can also secure your controller using annotations:: - - // ... - use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security; - - /** - * @Security("has_role('ROLE_ADMIN')") - */ - public function helloAction($name) - { - // ... - } - -For more information, see the -:doc:`FrameworkExtraBundle documentation `. - -Securing other Services -~~~~~~~~~~~~~~~~~~~~~~~ - -In fact, anything in Symfony can be protected using a strategy similar to -the one seen in the previous section. For example, suppose you have a service -(i.e. a PHP class) whose job is to send emails from one user to another. -You can restrict use of this class - no matter where it's being used from - -to users that have a specific role. - -For more information on how you can use the Security component to secure -different services and methods in your application, see :doc:`/cookbook/security/securing_services`. +.. note:: -Access Control Lists (ACLs): Securing Individual Database Objects -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + You'll learn more about this ``ROLE_ADMIN`` thing and denying access + later in the :ref:`security-authorization` section. -Imagine you are designing a blog system where your users can comment on your -posts. Now, you want a user to be able to edit their own comments, but not -those of other users. Also, as the admin user, you yourself want to be able -to edit *all* comments. +Great! Now, if you go to ``/admin``, you'll see the HTTP Basic popup: -The Security component comes with an optional access control list (ACL) system -that you can use when you need to control access to individual instances -of an object in your system. *Without* ACL, you can secure your system so that -only certain users can edit blog comments in general. But *with* ACL, you -can restrict or allow access on a comment-by-comment basis. +.. image:: /images/book/security_http_basic_popup.png + :align: center -For more information, see the cookbook article: :doc:`/cookbook/security/acl`. +But who can you login as? Where do users come from? -Users ------ +.. _book-security-form-login: -In the previous sections, you learned how you can protect different resources -by requiring a set of *roles* for a resource. This section explores -the other side of authorization: users. +.. tip:: -Where do Users Come from? (*User Providers*) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Want to use a traditional login form? Great! See :doc:`/cookbook/security/form_login_setup`. + What other methods are supported? See the :doc:`Configuration Reference ` + or :doc:`build your own `. -During authentication, the user submits a set of credentials (usually a username -and password). The job of the authentication system is to match those credentials -against some pool of users. So where does this list of users come from? +.. _security-user-providers: +.. _where-do-users-come-from-user-providers: -In Symfony, users can come from anywhere - a configuration file, a database -table, a web service, or anything else you can dream up. Anything that provides -one or more users to the authentication system is known as a "user provider". -Symfony comes standard with the two most common user providers: one that -loads users from a configuration file and one that loads users from a database -table. +B) Configuring how Users are Loaded +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Specifying Users in a Configuration File -........................................ +When you type in your username, Symfony needs to load that user's information +from somewhere. This is called a "user provider", and you're in charge of +configuring it. Symfony has a built-in way to +:doc:`load users from the database `, +or you can :doc:`create your own user provider `. -The easiest way to specify your users is directly in a configuration file. -In fact, you've seen this already in the example in this chapter. +The easiest (but most limited) way, is to configure Symfony to load hardcoded +users directly from the ``security.yml`` file itself. This is called an "in memory" +provider, but it's better to think of it as an "in configuration" provider: .. configuration-block:: @@ -1260,13 +301,17 @@ In fact, you've seen this already in the example in this chapter. # app/config/security.yml security: - # ... providers: - default_provider: + in_memory: memory: users: - ryan: { password: ryanpass, roles: 'ROLE_USER' } - admin: { password: kitten, roles: 'ROLE_ADMIN' } + ryan: + password: ryanpass + roles: 'ROLE_USER' + admin: + password: kitten + roles: 'ROLE_ADMIN' + # ... .. code-block:: xml @@ -1279,13 +324,13 @@ In fact, you've seen this already in the example in this chapter. http://symfony.com/schema/dic/services/services-1.0.xsd"> - - + + @@ -1293,9 +338,8 @@ In fact, you've seen this already in the example in this chapter. // app/config/security.php $container->loadFromExtension('security', array( - // ... 'providers' => array( - 'default_provider' => array( + 'in_memory' => array( 'memory' => array( 'users' => array( 'ryan' => array( @@ -1310,84 +354,101 @@ In fact, you've seen this already in the example in this chapter. ), ), ), + // ... )); -This user provider is called the "in-memory" user provider, since the users -aren't stored anywhere in a database. The actual user object is provided -by Symfony (:class:`Symfony\\Component\\Security\\Core\\User\\User`). +Like with ``firewalls``, you can have multiple ``providers``, but you'll +probably only need one. If you *do* have multiple, you can configure which +*one* provider to use for your firewall under its ``provider`` key (e.g. +``provider: in_memory``). -.. tip:: +Try to login using username ``admin`` and password ``kitten``. You should +see an error! - Any user provider can load users directly from configuration by specifying - the ``users`` configuration parameter and listing the users beneath it. + No encoder has been configured for account "Symfony\Component\Security\Core\User\User" -.. caution:: +To fix this, add an ``encoders`` key: - If your username is completely numeric (e.g. ``77``) or contains a dash - (e.g. ``user-name``), you should use an alternative syntax when specifying - users in YAML: +.. configuration-block:: .. code-block:: yaml - users: - - { name: 77, password: pass, roles: 'ROLE_USER' } - - { name: user-name, password: pass, roles: 'ROLE_USER' } + # app/config/security.yml + security: + # ... + + encoders: + Symfony\Component\Security\Core\User\User: plaintext + # ... -For smaller sites, this method is quick and easy to setup. For more complex -systems, you'll want to load your users from the database. + .. code-block:: xml -.. _book-security-user-entity: + + + -Loading Users from the Database -............................... + + -If you'd like to load your users via the Doctrine ORM, you can easily do -this by creating a ``User`` class and configuring the ``entity`` provider. + + + + -.. tip:: + .. code-block:: php + + // app/config/security.php + $container->loadFromExtension('security', array( + // ... + + 'encoders' => array( + 'Symfony\Component\Security\Core\User\User' => 'plaintext', + ), + // ... + )); - A high-quality open source bundle is available that allows your users - to be stored in a database. Read more about the `FOSUserBundle`_ - on GitHub. +User providers load user information and put it into a ``User`` object. If +you :doc:`load users from the database ` +or :doc:`some other source `, you'll +use your own custom User class. But when you use the "in memory" provider, +it gives you a ``Symfony\Component\Security\Core\User\User`` object. -With this approach, you'll first create your own ``User`` class, which will -be stored in the database. +Whatever your User class is, you need to tell Symfony what algorithm was +used to encode the passwords. In this case, the passwords are just plaintext, +but in a second, you'll change this to use ``bcrypt``. -.. code-block:: php +If you refresh now, you'll be logged in! The web debug toolbar even tells +you who you are and what roles you have: - // src/Acme/UserBundle/Entity/User.php - namespace Acme\UserBundle\Entity; +.. image:: /images/book/symfony_loggedin_wdt.png + :align: center - use Symfony\Component\Security\Core\User\UserInterface; - use Doctrine\ORM\Mapping as ORM; +Because this URL requires ``ROLE_ADMIN``, if you had logged in as ``ryan``, +this would deny you access. More on that later (:ref:`security-authorization-access-control`). - /** - * @ORM\Entity - */ - class User implements UserInterface - { - /** - * @ORM\Column(type="string", length=255) - */ - protected $username; +.. _book-security-user-entity: - // ... - } +Loading Users from the Database +............................... -As far as the security system is concerned, the only requirement for your -custom user class is that it implements the :class:`Symfony\\Component\\Security\\Core\\User\\UserInterface` -interface. This means that your concept of a "user" can be anything, as long -as it implements this interface. +If you'd like to load your users via the Doctrine ORM, that's easy! See +:doc:`/cookbook/security/entity_provider` for all the details. -.. note:: +.. _book-security-encoding-user-password: +.. _c-encoding-the-users-password: +.. _encoding-the-user-s-password: - The user object will be serialized and saved in the session during requests, - therefore it is recommended that you `implement the \Serializable interface`_ - in your user object. This is especially important if your ``User`` class - has a parent class with private properties. +C) Encoding the User's Password +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Next, configure an ``entity`` user provider, and point it to your ``User`` -class: +Whether your users are stored in ``security.yml``, in a database or somewhere +else, you'll want to encode their passwords. The best algorithm to use is +``bcrypt``: .. configuration-block:: @@ -1395,11 +456,12 @@ class: # app/config/security.yml security: - providers: - main: - entity: - class: Acme\UserBundle\Entity\User - property: username + # ... + + encoders: + Symfony\Component\Security\Core\User\User: + algorithm: bcrypt + cost: 12 .. code-block:: xml @@ -1412,9 +474,13 @@ class: http://symfony.com/schema/dic/services/services-1.0.xsd"> - - - + + + + + @@ -1422,39 +488,22 @@ class: // app/config/security.php $container->loadFromExtension('security', array( - 'providers' => array( - 'main' => array( - 'entity' => array( - 'class' => 'Acme\UserBundle\Entity\User', - 'property' => 'username', - ), - ), + // ... + + 'encoders' => array( + 'Symfony\Component\Security\Core\User\User' => array( + 'algorithm' => 'plaintext', + 'cost' => 12, + ) ), + // ... )); -With the introduction of this new provider, the authentication system will -attempt to load a ``User`` object from the database by using the ``username`` -field of that class. - -.. note:: - This example is just meant to show you the basic idea behind the ``entity`` - provider. For a full working example, see :doc:`/cookbook/security/entity_provider`. - -For more information on creating your own custom provider (e.g. if you needed -to load users via a web service), see :doc:`/cookbook/security/custom_provider`. - -.. _book-security-encoding-user-password: - -Encoding the User's Password -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. include:: /cookbook/security/_ircmaxwell_password-compat.rst.inc -So far, for simplicity, all the examples have stored the users' passwords -in plain text (whether those users are stored in a configuration file or in -a database somewhere). Of course, in a real application, you'll want to encode -your users' passwords for security reasons. This is easily accomplished by -mapping your User class to one of several built-in "encoders". For example, -to store your users in memory, but obscure their passwords via ``bcrypt``, -do the following: +Of course, your user's passwords now need to be encoded with this exact algorithm. +For hardcoded users, you can use an `online tool`_, which will give you something +like this: .. configuration-block:: @@ -1463,22 +512,18 @@ do the following: # app/config/security.yml security: # ... + providers: in_memory: memory: users: ryan: - password: $2a$12$w/aHvnC/XNeDVrrl65b3dept8QcKqpADxUlbraVXXsC03Jam5hvoO + password: $2a$12$LCY0MefVIEc3TYPHV9SNnuzOfyr2p/AXIGoQJEDs4am4JwhNz/jli roles: 'ROLE_USER' admin: - password: $2a$12$HmOsqRDJK0HuMDQ5Fb2.AOLMQHyNHGD0seyjU3lEVusjT72QQEIpW + password: $2a$12$cyTWeE9kpq1PjqKFiWUZFuCRPwVyAZwm4XzMZ1qPUFl7/flCM3V0G roles: 'ROLE_ADMIN' - encoders: - Symfony\Component\Security\Core\User\User: - algorithm: bcrypt - cost: 12 - .. code-block:: xml @@ -1490,24 +535,13 @@ do the following: http://symfony.com/schema/dic/services/services-1.0.xsd"> - - - + + - - + @@ -1515,203 +549,137 @@ do the following: // app/config/security.php $container->loadFromExtension('security', array( - // ... 'providers' => array( 'in_memory' => array( 'memory' => array( 'users' => array( 'ryan' => array( - 'password' => '$2a$12$w/aHvnC/XNeDVrrl65b3dept8QcKqpADxUlbraVXXsC03Jam5hvoO', + 'password' => '$2a$12$LCY0MefVIEc3TYPHV9SNnuzOfyr2p/AXIGoQJEDs4am4JwhNz/jli', 'roles' => 'ROLE_USER', ), 'admin' => array( - 'password' => '$2a$12$HmOsqRDJK0HuMDQ5Fb2.AOLMQHyNHGD0seyjU3lEVusjT72QQEIpW', + 'password' => '$2a$12$cyTWeE9kpq1PjqKFiWUZFuCRPwVyAZwm4XzMZ1qPUFl7/flCM3V0G', 'roles' => 'ROLE_ADMIN', ), ), ), ), ), - 'encoders' => array( - 'Symfony\Component\Security\Core\User\User' => array( - 'algorithm' => 'bcrypt', - 'iterations' => 12, - ), - ), + // ... )); -.. versionadded:: 2.2 - The BCrypt encoder was introduced in Symfony 2.2. - -You can now calculate the hashed password either programmatically -(e.g. ``password_hash('ryanpass', PASSWORD_BCRYPT, array('cost' => 12));``) -or via some online tool. - -.. include:: /cookbook/security/_ircmaxwell_password-compat.rst.inc - -Supported algorithms for this method depend on your PHP version. A full list -is available by calling the PHP function :phpfunction:`hash_algos`. - -.. versionadded:: 2.2 - As of Symfony 2.2 you can also use the :ref:`PBKDF2 ` - password encoder. +Everything will now work exactly like before. But if you have dynamic users +(e.g. from a database), how can you programmatically encode the password +before inserting them into the database? Don't worry, see +:ref:`security-encoding-password` for details. -Determining the Hashed Password -............................... +.. tip:: -If you're storing users in the database and you have some sort of registration -form for users, you'll need to be able to determine the hashed password so -that you can set it on your user before inserting it. No matter what algorithm -you configure for your user object, the hashed password can always be determined -in the following way from a controller:: + Supported algorithms for this method depend on your PHP version, but + include the algorithms returned by the PHP function :phpfunction:`hash_algos` + as well as a few others (e.g. bcrypt). See the ``encoders`` key in the + :doc:`Security Reference Section ` + for examples. - $factory = $this->get('security.encoder_factory'); - $user = new Acme\UserBundle\Entity\User(); + It's also possible to use different hashing algorithms on a user-by-user + basis. See :doc:`/cookbook/security/named_encoders` for more details. - $encoder = $factory->getEncoder($user); - $password = $encoder->encodePassword('ryanpass', $user->getSalt()); - $user->setPassword($password); +D) Configuration Done! +~~~~~~~~~~~~~~~~~~~~~~ -In order for this to work, just make sure that you have the encoder for your -user class (e.g. ``Acme\UserBundle\Entity\User``) configured under the ``encoders`` -key in ``app/config/security.yml``. +Congratulations! You now have a working authentication system that uses Http +Basic and loads users right from the ``security.yml`` file. -.. caution:: +Your next steps depend on your setup: - When you allow a user to submit a plaintext password (e.g. registration - form, change password form), you *must* have validation that guarantees - that the password is 4096 characters or less. Read more details in - :ref:`How to implement a simple Registration Form `. +* Configure a different way for your users to login, like a :ref:`login form ` + or :doc:`something completely custom `; -Retrieving the User Object -~~~~~~~~~~~~~~~~~~~~~~~~~~ +* Load users from a different source, like the :doc:`database ` + or :doc:`some other source `; -After authentication, the ``User`` object of the current user can be accessed -via the ``security.context`` service. From inside a controller, this will -look like:: +* Learn how to deny access, load the User object and deal with roles in the + :ref:`Authorization ` section. - public function indexAction() - { - $user = $this->get('security.context')->getToken()->getUser(); - } +.. _`security-authorization`: -In a controller this can be shortcut to: +2) Denying Access, Roles and other Authorization +------------------------------------------------ -.. code-block:: php +Users can now login to your app using ``http_basic`` or some other method. +Great! Now, you need to learn how to deny access and work with the User object. +This is called **authorization**, and its job is to decide if a user can +access some resource (a URL, a model object, a method call, ...). - public function indexAction() - { - $user = $this->getUser(); - } +The process of authorization has two different sides: -.. note:: +#. The user receives a specific set of roles when logging in (e.g. ``ROLE_ADMIN``). +#. You add code so that a resource (e.g. URL, controller) requires a specific + "attribute" (most commonly a role like ``ROLE_ADMIN``) in order to be + accessed. - Anonymous users are technically authenticated, meaning that the ``isAuthenticated()`` - method of an anonymous user object will return true. To check if your - user is actually authenticated, check for the ``IS_AUTHENTICATED_FULLY`` - role. +.. tip:: -In a Twig Template this object can be accessed via the ``app.user`` key, -which calls the :method:`GlobalVariables::getUser() ` -method: + In addition to roles (e.g. ``ROLE_ADMIN``), you can protect a resource + using other attributes/strings (e.g. ``EDIT``) and use voters or Symfony's + ACL system to give these meaning. This might come in handy if you need + to check if user A can "EDIT" some object B (e.g. a Product with id 5). + See :ref:`security-secure-objects`. -.. configuration-block:: +.. _book-security-roles: - .. code-block:: html+jinja +Roles +~~~~~ -

Username: {{ app.user.username }}

+When a user logs in, they receive a set of roles (e.g. ``ROLE_ADMIN``). In +the example above, these are hardcoded into ``security.yml``. If you're +loading users from the database, these are probably stored on a column +in your table. - .. code-block:: html+php +.. caution:: -

Username: getUser()->getUsername() ?>

+ All roles you assign to a user **must** begin with the ``ROLE_`` prefix. + Otherwise, they won't be handled by Symfony's security system in the + normal way (i.e. unless you're doing something advanced, assigning a + role like ``FOO`` to a user and then checking for ``FOO`` as described + :ref:`below ` will not work). -Using multiple User Providers -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Roles are simple, and are basically strings that you invent and use as needed. +For example, if you need to start limiting access to the blog admin section +of your website, you could protect that section using a ``ROLE_BLOG_ADMIN`` +role. This role doesn't need to be defined anywhere - you can just start using +it. -Each authentication mechanism (e.g. HTTP Authentication, form login, etc) -uses exactly one user provider, and will use the first declared user provider -by default. But what if you want to specify a few users via configuration -and the rest of your users in the database? This is possible by creating -a new provider that chains the two together: +.. tip:: -.. configuration-block:: + Make sure every user has at least *one* role, or your user will look + like they're not authenticated. A common convention is to give *every* + user ``ROLE_USER``. - .. code-block:: yaml +You can also specify a :ref:`role hierarchy ` where +some roles automatically mean that you also have other roles. - # app/config/security.yml - security: - providers: - chain_provider: - chain: - providers: [in_memory, user_db] - in_memory: - memory: - users: - foo: { password: test } - user_db: - entity: { class: Acme\UserBundle\Entity\User, property: username } +.. _security-role-authorization: - .. code-block:: xml +Add Code to Deny Access +~~~~~~~~~~~~~~~~~~~~~~~ - - - +There are **two** ways to deny access to something: - - - - in_memory - user_db - - - - - - - - - - - - +1) :ref:`access_control in security.yml ` + allows you to protect URL patterns (e.g. ``/admin/*``). This is easy, + but less flexible; - .. code-block:: php +2) :ref:`in your code via the security.authorization_checker service `. - // app/config/security.php - $container->loadFromExtension('security', array( - 'providers' => array( - 'chain_provider' => array( - 'chain' => array( - 'providers' => array('in_memory', 'user_db'), - ), - ), - 'in_memory' => array( - 'memory' => array( - 'users' => array( - 'foo' => array('password' => 'test'), - ), - ), - ), - 'user_db' => array( - 'entity' => array( - 'class' => 'Acme\UserBundle\Entity\User', - 'property' => 'username', - ), - ), - ), - )); +.. _security-authorization-access-control: -Now, all authentication mechanisms will use the ``chain_provider``, since -it's the first specified. The ``chain_provider`` will, in turn, try to load -the user from both the ``in_memory`` and ``user_db`` providers. +Securing URL patterns (access_control) +...................................... -You can also configure the firewall or individual authentication mechanisms -to use a specific provider. Again, unless a provider is specified explicitly, -the first provider is always used: +The most basic way to secure part of your application is to secure an entire +URL pattern. You saw this earlier, where anything matching the regular expression +``^/admin`` requires the ``ROLE_ADMIN`` role: .. configuration-block:: @@ -1719,15 +687,13 @@ the first provider is always used: # app/config/security.yml security: - firewalls: - secured_area: - # ... - pattern: ^/ - provider: user_db - http_basic: - realm: "Secured Demo Area" - provider: in_memory - form_login: ~ + # ... + firewalls: + # ... + + access_control: + # require ROLE_ADMIN for /admin* + - { path: ^/admin, roles: ROLE_ADMIN } .. code-block:: xml @@ -1740,11 +706,16 @@ the first provider is always used: http://symfony.com/schema/dic/services/services-1.0.xsd"> - + + + - - + + + + + @@ -1752,56 +723,27 @@ the first provider is always used: // app/config/security.php $container->loadFromExtension('security', array( + // ... 'firewalls' => array( - 'secured_area' => array( + // ... + 'default' => array( // ... - 'pattern' => '^/', - 'provider' => 'user_db', - 'http_basic' => array( - // ... - 'provider' => 'in_memory', - ), - 'form_login' => array(), ), ), + 'access_control' => array( + // require ROLE_ADMIN for /admin* + array('path' => '^/admin', 'role' => 'ROLE_ADMIN'), + ), )); -In this example, if a user tries to log in via HTTP authentication, the authentication -system will use the ``in_memory`` user provider. But if the user tries to -log in via the form login, the ``user_db`` provider will be used (since it's -the default for the firewall as a whole). - -For more information about user provider and firewall configuration, see -the :doc:`/reference/configuration/security`. +This is great for securing entire sections, but you'll also probably want +to :ref:`secure your individual controllers ` +as well. -.. _book-security-roles: - -Roles ------ - -The idea of a "role" is key to the authorization process. Each user is assigned -a set of roles and then each resource requires one or more roles. If the user -has any one of the required roles, access is granted. Otherwise access is denied. - -Roles are pretty simple, and are basically strings that you can invent and -use as needed (though roles are objects internally). For example, if you -need to start limiting access to the blog admin section of your website, -you could protect that section using a ``ROLE_BLOG_ADMIN`` role. This role -doesn't need to be defined anywhere - you can just start using it. - -.. note:: - - All roles **must** begin with the ``ROLE_`` prefix to be managed by - Symfony. If you define your own roles with a dedicated ``Role`` class - (more advanced), don't use the ``ROLE_`` prefix. - -.. _book-security-role-hierarchy: - -Hierarchical Roles -~~~~~~~~~~~~~~~~~~ - -Instead of associating many roles to users, you can define role inheritance -rules by creating a role hierarchy: +You can define as many URL patterns as you need - each is a regular expression. +**BUT**, only **one** will be matched. Symfony will look at each starting +at the top, and stop as soon as it finds one ``access_control`` entry that +matches the URL. .. configuration-block:: @@ -1809,9 +751,10 @@ rules by creating a role hierarchy: # app/config/security.yml security: - role_hierarchy: - ROLE_ADMIN: ROLE_USER - ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH] + # ... + access_control: + - { path: ^/admin/users, roles: ROLE_SUPER_ADMIN } + - { path: ^/admin, roles: ROLE_ADMIN } .. code-block:: xml @@ -1824,8 +767,11 @@ rules by creating a role hierarchy: http://symfony.com/schema/dic/services/services-1.0.xsd"> - ROLE_USER - ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH + + + + + @@ -1833,151 +779,84 @@ rules by creating a role hierarchy: // app/config/security.php $container->loadFromExtension('security', array( - 'role_hierarchy' => array( - 'ROLE_ADMIN' => 'ROLE_USER', - 'ROLE_SUPER_ADMIN' => array( - 'ROLE_ADMIN', - 'ROLE_ALLOWED_TO_SWITCH', - ), + // ... + 'access_control' => array( + array('path' => '^/admin/users', 'role' => 'ROLE_SUPER_ADMIN'), + array('path' => '^/admin', 'role' => 'ROLE_ADMIN'), ), )); -In the above configuration, users with ``ROLE_ADMIN`` role will also have the -``ROLE_USER`` role. The ``ROLE_SUPER_ADMIN`` role has ``ROLE_ADMIN``, ``ROLE_ALLOWED_TO_SWITCH`` -and ``ROLE_USER`` (inherited from ``ROLE_ADMIN``). +Prepending the path with ``^`` means that only URLs *beginning* with the +pattern are matched. For example, a path of simply ``/admin`` (without +the ``^``) would match ``/admin/foo`` but would also match URLs like ``/foo/admin``. + +.. _security-book-access-control-explanation: -Access Control --------------- +.. sidebar:: Understanding how ``access_control`` Works -Now that you have a User and Roles, you can go further than URL-pattern based -authorization. + The ``access_control`` section is very powerful, but it can also be dangerous + (because it involves security) if you don't understand *how* it works. + In addition to the URL, the ``access_control`` can match on IP address, + host name and HTTP methods. It can also be used to redirect a user to + the ``https`` version of a URL pattern. -Access Control in Controllers -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + To learn about all of this, see :doc:`/cookbook/security/access_control`. -Protecting your application based on URL patterns is easy, but may not be -fine-grained enough in certain cases. When necessary, you can easily force -authorization from inside a controller:: +.. _`book-security-securing-controller`: + +Securing Controllers and other Code +................................... + +You can easily deny access from inside a controller:: // ... - use Symfony\Component\Security\Core\Exception\AccessDeniedException; public function helloAction($name) { - if (false === $this->get('security.context')->isGranted('ROLE_ADMIN')) { - throw new AccessDeniedException(); + if (false === $this->get('security.authorization_checker')->isGranted('ROLE_ADMIN')) { + throw $this->createAccessDeniedException(); } // ... } -.. caution:: +.. versionadded:: 2.6 + The ``security.authorization_checker`` service was introduced in Symfony 2.6. Prior + to Symfony 2.6, you had to use the ``isGranted()`` method of the ``security.context`` service. - A firewall must be active or an exception will be thrown when the ``isGranted()`` - method is called. It's almost always a good idea to have a main firewall that - covers all URLs (as is shown in this chapter). +.. versionadded:: 2.5 + The ``createAccessDeniedException`` method was introduced in Symfony 2.5. -.. _book-security-expressions: +The :method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::createAccessDeniedException` +method creates a special :class:`Symfony\\Component\\Security\\Core\\Exception\\AccessDeniedException` +object, which ultimately triggers a 403 HTTP response inside Symfony. -Complex Access Controls with Expressions ----------------------------------------- +That's it! If the user isn't logged in yet, they will be asked to login (e.g. +redirected to the login page). If they *are* logged in, they'll be shown +the 403 access denied page (which you can :ref:`customize `). -.. versionadded:: 2.4 - The expression functionality was introduced in Symfony 2.4. +.. _book-security-securing-controller-annotations: -In addition to a role like ``ROLE_ADMIN``, the ``isGranted`` method also -accepts an :class:`Symfony\\Component\\ExpressionLanguage\\Expression` object:: +Thanks to the SensioFrameworkExtraBundle, you can also secure your controller +using annotations:: - use Symfony\Component\Security\Core\Exception\AccessDeniedException; - use Symfony\Component\ExpressionLanguage\Expression; // ... + use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security; - public function indexAction() + /** + * @Security("has_role('ROLE_ADMIN')") + */ + public function helloAction($name) { - if (!$this->get('security.context')->isGranted(new Expression( - '"ROLE_ADMIN" in roles or (user and user.isSuperAdmin())' - ))) { - throw new AccessDeniedException(); - } - // ... } -In this example, if the current user has ``ROLE_ADMIN`` or if the current -user object's ``isSuperAdmin()`` method returns ``true``, then access will -be granted (note: your User object may not have an ``isSuperAdmin`` method, -that method is invented for this example). - -This uses an expression and you can learn more about the expression language -syntax, see :doc:`/components/expression_language/syntax`. - -.. _book-security-expression-variables: - -Inside the expression, you have access to a number of variables: - -* ``user`` The user object (or the string ``anon`` if you're not authenticated); -* ``roles`` The array of roles the user has, including from the - :ref:`role hierarchy ` but not including - the ``IS_AUTHENTICATED_*`` attributes (see the functions below); -* ``object``: The object (if any) that's passed as the second argument to - ``isGranted`` ; -* ``token`` The token object; -* ``trust_resolver``: The :class:`Symfony\\Component\\Security\\Core\\Authentication\\AuthenticationTrustResolverInterface`, - object: you'll probably use the ``is_*`` functions below instead. - -Additionally, you have access to a number of functions inside the expression: - -* ``is_authenticated``: Returns ``true`` if the user is authenticated via "remember-me" - or authenticated "fully" - i.e. returns true if the user is "logged in"; -* ``is_anonymous``: Equal to using ``IS_AUTHENTICATED_ANONYMOUSLY`` with - the ``isGranted`` function; -* ``is_remember_me``: Similar, but not equal to ``IS_AUTHENTICATED_REMEMBERED``, - see below; -* ``is_fully_authenticated``: Similar, but not equal to ``IS_AUTHENTICATED_FULLY``, - see below; -* ``has_role``: Checks to see if the user has the given role - equivalent - to an expression like ``'ROLE_ADMIN' in roles``. - -.. sidebar:: ``is_remember_me`` is different than checking ``IS_AUTHENTICATED_REMEMBERED`` - - The ``is_remember_me`` and ``is_authenticated_fully`` functions are *similar* - to using ``IS_AUTHENTICATED_REMEMBERED`` and ``IS_AUTHENTICATED_FULLY`` - with the ``isGranted`` function - but they are **not** the same. The - following shows the difference:: - - use Symfony\Component\ExpressionLanguage\Expression; - // ... - - $sc = $this->get('security.context'); - $access1 = $sc->isGranted('IS_AUTHENTICATED_REMEMBERED'); - - $access2 = $sc->isGranted(new Expression( - 'is_remember_me() or is_fully_authenticated()' - )); - - Here, ``$access1`` and ``$access2`` will be the same value. Unlike the - behavior of ``IS_AUTHENTICATED_REMEMBERED`` and ``IS_AUTHENTICATED_FULLY``, - the ``is_remember_me`` function *only* returns true if the user is authenticated - via a remember-me cookie and ``is_fully_authenticated`` *only* returns - true if the user has actually logged in during this session (i.e. is - full-fledged). - -Access Control in Other Services -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -In fact, anything in Symfony can be protected using a strategy similar to -the one seen in the previous section. For example, suppose you have a service -(i.e. a PHP class) whose job is to send emails from one user to another. -You can restrict use of this class - no matter where it's being used from - -to users that have a specific role. - -For more information on how you can use the Security component to secure -different services and methods in your application, see :doc:`/cookbook/security/securing_services`. +For more information, see the `FrameworkExtraBundle documentation`_. .. _book-security-template: Access Control in Templates -~~~~~~~~~~~~~~~~~~~~~~~~~~~ +........................... If you want to check if the current user has a role inside a template, use the built-in helper function: @@ -1994,19 +873,73 @@ the built-in helper function: isGranted('ROLE_ADMIN')): ?> Delete - + -.. note:: +If you use this function and are *not* behind a firewall, an exception +will be thrown. Again, it's almost always a good +idea to have a main firewall that covers all URLs (as has been shown +in this chapter). + +.. caution:: - If you use this function and are *not* at a URL behind a firewall - active, an exception will be thrown. Again, it's almost always a good - idea to have a main firewall that covers all URLs (as has been shown - in this chapter). + Be careful with this in your layout or on your error pages! Because of + some internal Symfony details, to avoid broken error pages in the ``prod`` + environment, wrap calls in these templates with a check for ``app.user``: -.. _book-security-template-expression: + .. code-block:: html+jinja + + {% if app.user and is_granted('ROLE_ADMIN') %} + +Securing other Services +....................... + +In fact, anything in Symfony can be protected by doing something similar +to this. For example, suppose you have a service (i.e. a PHP class) whose +job is to send emails. You can restrict use of this class - no matter where +it's being used from - to only certain users. + +For more information see :doc:`/cookbook/security/securing_services`. + +Checking to see if a User is Logged In (IS_AUTHENTICATED_FULLY) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +So far, you've checked access based on roles - those strings that start with +``ROLE_`` and are assigned to users. But if you *only* want to check if a +user is logged in (you don't care about roles), then you can see ``IS_AUTHENTICATED_FULLY``:: + + // ... -.. versionadded:: 2.4 - The ``expression`` functionality was introduced in Symfony 2.4. + public function helloAction($name) + { + if (!$this->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_FULLY')) { + throw $this->createAccessDeniedException(); + } + + // ... + } + +.. tip:: + + You can of course also use this in ``access_control``. + +``IS_AUTHENTICATED_FULLY`` isn't a role, but it kind of acts like one, and every +user that has successfully logged in will have this. In fact, there are three +special attributes like this: + +* ``IS_AUTHENTICATED_REMEMBERED``: *All* logged in users have this, even + if they are logged in because of a "remember me cookie". Even if you don't + use the :doc:`remember me functionality `, + you can use this to check if the user is logged in. + +* ``IS_AUTHENTICATED_FULLY``: This is similar to ``IS_AUTHENTICATED_REMEMBERED``, + but stronger. Users who are logged in only because of a "remember me cookie" + will have ``IS_AUTHENTICATED_REMEMBERED`` but will not have ``IS_AUTHENTICATED_FULLY``. + +* ``IS_AUTHENTICATED_ANONYMOUSLY``: *All* users (even anonymous ones) have + this - this is useful when *whitelisting* URLs to guarantee access - some + details are in :doc:`/cookbook/security/access_control`. + +.. _book-security-template-expression: You can also use expressions inside your templates: @@ -2030,21 +963,113 @@ You can also use expressions inside your templates: For more details on expressions and security, see :ref:`book-security-expressions`. +.. _security-secure-objects: + Access Control Lists (ACLs): Securing individual Database Objects ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Imagine you are designing a blog system where your users can comment on your -posts. Now, you want a user to be able to edit their own comments, but not -those of other users. Also, as the admin user, you yourself want to be able -to edit *all* comments. +Imagine you are designing a blog where users can comment on your posts. You +also want a user to be able to edit their own comments, but not those of +other users. Also, as the admin user, you yourself want to be able to edit +*all* comments. + +To accomplish this you have 2 options: + +* :doc:`Voters ` allow you to + use business logic (e.g. the user can edit this post because they were + the creator) to determine access. You'll probably want this option - it's + flexible enough to solve the above situation. + +* :doc:`ACLs ` allow you to create a database structure + where you can assign *any* arbitrary user *any* access (e.g. EDIT, VIEW) + to *any* object in your system. Use this if you need an admin user to be + able to grant customized access across your system via some admin interface. + +In both cases, you'll still deny access using methods similar to what was +shown above. + +Retrieving the User Object +-------------------------- + +.. versionadded:: 2.6 + The ``security.token_storage`` service was introduced in Symfony 2.6. Prior + to Symfony 2.6, you had to use the ``getToken()`` method of the ``security.context`` service. + +After authentication, the ``User`` object of the current user can be accessed +via the ``security.token_storage`` service. From inside a controller, this will +look like:: + + public function indexAction() + { + if (!$this->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_FULLY')) { + throw $this->createAccessDeniedException(); + } + + $user = $this->getUser(); + + // the above is a shortcut for this + $user = $this->get('security.token_storage')->getToken()->getUser(); + } + +.. tip:: + + The user will be an object and the class of that object will depend on + your :ref:`user provider `. + +Now you can call whatever methods are on *your* User object. For example, +if your User object has a ``getFirstName()`` method, you could use that:: + + use Symfony\Component\HttpFoundation\Response; + + public function indexAction() + { + // ... + + return new Response('Well hi there '.$user->getFirstName()); + } + +Always Check if the User is Logged In +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +It's important to check if the user is authenticated first. If they're not, +``$user`` will either be ``null`` or the string ``anon.``. Wait, what? Yes, +this is a quirk. If you're not logged in, the user is technically the string +``anon.``, though the ``getUser()`` controller shortcut converts this to +``null`` for convenience. + +The point is this: always check to see if the user is logged in before using +the User object, and use the ``isGranted`` method (or +:ref:`access_control `) to do this:: -The Security component comes with an optional access control list (ACL) system -that you can use when you need to control access to individual instances -of an object in your system. *Without* ACL, you can secure your system so that -only certain users can edit blog comments in general. But *with* ACL, you -can restrict or allow access on a comment-by-comment basis. + // yay! Use this to see if the user is logged in + if (!$this->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_FULLY')) { + throw $this->createAccessDeniedException(); + } + + // boo :(. Never check for the User object to see if they're logged in + if ($this->getUser()) { + + } + +Retrieving the User in a Template +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In a Twig Template this object can be accessed via the `app.user `_ +key: + +.. configuration-block:: + + .. code-block:: html+jinja + + {% if is_granted('IS_AUTHENTICATED_FULLY') %} +

Username: {{ app.user.username }}

+ {% endif %} + + .. code-block:: html+php -For more information, see the cookbook article: :doc:`/cookbook/security/acl`. + isGranted('IS_AUTHENTICATED_FULLY')): ?> +

Username: getUser()->getUsername() ?>

+ .. _book-security-logging-out: @@ -2101,30 +1126,7 @@ the firewall can handle this automatically for you when you activate the // ... )); -Once this is configured under your firewall, sending a user to ``/logout`` -(or whatever you configure the ``path`` to be), will un-authenticate the -current user. The user will then be sent to the homepage (the value defined -by the ``target`` parameter). Both the ``path`` and ``target`` config parameters -default to what's specified here. In other words, unless you need to customize -them, you can omit them entirely and shorten your configuration: - -.. configuration-block:: - - .. code-block:: yaml - - logout: ~ - - .. code-block:: xml - - - - .. code-block:: php - - 'logout' => array(), - -Note that you will *not* need to implement a controller for the ``/logout`` -URL as the firewall takes care of everything. You *do*, however, need to create -a route so that you can use it to generate the URL: +Next, you'll need to create a route for this URL (but not a controller): .. configuration-block:: @@ -2157,10 +1159,106 @@ a route so that you can use it to generate the URL: return $collection; +And that's it! By sending a user to ``/logout`` (or whatever you configure +the ``path`` to be), Symfony will un-authenticate the current user. + Once the user has been logged out, they will be redirected to whatever path -is defined by the ``target`` parameter above (e.g. the ``homepage``). For -more information on configuring the logout, see the -:doc:`Security Configuration Reference `. +is defined by the ``target`` parameter above (e.g. the ``homepage``). + +.. tip:: + + If you need to do something more interesting after logging out, you can + specify a logout success handler by adding a ``success_handler`` key + and pointing it to a service id of a class that implements + :class:`Symfony\\Component\\Security\\Http\\Logout\\LogoutSuccessHandlerInterface`. + See :doc:`Security Configuration Reference `. + +.. _`security-encoding-password`: + +Dynamically Encoding a Password +------------------------------- + +If, for example, you're storing users in the database, you'll need to encode +the users' passwords before inserting them. No matter what algorithm you +configure for your user object, the hashed password can always be determined +in the following way from a controller:: + + // whatever *your* User object is + $user = new AppBundle\Entity\User(); + $plainPassword = 'ryanpass'; + $encoder = $this->container->get('security.password_encoder'); + $encoded = $encoder->encodePassword($user, $plainPassword); + + $user->setPassword($encoded); + +.. versionadded:: 2.6 + The ``security.password_encoder`` service was introduced in Symfony 2.6. + +In order for this to work, just make sure that you have the encoder for your +user class (e.g. ``AppBundle\Entity\User``) configured under the ``encoders`` +key in ``app/config/security.yml``. + +The ``$encoder`` object also has an ``isPasswordValid`` method, which takes +the ``User`` object as the first argument and the plain password to check +as the second argument. + +.. caution:: + + When you allow a user to submit a plaintext password (e.g. registration + form, change password form), you *must* have validation that guarantees + that the password is 4096 characters or fewer. Read more details in + :ref:`How to implement a simple Registration Form `. + +.. _security-role-hierarchy: + +Hierarchical Roles +------------------ + +Instead of associating many roles to users, you can define role inheritance +rules by creating a role hierarchy: + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/security.yml + security: + role_hierarchy: + ROLE_ADMIN: ROLE_USER + ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH] + + .. code-block:: xml + + + + + + + ROLE_USER + ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH + + + + .. code-block:: php + + // app/config/security.php + $container->loadFromExtension('security', array( + 'role_hierarchy' => array( + 'ROLE_ADMIN' => 'ROLE_USER', + 'ROLE_SUPER_ADMIN' => array( + 'ROLE_ADMIN', + 'ROLE_ALLOWED_TO_SWITCH', + ), + ), + )); + +In the above configuration, users with ``ROLE_ADMIN`` role will also have the +``ROLE_USER`` role. The ``ROLE_SUPER_ADMIN`` role has ``ROLE_ADMIN``, ``ROLE_ALLOWED_TO_SWITCH`` +and ``ROLE_USER`` (inherited from ``ROLE_ADMIN``). Stateless Authentication ------------------------ @@ -2214,92 +1312,62 @@ cookie will be ever created by Symfony): If you use a form login, Symfony will create a cookie even if you set ``stateless`` to ``true``. -Utilities ---------- - -.. versionadded:: 2.2 - The ``StringUtils`` and ``SecureRandom`` classes were introduced in Symfony - 2.2 - -The Symfony Security component comes with a collection of nice utilities related -to security. These utilities are used by Symfony, but you should also use -them if you want to solve the problem they address. - -Comparing Strings -~~~~~~~~~~~~~~~~~ - -The time it takes to compare two strings depends on their differences. This -can be used by an attacker when the two strings represent a password for -instance; it is known as a `Timing attack`_. - -Internally, when comparing two passwords, Symfony uses a constant-time -algorithm; you can use the same strategy in your own code thanks to the -:class:`Symfony\\Component\\Security\\Core\\Util\\StringUtils` class:: +.. _book-security-checking-vulnerabilities: - use Symfony\Component\Security\Core\Util\StringUtils; +Checking for Known Security Vulnerabilities in Dependencies +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // is password1 equals to password2? - $bool = StringUtils::equals($password1, $password2); +.. versionadded:: 2.5 + The ``security:check`` command was introduced in Symfony 2.5. This command is + included in ``SensioDistributionBundle``, which has to be registered in your + application in order to use this command. -Generating a secure random Number -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Whenever you need to generate a secure random number, you are highly -encouraged to use the Symfony -:class:`Symfony\\Component\\Security\\Core\\Util\\SecureRandom` class:: - - use Symfony\Component\Security\Core\Util\SecureRandom; - - $generator = new SecureRandom(); - $random = $generator->nextBytes(10); +When using lots of dependencies in your Symfony projects, some of them may +contain security vulnerabilities. That's why Symfony includes a command called +``security:check`` that checks your ``composer.lock`` file to find any known +security vulnerability in your installed dependencies: -The -:method:`Symfony\\Component\\Security\\Core\\Util\\SecureRandom::nextBytes` -methods returns a random string composed of the number of characters passed as -an argument (10 in the above example). +.. code-block:: bash -The SecureRandom class works better when OpenSSL is installed but when it's -not available, it falls back to an internal algorithm, which needs a seed file -to work correctly. Just pass a file name to enable it:: + $ php app/console security:check - $generator = new SecureRandom('/some/path/to/store/the/seed.txt'); - $random = $generator->nextBytes(10); +A good security practice is to execute this command regularly to be able to +update or replace compromised dependencies as soon as possible. Internally, +this command uses the public `security advisories database`_ published by the +FriendsOfPHP organization. -.. note:: +.. tip:: - You can also access a secure random instance directly from the Symfony - dependency injection container; its name is ``security.secure_random``. + The ``security:check`` command terminates with a non-zero exit code if + any of your dependencies is affected by a known security vulnerability. + Therefore, you can easily integrate it in your build process. Final Words ----------- -Security can be a deep and complex issue to solve correctly in your application. -Fortunately, Symfony's Security component follows a well-proven security -model based around *authentication* and *authorization*. Authentication, -which always happens first, is handled by a firewall whose job is to determine -the identity of the user through several different methods (e.g. HTTP authentication, -login form, etc). In the cookbook, you'll find examples of other methods -for handling authentication, including how to implement a "remember me" cookie -functionality. - -Once a user is authenticated, the authorization layer can determine whether -or not the user should have access to a specific resource. Most commonly, -*roles* are applied to URLs, classes or methods and if the current user -doesn't have that role, access is denied. The authorization layer, however, -is much deeper, and follows a system of "voting" so that multiple parties -can determine if the current user should have access to a given resource. -Find out more about this and other topics in the cookbook. +Woh! Nice work! You now know more than the basics of security. The hardest +parts are when you have custom requirements: like a custom authentication +strategy (e.g. API tokens), complex authorization logic and many other things +(because security is complex!). + +Fortunately, there are a lot of :doc:`Security Cookbook Articles ` +aimed at describing many of these situations. Also, see the +:doc:`Security Reference Section `. Many +of the options don't have specific details, but seeing the full possible +configuration tree may be useful. + +Good luck! Learn more from the Cookbook ---------------------------- * :doc:`Forcing HTTP/HTTPS ` * :doc:`Impersonating a User ` -* :doc:`Blacklist users by IP address with a custom voter ` +* :doc:`/cookbook/security/voters_data_permission` * :doc:`Access Control Lists (ACLs) ` * :doc:`/cookbook/security/remember_me` -* :doc:`How to Restrict Firewalls to a Specific Request ` +* :doc:`/cookbook/security/multiple_user_providers` -.. _`FOSUserBundle`: https://github.com/FriendsOfSymfony/FOSUserBundle -.. _`implement the \Serializable interface`: http://php.net/manual/en/class.serializable.php -.. _`Timing attack`: http://en.wikipedia.org/wiki/Timing_attack +.. _`online tool`: https://www.dailycred.com/blog/12/bcrypt-calculator +.. _`frameworkextrabundle documentation`: http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/index.html +.. _`security advisories database`: https://github.com/FriendsOfPHP/security-advisories diff --git a/book/service_container.rst b/book/service_container.rst index 5e43c1d6794..8ea418fcd2e 100644 --- a/book/service_container.rst +++ b/book/service_container.rst @@ -95,6 +95,8 @@ down every place you create a ``Mailer`` service and change it. .. index:: single: Service Container; Configuring services +.. _service-container-creating-service: + Creating/Configuring Services in the Container ---------------------------------------------- @@ -139,7 +141,7 @@ is never created. This saves memory and increases the speed of your application. This also means that there's very little or no performance hit for defining lots of services. Services that are never used are never constructed. -As an added bonus, the ``Mailer`` service is only created once and the same +As a bonus, the ``Mailer`` service is only created once and the same instance is returned each time you ask for the service. This is almost always the behavior you'll need (it's more flexible and powerful), but you'll learn later how you can configure a service that has multiple instances in the @@ -168,12 +170,11 @@ straightforward. Parameters make defining services more organized and flexible: # app/config/config.yml parameters: - my_mailer.class: Acme\HelloBundle\Mailer my_mailer.transport: sendmail services: my_mailer: - class: "%my_mailer.class%" + class: Acme\HelloBundle\Mailer arguments: ["%my_mailer.transport%"] .. code-block:: xml @@ -182,15 +183,15 @@ straightforward. Parameters make defining services more organized and flexible: + xsi:schemaLocation="http://symfony.com/schema/dic/services + http://symfony.com/schema/dic/services/services-1.0.xsd"> - Acme\HelloBundle\Mailer sendmail - + %my_mailer.transport% @@ -201,24 +202,23 @@ straightforward. Parameters make defining services more organized and flexible: // app/config/config.php use Symfony\Component\DependencyInjection\Definition; - $container->setParameter('my_mailer.class', 'Acme\HelloBundle\Mailer'); $container->setParameter('my_mailer.transport', 'sendmail'); $container->setDefinition('my_mailer', new Definition( - '%my_mailer.class%', + 'Acme\HelloBundle\Mailer', array('%my_mailer.transport%') )); The end result is exactly the same as before - the difference is only in -*how* you defined the service. By surrounding the ``my_mailer.class`` and -``my_mailer.transport`` strings in percent (``%``) signs, the container knows -to look for parameters with those names. When the container is built, it -looks up the value of each parameter and uses it in the service definition. +*how* you defined the service. By surrounding the ``my_mailer.transport`` +string in percent (``%``) signs, the container knows to look for a parameter +with that name. When the container is built, it looks up the value of each +parameter and uses it in the service definition. .. note:: If you want to use a string that starts with an ``@`` sign as a parameter - value (i.e. a very safe mailer password) in a YAML file, you need to escape + value (e.g. a very safe mailer password) in a YAML file, you need to escape it by adding another ``@`` sign (this only applies to the YAML format): .. code-block:: yaml @@ -291,12 +291,12 @@ Importing Configuration with ``imports`` So far, you've placed your ``my_mailer`` service container definition directly in the application configuration file (e.g. ``app/config/config.yml``). Of -course, since the ``Mailer`` class itself lives inside the ``AcmeHelloBundle``, -it makes more sense to put the ``my_mailer`` container definition inside the +course, since the ``Mailer`` class itself lives inside the AcmeHelloBundle, it +makes more sense to put the ``my_mailer`` container definition inside the bundle as well. First, move the ``my_mailer`` container definition into a new container resource -file inside ``AcmeHelloBundle``. If the ``Resources`` or ``Resources/config`` +file inside AcmeHelloBundle. If the ``Resources`` or ``Resources/config`` directories don't exist, create them. .. configuration-block:: @@ -305,12 +305,11 @@ directories don't exist, create them. # src/Acme/HelloBundle/Resources/config/services.yml parameters: - my_mailer.class: Acme\HelloBundle\Mailer my_mailer.transport: sendmail services: my_mailer: - class: "%my_mailer.class%" + class: Acme\HelloBundle\Mailer arguments: ["%my_mailer.transport%"] .. code-block:: xml @@ -319,15 +318,15 @@ directories don't exist, create them. + xsi:schemaLocation="http://symfony.com/schema/dic/services + http://symfony.com/schema/dic/services/services-1.0.xsd"> - Acme\HelloBundle\Mailer sendmail - + %my_mailer.transport% @@ -338,11 +337,10 @@ directories don't exist, create them. // src/Acme/HelloBundle/Resources/config/services.php use Symfony\Component\DependencyInjection\Definition; - $container->setParameter('my_mailer.class', 'Acme\HelloBundle\Mailer'); $container->setParameter('my_mailer.transport', 'sendmail'); $container->setDefinition('my_mailer', new Definition( - '%my_mailer.class%', + 'Acme\HelloBundle\Mailer', array('%my_mailer.transport%') )); @@ -365,7 +363,8 @@ configuration. + xsi:schemaLocation="http://symfony.com/schema/dic/services + http://symfony.com/schema/dic/services/services-1.0.xsd"> @@ -382,10 +381,9 @@ configuration. The ``imports`` directive allows your application to include service container configuration resources from any other location (most commonly from bundles). The ``resource`` location, for files, is the absolute path to the resource -file. The special ``@AcmeHello`` syntax resolves the directory path of -the ``AcmeHelloBundle`` bundle. This helps you specify the path to the resource -without worrying later if you move the ``AcmeHelloBundle`` to a different -directory. +file. The special ``@AcmeHelloBundle`` syntax resolves the directory path +of the AcmeHelloBundle bundle. This helps you specify the path to the resource +without worrying later if you move the AcmeHelloBundle to a different directory. .. index:: single: Service Container; Extension configuration @@ -444,8 +442,10 @@ invokes the service container extension inside the FrameworkBundle: + xsi:schemaLocation="http://symfony.com/schema/dic/services + http://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/symfony + http://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> @@ -488,7 +488,7 @@ In this case, the extension allows you to customize the ``error_handler``, the FrameworkBundle uses the options specified here to define and configure the services specific to it. The bundle takes care of creating all the necessary ``parameters`` and ``services`` for the service container, while still allowing -much of the configuration to be easily customized. As an added bonus, most +much of the configuration to be easily customized. As a bonus, most service container extensions are also smart enough to perform validation - notifying you of options that are missing or the wrong data type. @@ -565,15 +565,12 @@ the service container gives you a much more appealing option: .. code-block:: yaml # src/Acme/HelloBundle/Resources/config/services.yml - parameters: - # ... - newsletter_manager.class: Acme\HelloBundle\Newsletter\NewsletterManager - services: my_mailer: # ... + newsletter_manager: - class: "%newsletter_manager.class%" + class: Acme\HelloBundle\Newsletter\NewsletterManager arguments: ["@my_mailer"] .. code-block:: xml @@ -582,18 +579,15 @@ the service container gives you a much more appealing option: - - - - Acme\HelloBundle\Newsletter\NewsletterManager - + xsi:schemaLocation="http://symfony.com/schema/dic/services + http://symfony.com/schema/dic/services/services-1.0.xsd"> - + + @@ -605,15 +599,10 @@ the service container gives you a much more appealing option: use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; - // ... - $container->setParameter( - 'newsletter_manager.class', - 'Acme\HelloBundle\Newsletter\NewsletterManager' - ); - $container->setDefinition('my_mailer', ...); + $container->setDefinition('newsletter_manager', new Definition( - '%newsletter_manager.class%', + 'Acme\HelloBundle\Newsletter\NewsletterManager', array(new Reference('my_mailer')) )); @@ -634,9 +623,6 @@ the work of instantiating the classes. Using the Expression Language ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. versionadded:: 2.4 - The Expression Language functionality was introduced in Symfony 2.4. - The service container also supports an "expression" that allows you to inject very specific values into a service. @@ -692,8 +678,10 @@ To learn more about the expression language syntax, see :doc:`/components/expres In this context, you have access to 2 functions: -* ``service`` - returns a given service (see the example above); -* ``parameter`` - returns a specific parameter value (syntax is just like ``service``) +``service`` + Returns a given service (see the example above). +``parameter`` + Returns a specific parameter value (syntax is just like ``service``). You also have access to the :class:`Symfony\\Component\\DependencyInjection\\ContainerBuilder` via a ``container`` variable. Here's another example: @@ -718,7 +706,7 @@ via a ``container`` variable. Here's another example: - @=container.hasParameter('some_param') ? parameter('some_param') : 'default_value' + container.hasParameter('some_param') ? parameter('some_param') : 'default_value' @@ -731,7 +719,7 @@ via a ``container`` variable. Here's another example: $container->setDefinition('my_mailer', new Definition( 'Acme\HelloBundle\Mailer', array(new Expression( - "@=container.hasParameter('some_param') ? parameter('some_param') : 'default_value'" + "container.hasParameter('some_param') ? parameter('some_param') : 'default_value'" )) )); @@ -770,15 +758,12 @@ Injecting the dependency by the setter method just needs a change of syntax: .. code-block:: yaml # src/Acme/HelloBundle/Resources/config/services.yml - parameters: - # ... - newsletter_manager.class: Acme\HelloBundle\Newsletter\NewsletterManager - services: my_mailer: # ... + newsletter_manager: - class: "%newsletter_manager.class%" + class: Acme\HelloBundle\Newsletter\NewsletterManager calls: - [setMailer, ["@my_mailer"]] @@ -788,18 +773,15 @@ Injecting the dependency by the setter method just needs a change of syntax: - - - - Acme\HelloBundle\Newsletter\NewsletterManager - + xsi:schemaLocation="http://symfony.com/schema/dic/services + http://symfony.com/schema/dic/services/services-1.0.xsd"> - + + @@ -813,15 +795,10 @@ Injecting the dependency by the setter method just needs a change of syntax: use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; - // ... - $container->setParameter( - 'newsletter_manager.class', - 'Acme\HelloBundle\Newsletter\NewsletterManager' - ); - $container->setDefinition('my_mailer', ...); + $container->setDefinition('newsletter_manager', new Definition( - '%newsletter_manager.class%' + 'Acme\HelloBundle\Newsletter\NewsletterManager' ))->addMethodCall('setMailer', array( new Reference('my_mailer'), )); @@ -837,9 +814,6 @@ Injecting the dependency by the setter method just needs a change of syntax: Injecting the Request ~~~~~~~~~~~~~~~~~~~~~ -.. versionadded:: 2.4 - The ``request_stack`` service was introduced in Symfony 2.4. - As of Symfony 2.4, instead of injecting the ``request`` service, you should inject the ``request_stack`` service and access the ``Request`` by calling the :method:`Symfony\\Component\\HttpFoundation\\RequestStack::getCurrentRequest` @@ -945,12 +919,9 @@ it exists and do nothing if it doesn't: .. code-block:: yaml # src/Acme/HelloBundle/Resources/config/services.yml - parameters: - # ... - services: newsletter_manager: - class: "%newsletter_manager.class%" + class: Acme\HelloBundle\Newsletter\NewsletterManager arguments: ["@?my_mailer"] .. code-block:: xml @@ -959,13 +930,15 @@ it exists and do nothing if it doesn't: + xsi:schemaLocation="http://symfony.com/schema/dic/services + http://symfony.com/schema/dic/services/services-1.0.xsd"> - + + @@ -978,15 +951,10 @@ it exists and do nothing if it doesn't: use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\ContainerInterface; - // ... - $container->setParameter( - 'newsletter_manager.class', - 'Acme\HelloBundle\Newsletter\NewsletterManager' - ); - $container->setDefinition('my_mailer', ...); + $container->setDefinition('newsletter_manager', new Definition( - '%newsletter_manager.class%', + 'Acme\HelloBundle\Newsletter\NewsletterManager', array( new Reference( 'my_mailer', @@ -1063,7 +1031,7 @@ Configuring the service container is easy: # src/Acme/HelloBundle/Resources/config/services.yml services: newsletter_manager: - class: "%newsletter_manager.class%" + class: Acme\HelloBundle\Newsletter\NewsletterManager arguments: ["@mailer", "@templating"] .. code-block:: xml @@ -1072,9 +1040,10 @@ Configuring the service container is easy: + xsi:schemaLocation="http://symfony.com/schema/dic/services + http://symfony.com/schema/dic/services/services-1.0.xsd"> - + @@ -1084,7 +1053,7 @@ Configuring the service container is easy: // src/Acme/HelloBundle/Resources/config/services.php $container->setDefinition('newsletter_manager', new Definition( - '%newsletter_manager.class%', + 'Acme\HelloBundle\Newsletter\NewsletterManager', array( new Reference('mailer'), new Reference('templating'), @@ -1121,6 +1090,7 @@ to be used for a specific purpose. Take the following example: services: foo.twig.extension: class: Acme\HelloBundle\Extension\FooExtension + public: false tags: - { name: twig.extension } @@ -1130,11 +1100,13 @@ to be used for a specific purpose. Take the following example: + xsi:schemaLocation="http://symfony.com/schema/dic/services + http://symfony.com/schema/dic/services/services-1.0.xsd"> + class="Acme\HelloBundle\Extension\FooExtension" + public="false"> @@ -1146,6 +1118,7 @@ to be used for a specific purpose. Take the following example: use Symfony\Component\DependencyInjection\Definition; $definition = new Definition('Acme\HelloBundle\Extension\FooExtension'); + $definition->setPublic(false); $definition->addTag('twig.extension'); $container->setDefinition('foo.twig.extension', $definition); @@ -1171,18 +1144,21 @@ console. To show all services and the class for each service, run: .. code-block:: bash - $ php app/console container:debug + $ php app/console debug:container + +.. versionadded:: 2.6 + Prior to Symfony 2.6, this command was called ``container:debug``. -By default only public services are shown, but you can also view private services: +By default, only public services are shown, but you can also view private services: .. code-block:: bash - $ php app/console container:debug --show-private + $ php app/console debug:container --show-private .. note:: If a private service is only used as an argument to just *one* other service, - it won't be displayed by the ``container:debug`` command, even when using + it won't be displayed by the ``debug:container`` command, even when using the ``--show-private`` option. See :ref:`Inline Private Services ` for more details. @@ -1191,7 +1167,7 @@ its id: .. code-block:: bash - $ php app/console container:debug my_mailer + $ php app/console debug:container my_mailer Learn more ---------- diff --git a/book/stable_api.rst b/book/stable_api.rst deleted file mode 100644 index 56d0bcb7d4d..00000000000 --- a/book/stable_api.rst +++ /dev/null @@ -1,50 +0,0 @@ -.. index:: - single: Stable API - -.. _the-symfony2-stable-api: - -The Symfony Stable API -====================== - -The Symfony stable API is a subset of all Symfony published public methods -(components and core bundles) that share the following properties: - -* The namespace and class name won't change; -* The method name won't change; -* The method signature (arguments and return value type) won't change; -* The semantic of what the method does won't change. - -The implementation itself can change though. The only valid case for a change -in the stable API is in order to fix a security issue. - -The stable API is based on a whitelist, tagged with `@api`. Therefore, -everything not tagged explicitly is not part of the stable API. - -.. tip:: - - Read more about the stable API in :doc:`/contributing/code/bc`. - -.. tip:: - - Any third party bundle should also publish its own stable API. - -As of the latest stable release of Symfony, the following components have -a public tagged API: - -* BrowserKit -* ClassLoader -* Console -* CssSelector -* DependencyInjection -* DomCrawler -* EventDispatcher -* Filesystem -* Finder -* HttpFoundation -* HttpKernel -* Process -* Routing -* Templating -* Translation -* Validator -* Yaml diff --git a/book/templating.rst b/book/templating.rst index 4f5d80ebebf..d8a4fba2cf0 100644 --- a/book/templating.rst +++ b/book/templating.rst @@ -16,8 +16,8 @@ code. .. note:: - How to render templates is covered in the :ref:`controller ` - page of the book. + How to render templates is covered in the + :ref:`controller ` page of the book. .. index:: single: Templating; What is a template? @@ -46,7 +46,7 @@ template - a text file parsed by PHP that contains a mix of text and PHP code: getCaption() ?> - + @@ -77,15 +77,18 @@ to web designers and, in several ways, more powerful than PHP templates: Twig defines three types of special syntax: -* ``{{ ... }}``: "Says something": prints a variable or the result of an - expression to the template; +``{{ ... }}`` + "Says something": prints a variable or the result of an expression to the + template. -* ``{% ... %}``: "Does something": a **tag** that controls the logic of the - template; it is used to execute statements such as for-loops for example. +``{% ... %}`` + "Does something": a **tag** that controls the logic of the template; it is + used to execute statements such as for-loops for example. -* ``{# ... #}``: "Comment something": it's the equivalent of the PHP - ``/* comment */`` syntax. It's used to add single or multi-line comments. - The content of the comments isn't included in the rendered pages. +``{# ... #}`` + "Comment something": it's the equivalent of the PHP ``/* comment */`` syntax. + It's used to add single or multi-line comments. The content of the comments + isn't included in the rendered pages. Twig also contains **filters**, which modify content before being rendered. The following makes the ``title`` variable all uppercase before rendering @@ -233,7 +236,7 @@ First, build a base layout file:
  • Home
  • Blog
  • - +
    @@ -260,8 +263,8 @@ A child template might look like this: .. code-block:: html+jinja - {# src/Acme/BlogBundle/Resources/views/Blog/index.html.twig #} - {% extends '::base.html.twig' %} + {# app/Resources/views/Blog/index.html.twig #} + {% extends 'base.html.twig' %} {% block title %}My cool blog posts{% endblock %} @@ -274,8 +277,8 @@ A child template might look like this: .. code-block:: html+php - - extend('::base.html.php') ?> + + extend('base.html.php') ?> set('title', 'My cool blog posts') ?> @@ -283,15 +286,16 @@ A child template might look like this:

    getTitle() ?>

    getBody() ?>

    - + stop() ?> .. note:: The parent template is identified by a special string syntax - (``::base.html.twig``) that indicates that the template lives in the - ``app/Resources/views`` directory of the project. This naming convention is - explained fully in :ref:`template-naming-locations`. + (``base.html.twig``). This path is relative to the ``app/Resources/views`` + directory of the project. You could also use the logical name equivalent: + ``::base.html.twig``. This naming convention is explained fully in + :ref:`template-naming-locations`. The key to template inheritance is the ``{% extends %}`` tag. This tells the templating engine to first evaluate the base template, which sets up @@ -354,15 +358,15 @@ When working with template inheritance, here are some tips to keep in mind: can use the ``{{ parent() }}`` function. This is useful if you want to add to the contents of a parent block instead of completely overriding it: - .. code-block:: html+jinja + .. code-block:: html+jinja - {% block sidebar %} -

    Table of Contents

    + {% block sidebar %} +

    Table of Contents

    - {# ... #} + {# ... #} - {{ parent() }} - {% endblock %} + {{ parent() }} + {% endblock %} .. index:: single: Templating; Naming conventions @@ -373,75 +377,81 @@ When working with template inheritance, here are some tips to keep in mind: Template Naming and Locations ----------------------------- -.. versionadded:: 2.2 - Namespaced path support was introduced in 2.2, allowing for template names - like ``@AcmeDemo/layout.html.twig``. See :doc:`/cookbook/templating/namespaced_paths` - for more details. - By default, templates can live in two different locations: -* ``app/Resources/views/``: The applications ``views`` directory can contain - application-wide base templates (i.e. your application's layouts) as well as - templates that override bundle templates (see - :ref:`overriding-bundle-templates`); +``app/Resources/views/`` + The applications ``views`` directory can contain application-wide base templates + (i.e. your application's layouts and templates of the application bundle) as + well as templates that override third party bundle templates + (see :ref:`overriding-bundle-templates`). -* ``path/to/bundle/Resources/views/``: Each bundle houses its templates in its - ``Resources/views`` directory (and subdirectories). The majority of templates - will live inside a bundle. +``path/to/bundle/Resources/views/`` + Each third party bundle houses its templates in its ``Resources/views/`` + directory (and subdirectories). When you plan to share your bundle, you should + put the templates in the bundle instead of the ``app/`` directory. -Symfony uses a **bundle**:**controller**:**template** string syntax for -templates. This allows for several different types of templates, each which -lives in a specific location: +Most of the templates you'll use live in the ``app/Resources/views/`` +directory. The path you'll use will be relative to this directory. For example, +to render/extend ``app/Resources/views/base.html.twig``, you'll use the +``base.html.twig`` path and to render/extend +``app/Resources/views/Blog/index.html.twig``, you'll use the +``Blog/index.html.twig`` path. + +.. _template-referencing-in-bundle: + +Referencing Templates in a Bundle +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Symfony uses a **bundle**:**directory**:**filename** string syntax for +templates that live inside a bundle. This allows for several types of +templates, each which lives in a specific location: * ``AcmeBlogBundle:Blog:index.html.twig``: This syntax is used to specify a template for a specific page. The three parts of the string, each separated by a colon (``:``), mean the following: - * ``AcmeBlogBundle``: (*bundle*) the template lives inside the - ``AcmeBlogBundle`` (e.g. ``src/Acme/BlogBundle``); + * ``AcmeBlogBundle``: (*bundle*) the template lives inside the AcmeBlogBundle + (e.g. ``src/Acme/BlogBundle``); - * ``Blog``: (*controller*) indicates that the template lives inside the + * ``Blog``: (*directory*) indicates that the template lives inside the ``Blog`` subdirectory of ``Resources/views``; - * ``index.html.twig``: (*template*) the actual name of the file is + * ``index.html.twig``: (*filename*) the actual name of the file is ``index.html.twig``. - Assuming that the ``AcmeBlogBundle`` lives at ``src/Acme/BlogBundle``, the + Assuming that the AcmeBlogBundle lives at ``src/Acme/BlogBundle``, the final path to the layout would be ``src/Acme/BlogBundle/Resources/views/Blog/index.html.twig``. * ``AcmeBlogBundle::layout.html.twig``: This syntax refers to a base template - that's specific to the ``AcmeBlogBundle``. Since the middle, "controller", - portion is missing (e.g. ``Blog``), the template lives at - ``Resources/views/layout.html.twig`` inside ``AcmeBlogBundle``. - -* ``::base.html.twig``: This syntax refers to an application-wide base template - or layout. Notice that the string begins with two colons (``::``), meaning - that both the *bundle* and *controller* portions are missing. This means - that the template is not located in any bundle, but instead in the root - ``app/Resources/views/`` directory. + that's specific to the AcmeBlogBundle. Since the middle, "directory", portion + is missing (e.g. ``Blog``), the template lives at + ``Resources/views/layout.html.twig`` inside AcmeBlogBundle. Yes, there are 2 + colons in the middle of the string when the "controller" subdirectory part is + missing. In the :ref:`overriding-bundle-templates` section, you'll find out how each -template living inside the ``AcmeBlogBundle``, for example, can be overridden +template living inside the AcmeBlogBundle, for example, can be overridden by placing a template of the same name in the ``app/Resources/AcmeBlogBundle/views/`` directory. This gives the power to override templates from any vendor bundle. .. tip:: - Hopefully the template naming syntax looks familiar - it's the same naming - convention used to refer to :ref:`controller-string-syntax`. + Hopefully the template naming syntax looks familiar - it's similar to + the naming convention used to refer to :ref:`controller-string-syntax`. Template Suffix ~~~~~~~~~~~~~~~ -The **bundle**:**controller**:**template** format of each template specifies -*where* the template file is located. Every template name also has two extensions -that specify the *format* and *engine* for that template. - -* **AcmeBlogBundle:Blog:index.html.twig** - HTML format, Twig engine - -* **AcmeBlogBundle:Blog:index.html.php** - HTML format, PHP engine +Every template name also has two extensions that specify the *format* and +*engine* for that template. -* **AcmeBlogBundle:Blog:index.css.twig** - CSS format, Twig engine +======================== ====== ====== +Filename Format Engine +======================== ====== ====== +``Blog/index.html.twig`` HTML Twig +``Blog/index.html.php`` HTML PHP +``Blog/index.css.twig`` CSS Twig +======================== ====== ====== By default, any Symfony template can be written in either Twig or PHP, and the last part of the extension (e.g. ``.twig`` or ``.php``) specifies which @@ -489,7 +499,7 @@ Including other Templates ~~~~~~~~~~~~~~~~~~~~~~~~~ You'll often want to include the same template or code fragment on several -different pages. For example, in an application with "news articles", the +pages. For example, in an application with "news articles", the template code displaying an article might be used on the article detail page, on a page displaying the most popular articles, or in a list of the latest articles. @@ -503,7 +513,7 @@ template. First, create the template that you'll need to reuse. .. code-block:: html+jinja - {# src/Acme/ArticleBundle/Resources/views/Article/articleDetails.html.twig #} + {# app/Resources/views/Article/articleDetails.html.twig #}

    {{ article.title }}

    @@ -513,7 +523,7 @@ template. First, create the template that you'll need to reuse. .. code-block:: html+php - +

    getTitle() ?>

    @@ -527,34 +537,31 @@ Including this template from any other template is simple: .. code-block:: html+jinja - {# src/Acme/ArticleBundle/Resources/views/Article/list.html.twig #} - {% extends 'AcmeArticleBundle::layout.html.twig' %} + {# app/Resources/views/Article/list.html.twig #} + {% extends 'layout.html.twig' %} {% block body %}

    Recent Articles

    {% for article in articles %} - {{ include( - 'AcmeArticleBundle:Article:articleDetails.html.twig', - { 'article': article } - ) }} + {{ include('Article/articleDetails.html.twig', { 'article': article }) }} {% endfor %} {% endblock %} .. code-block:: html+php - - extend('AcmeArticleBundle::layout.html.php') ?> + + extend('layout.html.php') ?> start('body') ?>

    Recent Articles

    render( - 'AcmeArticleBundle:Article:articleDetails.html.php', + 'Article/articleDetails.html.php', array('article' => $article) ) ?> - + stop() ?> The template is included using the ``{{ include() }}`` function. Notice that the @@ -570,10 +577,6 @@ you set `with_context`_ to false). maps (i.e. an array with named keys). If you needed to pass in multiple elements, it would look like this: ``{'foo': foo, 'bar': bar}``. -.. versionadded:: 2.2 - The `include() function`_ is a new Twig feature that's available in Symfony - 2.2. Prior, the `{% include %} tag`_ tag was used. - .. index:: single: Templating; Embedding action @@ -591,7 +594,11 @@ The solution is to simply embed the result of an entire controller from your template. First, create a controller that renders a certain number of recent articles:: - // src/Acme/ArticleBundle/Controller/ArticleController.php + // src/AppBundle/Controller/ArticleController.php + namespace AppBundle\Controller; + + // ... + class ArticleController extends Controller { public function recentArticlesAction($max = 3) @@ -601,7 +608,7 @@ articles:: $articles = ...; return $this->render( - 'AcmeArticleBundle:Article:recentList.html.twig', + 'Article/recentList.html.twig', array('articles' => $articles) ); } @@ -613,7 +620,7 @@ The ``recentList`` template is perfectly straightforward: .. code-block:: html+jinja - {# src/Acme/ArticleBundle/Resources/views/Article/recentList.html.twig #} + {# app/Resources/views/Article/recentList.html.twig #} {% for article in articles %} {{ article.title }} @@ -622,12 +629,12 @@ The ``recentList`` template is perfectly straightforward: .. code-block:: html+php - + getTitle() ?> - + .. note:: @@ -646,9 +653,10 @@ string syntax for controllers (i.e. **bundle**:**controller**:**action**): {# ... #} .. code-block:: html+php @@ -751,7 +759,7 @@ in your application configuration: framework: # ... templating: - hinclude_default_template: AcmeDemoBundle::hinclude.html.twig + hinclude_default_template: hinclude.html.twig .. code-block:: xml @@ -765,7 +773,7 @@ in your application configuration: - + @@ -776,14 +784,11 @@ in your application configuration: // ... 'templating' => array( 'hinclude_default_template' => array( - 'AcmeDemoBundle::hinclude.html.twig', + 'hinclude.html.twig', ), ), )); -.. versionadded:: 2.2 - Default templates per render function was introduced in Symfony 2.2 - You can define default templates per ``render`` function (which will override any global default template that is defined): @@ -792,7 +797,7 @@ any global default template that is defined): .. code-block:: jinja {{ render_hinclude(controller('...'), { - 'default': 'AcmeDemoBundle:Default:content.html.twig' + 'default': 'Default/content.html.twig' }) }} .. code-block:: php @@ -801,7 +806,7 @@ any global default template that is defined): new ControllerReference('...'), array( 'renderer' => 'hinclude', - 'default' => 'AcmeDemoBundle:Default:content.html.twig', + 'default' => 'Default/content.html.twig', ) ) ?> @@ -848,7 +853,7 @@ configuration: # app/config/routing.yml _welcome: path: / - defaults: { _controller: AcmeDemoBundle:Welcome:index } + defaults: { _controller: AppBundle:Welcome:index } .. code-block:: xml @@ -860,7 +865,7 @@ configuration: http://symfony.com/schema/routing/routing-1.0.xsd"> - AcmeDemoBundle:Welcome:index + AppBundle:Welcome:index @@ -872,7 +877,7 @@ configuration: $collection = new RouteCollection(); $collection->add('_welcome', new Route('/', array( - '_controller' => 'AcmeDemoBundle:Welcome:index', + '_controller' => 'AppBundle:Welcome:index', ))); return $collection; @@ -899,7 +904,7 @@ route: # app/config/routing.yml article_show: path: /article/{slug} - defaults: { _controller: AcmeArticleBundle:Article:show } + defaults: { _controller: AppBundle:Article:show } .. code-block:: xml @@ -911,7 +916,7 @@ route: http://symfony.com/schema/routing/routing-1.0.xsd"> - AcmeArticleBundle:Article:show + AppBundle:Article:show @@ -923,7 +928,7 @@ route: $collection = new RouteCollection(); $collection->add('article_show', new Route('/article/{slug}', array( - '_controller' => 'AcmeArticleBundle:Article:show', + '_controller' => 'AppBundle:Article:show', ))); return $collection; @@ -937,7 +942,7 @@ correctly: .. code-block:: html+jinja - {# src/Acme/ArticleBundle/Resources/views/Article/recentList.html.twig #} + {# app/Resources/views/Article/recentList.html.twig #} {% for article in articles %} {{ article.title }} @@ -946,14 +951,14 @@ correctly: .. code-block:: html+php - + getTitle() ?> - + .. tip:: @@ -1014,6 +1019,53 @@ assets won't be cached when deployed. For example, ``/images/logo.png`` might look like ``/images/logo.png?v2``. For more information, see the :ref:`ref-framework-assets-version` configuration option. +.. _`book-templating-version-by-asset`: + +.. versionadded:: 2.5 + Setting versioned URLs on an asset-by-asset basis was introduced in Symfony 2.5. + +If you need to set a version for a specific asset, you can set the fourth +argument (or the ``version`` argument) to the desired version: + +.. configuration-block:: + + .. code-block:: html+jinja + + Symfony! + + .. code-block:: html+php + + Symfony! + +If you don't give a version or pass ``null``, the default package version +(from :ref:`ref-framework-assets-version`) will be used. If you pass ``false``, +versioned URL will be deactivated for this asset. + +.. versionadded:: 2.5 + Absolute URLs for assets were introduced in Symfony 2.5. + +If you need absolute URLs for assets, you can set the third argument (or the +``absolute`` argument) to ``true``: + +.. configuration-block:: + + .. code-block:: html+jinja + + Symfony! + + .. code-block:: html+php + + Symfony! + .. index:: single: Templating; Including stylesheets and JavaScripts single: Stylesheets; Including stylesheets @@ -1039,43 +1091,76 @@ one called ``stylesheets`` inside the ``head`` tag and another called ``javascri just above the closing ``body`` tag. These blocks will contain all of the stylesheets and JavaScripts that you'll need throughout your site: -.. code-block:: html+jinja +.. configuration-block:: - {# app/Resources/views/base.html.twig #} - - - {# ... #} + .. code-block:: html+jinja - {% block stylesheets %} - - {% endblock %} - - - {# ... #} + {# app/Resources/views/base.html.twig #} + + + {# ... #} - {% block javascripts %} - - {% endblock %} - - + {% block stylesheets %} + + {% endblock %} + + + {# ... #} + + {% block javascripts %} + + {% endblock %} + + + + .. code-block:: php + + // app/Resources/views/base.html.php + + + + + start('stylesheets') ?> + + stop() ?> + + + + + start('javascripts') ?> + + stop() ?> + + That's easy enough! But what if you need to include an extra stylesheet or JavaScript from a child template? For example, suppose you have a contact page and you need to include a ``contact.css`` stylesheet *just* on that page. From inside that contact page's template, do the following: -.. code-block:: html+jinja +.. configuration-block:: - {# src/Acme/DemoBundle/Resources/views/Contact/contact.html.twig #} - {% extends '::base.html.twig' %} + .. code-block:: html+jinja - {% block stylesheets %} - {{ parent() }} + {# app/Resources/views/Contact/contact.html.twig #} + {% extends 'base.html.twig' %} - - {% endblock %} + {% block stylesheets %} + {{ parent() }} - {# ... #} + + {% endblock %} + + {# ... #} + + .. code-block:: php + + // app/Resources/views/Contact/contact.html.twig + extend('base.html.php') ?> + + start('stylesheets') ?> + + stop() ?> In the child template, you simply override the ``stylesheets`` block and put your new stylesheet tag inside of that block. Of course, since you want @@ -1104,12 +1189,18 @@ is a :class:`Symfony\\Bundle\\FrameworkBundle\\Templating\\GlobalVariables` instance which will give you access to some application specific variables automatically: -* ``app.security`` - The security context. -* ``app.user`` - The current user object. -* ``app.request`` - The request object. -* ``app.session`` - The session object. -* ``app.environment`` - The current environment (dev, prod, etc). -* ``app.debug`` - True if in debug mode. False otherwise. +``app.security`` + The security context. +``app.user`` + The current user object. +``app.request`` + The request object. +``app.session`` + The session object. +``app.environment`` + The current environment (dev, prod, etc). +``app.debug`` + True if in debug mode. False otherwise. .. configuration-block:: @@ -1127,7 +1218,13 @@ automatically: getDebug()): ?>

    Request method: getRequest()->getMethod() ?>

    Application Environment: getEnvironment() ?>

    - + + +.. versionadded:: 2.6 + The global ``app.security`` variable (or the ``$app->getSecurity()`` + method in PHP templates) is deprecated as of Symfony 2.6. Use ``app.user`` + (``$app->getUser()``) and ``is_granted()`` (``$view['security']->isGranted()``) + instead. .. tip:: @@ -1145,14 +1242,14 @@ This special object is responsible for rendering templates and returning their content. When you render a template in a controller, for example, you're actually using the templating engine service. For example:: - return $this->render('AcmeArticleBundle:Article:index.html.twig'); + return $this->render('Article/index.html.twig'); is equivalent to:: use Symfony\Component\HttpFoundation\Response; $engine = $this->container->get('templating'); - $content = $engine->render('AcmeArticleBundle:Article:index.html.twig'); + $content = $engine->render('Article/index.html.twig'); return $response = new Response($content); @@ -1221,11 +1318,11 @@ bundles (see `KnpBundles.com`_) for a large number of different features. Once you use a third-party bundle, you'll likely need to override and customize one or more of its templates. -Suppose you've included the imaginary open-source ``AcmeBlogBundle`` in your -project (e.g. in the ``src/Acme/BlogBundle`` directory). And while you're -really happy with everything, you want to override the blog "list" page to -customize the markup specifically for your application. By digging into the -``Blog`` controller of the ``AcmeBlogBundle``, you find the following:: +Suppose you've installed the imaginary open-source AcmeBlogBundle in your +project. And while you're really happy with everything, you want to override +the blog "list" page to customize the markup specifically for your application. +By digging into the ``Blog`` controller of the AcmeBlogBundle, you find the +following:: public function indexAction() { @@ -1255,7 +1352,7 @@ to create it). You're now free to customize the template. cache (``php app/console cache:clear``), even if you are in debug mode. This logic also applies to base bundle templates. Suppose also that each -template in ``AcmeBlogBundle`` inherits from a base template called +template in AcmeBlogBundle inherits from a base template called ``AcmeBlogBundle::layout.html.twig``. Just as before, Symfony will look in the following two places for the template: @@ -1305,16 +1402,16 @@ covered: * Create a ``app/Resources/views/base.html.twig`` file that contains the main layout for your application (like in the previous example). Internally, this - template is called ``::base.html.twig``; + template is called ``base.html.twig``; -* Create a template for each "section" of your site. For example, an ``AcmeBlogBundle``, - would have a template called ``AcmeBlogBundle::layout.html.twig`` that contains - only blog section-specific elements; +* Create a template for each "section" of your site. For example, the blog + functionality would have a template called ``Blog/layout.html.twig`` that + contains only blog section-specific elements; .. code-block:: html+jinja - {# src/Acme/BlogBundle/Resources/views/layout.html.twig #} - {% extends '::base.html.twig' %} + {# app/Resources/views/Blog/layout.html.twig #} + {% extends 'base.html.twig' %} {% block body %}

    Blog Application

    @@ -1324,12 +1421,12 @@ covered: * Create individual templates for each page and make each extend the appropriate section template. For example, the "index" page would be called something - close to ``AcmeBlogBundle:Blog:index.html.twig`` and list the actual blog posts. + close to ``Blog/index.html.twig`` and list the actual blog posts. .. code-block:: html+jinja - {# src/Acme/BlogBundle/Resources/views/Blog/index.html.twig #} - {% extends 'AcmeBlogBundle::layout.html.twig' %} + {# app/Resources/views/Blog/index.html.twig #} + {% extends 'Blog/layout.html.twig' %} {% block content %} {% for entry in blog_entries %} @@ -1338,15 +1435,15 @@ covered: {% endfor %} {% endblock %} -Notice that this template extends the section template (``AcmeBlogBundle::layout.html.twig``) -which in-turn extends the base application layout (``::base.html.twig``). -This is the common three-level inheritance model. +Notice that this template extends the section template (``Blog/layout.html.twig``) +which in turn extends the base application layout (``base.html.twig``). This is +the common three-level inheritance model. When building your application, you may choose to follow this method or simply make each page template extend the base application template directly -(e.g. ``{% extends '::base.html.twig' %}``). The three-template model is -a best-practice method used by vendor bundles so that the base template for -a bundle can be easily overridden to properly extend your application's base +(e.g. ``{% extends 'base.html.twig' %}``). The three-template model is a +best-practice method used by vendor bundles so that the base template for a +bundle can be easily overridden to properly extend your application's base layout. .. index:: @@ -1450,15 +1547,37 @@ in a JavaScript string, use the ``js`` context: Debugging --------- -When using PHP, you can use ``var_dump()`` if you need to quickly find the -value of a variable passed. This is useful, for example, inside your controller. -The same can be achieved when using Twig thanks to the debug extension. +When using PHP, you can use the +:ref:`dump() function from the VarDumper component ` +if you need to quickly find the value of a variable passed. This is useful, +for example, inside your controller:: + + // src/AppBundle/Controller/ArticleController.php + namespace AppBundle\Controller; -Template parameters can then be dumped using the ``dump`` function: + // ... + + class ArticleController extends Controller + { + public function recentListAction() + { + $articles = ...; + dump($articles); + + // ... + } + } + +.. note:: + + The output of the ``dump()`` function is then rendered in the web developer + toolbar. + +The same mechanism can be used in Twig templates thanks to ``dump`` function: .. code-block:: html+jinja - {# src/Acme/ArticleBundle/Resources/views/Article/recentList.html.twig #} + {# app/Resources/views/Article/recentList.html.twig #} {{ dump(articles) }} {% for article in articles %} @@ -1480,13 +1599,10 @@ console command: .. code-block:: bash # You can check by filename: - $ php app/console twig:lint src/Acme/ArticleBundle/Resources/views/Article/recentList.html.twig + $ php app/console twig:lint app/Resources/views/Article/recentList.html.twig # or by directory: - $ php app/console twig:lint src/Acme/ArticleBundle/Resources/views - - # or using the bundle name: - $ php app/console twig:lint @AcmeArticleBundle + $ php app/console twig:lint app/Resources/views .. _template-formats: @@ -1497,11 +1613,11 @@ Templates are a generic way to render content in *any* format. And while in most cases you'll use templates to render HTML content, a template can just as easily generate JavaScript, CSS, XML or any other format you can dream of. -For example, the same "resource" is often rendered in several different formats. +For example, the same "resource" is often rendered in several formats. To render an article index page in XML, simply include the format in the template name: -* *XML template name*: ``AcmeArticleBundle:Article:index.xml.twig`` +* *XML template name*: ``Article/index.xml.twig`` * *XML template filename*: ``index.xml.twig`` In reality, this is nothing more than a naming convention and the template @@ -1515,7 +1631,7 @@ pattern is to do the following:: { $format = $request->getRequestFormat(); - return $this->render('AcmeBlogBundle:Blog:index.'.$format.'.twig'); + return $this->render('Blog/index.'.$format.'.twig'); } The ``getRequestFormat`` on the ``Request`` object defaults to ``html``, @@ -1555,7 +1671,7 @@ their use is not mandatory. The ``Response`` object returned by a controller can be created with or without the use of a template:: // creates a Response object whose content is the rendered template - $response = $this->render('AcmeArticleBundle:Article:index.html.twig'); + $response = $this->render('Article/index.html.twig'); // creates a Response object whose content is simple text $response = new Response('response content'); diff --git a/book/testing.rst b/book/testing.rst index f870ac58272..8101e7480e4 100644 --- a/book/testing.rst +++ b/book/testing.rst @@ -17,8 +17,8 @@ it has its own excellent `documentation`_. .. note:: - Symfony works with PHPUnit 3.5.11 or later, though version 3.6.4 is - needed to test the Symfony core code itself. + It's recommended to use the latest stable PHPUnit version (you will have + to use version 4.2 or higher to test the Symfony core code itself). Each test - whether it's a unit test or a functional test - is a PHP class that should live in the ``Tests/`` subdirectory of your bundles. If you follow @@ -47,7 +47,7 @@ Unit Tests A unit test is usually a test against a specific PHP class. If you want to test the overall behavior of your application, see the section about `Functional Tests`_. -Writing Symfony unit tests is no different than writing standard PHPUnit +Writing Symfony unit tests is no different from writing standard PHPUnit unit tests. Suppose, for example, that you have an *incredibly* simple class called ``Calculator`` in the ``Utility/`` directory of your bundle:: @@ -459,7 +459,10 @@ injection container:: Be warned that this does not work if you insulate the client or if you use an HTTP layer. For a list of services available in your application, use the -``container:debug`` console task. +``debug:container`` console task. + +.. versionadded:: 2.6 + Prior to Symfony 2.6, this command was called ``container:debug``. .. tip:: @@ -532,31 +535,28 @@ selects the last one on the page, and then selects its immediate parent element: Many other methods are also available: -+------------------------+----------------------------------------------------+ -| Method | Description | -+========================+====================================================+ -| ``filter('h1.title')`` | Nodes that match the CSS selector | -+------------------------+----------------------------------------------------+ -| ``filterXpath('h1')`` | Nodes that match the XPath expression | -+------------------------+----------------------------------------------------+ -| ``eq(1)`` | Node for the specified index | -+------------------------+----------------------------------------------------+ -| ``first()`` | First node | -+------------------------+----------------------------------------------------+ -| ``last()`` | Last node | -+------------------------+----------------------------------------------------+ -| ``siblings()`` | Siblings | -+------------------------+----------------------------------------------------+ -| ``nextAll()`` | All following siblings | -+------------------------+----------------------------------------------------+ -| ``previousAll()`` | All preceding siblings | -+------------------------+----------------------------------------------------+ -| ``parents()`` | Returns the parent nodes | -+------------------------+----------------------------------------------------+ -| ``children()`` | Returns children nodes | -+------------------------+----------------------------------------------------+ -| ``reduce($lambda)`` | Nodes for which the callable does not return false | -+------------------------+----------------------------------------------------+ +``filter('h1.title')`` + Nodes that match the CSS selector. +``filterXpath('h1')`` + Nodes that match the XPath expression. +``eq(1)`` + Node for the specified index. +``first()`` + First node. +``last()`` + Last node. +``siblings()`` + Siblings. +``nextAll()`` + All following siblings. +``previousAll()`` + All preceding siblings. +``parents()`` + Returns the parent nodes. +``children()`` + Returns children nodes. +``reduce($lambda)`` + Nodes for which the callable does not return false. Since each of these methods returns a new ``Crawler`` instance, you can narrow down your node selection by chaining the method calls:: @@ -568,7 +568,8 @@ narrow down your node selection by chaining the method calls:: return false; } }) - ->first(); + ->first() + ; .. tip:: @@ -632,7 +633,7 @@ Just like links, you select forms with the ``selectButton()`` method:: button. The ``selectButton()`` method can select ``button`` tags and submit ``input`` -tags. It uses several different parts of the buttons to find them: +tags. It uses several parts of the buttons to find them: * The ``value`` attribute value; @@ -735,8 +736,10 @@ configuration option: + xsi:schemaLocation="http://symfony.com/schema/dic/services + http://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/swiftmailer + http://symfony.com/schema/dic/swiftmailer/swiftmailer-1.0.xsd"> @@ -790,7 +793,7 @@ PHPUnit Configuration Each application has its own PHPUnit configuration, stored in the ``app/phpunit.xml.dist`` file. You can edit this file to change the defaults or -create an ``app/phpunit.xml`` file to setup a configuration for your local +create an ``app/phpunit.xml`` file to set up a configuration for your local machine only. .. tip:: @@ -799,8 +802,9 @@ machine only. the ``app/phpunit.xml`` file. By default, only the tests from your own custom bundles stored in the standard -directories ``src/*/*Bundle/Tests`` or ``src/*/Bundle/*Bundle/Tests`` are run -by the ``phpunit`` command, as configured in the ``app/phpunit.xml.dist`` file: +directories ``src/*/*Bundle/Tests``, ``src/*/Bundle/*Bundle/Tests``, +``src/*Bundle/Tests`` are run by the ``phpunit`` command, as configured +in the ``app/phpunit.xml.dist`` file: .. code-block:: xml @@ -811,6 +815,7 @@ by the ``phpunit`` command, as configured in the ``app/phpunit.xml.dist`` file: ../src/*/*Bundle/Tests ../src/*/Bundle/*Bundle/Tests + ../src/*Bundle/Tests @@ -864,6 +869,6 @@ Learn more * :doc:`/cookbook/testing/profiling` * :doc:`/cookbook/testing/bootstrap` -.. _`DemoControllerTest`: https://github.com/symfony/symfony-standard/blob/master/src/Acme/DemoBundle/Tests/Controller/DemoControllerTest.php +.. _`DemoControllerTest`: https://github.com/sensiolabs/SensioDistributionBundle/blob/master/Resources/skeleton/acme-demo-bundle/Acme/DemoBundle/Tests/Controller/DemoControllerTest.php .. _`$_SERVER`: http://php.net/manual/en/reserved.variables.server.php .. _`documentation`: http://phpunit.de/manual/current/en/ diff --git a/book/translation.rst b/book/translation.rst index 0e0dc68852f..e2170afaaf3 100644 --- a/book/translation.rst +++ b/book/translation.rst @@ -68,8 +68,10 @@ enable the ``translator`` in your configuration: + xsi:schemaLocation="http://symfony.com/schema/dic/services + http://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/symfony + http://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> @@ -363,7 +365,8 @@ provides many loaders, including: * ``yml``: YAML file. The choice of which loader to use is entirely up to you and is a matter of -taste. For more options, see :ref:`component-translator-message-catalogs`. +taste. The recommended option is to use ``xliff`` for translations. +For more options, see :ref:`component-translator-message-catalogs`. .. note:: @@ -389,17 +392,26 @@ Fallback Translation Locales Imagine that the user's locale is ``fr_FR`` and that you're translating the key ``Symfony is great``. To find the French translation, Symfony actually -checks translation resources for several different locales: +checks translation resources for several locales: -1. First, Symfony looks for the translation in a ``fr_FR`` translation resource +#. First, Symfony looks for the translation in a ``fr_FR`` translation resource (e.g. ``messages.fr_FR.xliff``); -2. If it wasn't found, Symfony looks for the translation in a ``fr`` translation +#. If it wasn't found, Symfony looks for the translation in a ``fr`` translation resource (e.g. ``messages.fr.xliff``); -3. If the translation still isn't found, Symfony uses the ``fallback`` configuration +#. If the translation still isn't found, Symfony uses the ``fallback`` configuration parameter, which defaults to ``en`` (see `Configuration`_). +.. versionadded:: 2.6 + The ability to log missing translations was introduced in Symfony 2.6. + +.. note:: + + When Symfony doesn't find a translation in the given locale, it will + add the missing translation to the log file. For details, + see :ref:`reference-framework-translator-logging`. + .. _book-translation-user-locale: Handling the User's Locale @@ -419,7 +431,7 @@ via the ``request`` object:: .. tip:: - Read :doc:`/cookbook/session/locale_sticky_session` to learn, how to store + Read :doc:`/cookbook/session/locale_sticky_session` to learn how to store the user's locale in the session. .. index:: @@ -434,7 +446,7 @@ The Locale and the URL ~~~~~~~~~~~~~~~~~~~~~~ Since you can store the locale of the user in the session, it may be tempting -to use the same URL to display a resource in many different languages based +to use the same URL to display a resource in different languages based on the user's locale. For example, ``http://www.example.com/contact`` could show content in English for one user and French for another user. Unfortunately, this violates a fundamental rule of the Web: that a particular URL returns @@ -451,7 +463,7 @@ by the routing system using the special ``_locale`` parameter: # app/config/routing.yml contact: path: /{_locale}/contact - defaults: { _controller: AcmeDemoBundle:Contact:index } + defaults: { _controller: AppBundle:Contact:index } requirements: _locale: en|fr|de @@ -465,7 +477,7 @@ by the routing system using the special ``_locale`` parameter: http://symfony.com/schema/routing/routing-1.0.xsd"> - AcmeDemoBundle:Contact:index + AppBundle:Contact:index en|fr|de @@ -480,7 +492,7 @@ by the routing system using the special ``_locale`` parameter: $collection->add('contact', new Route( '/{_locale}/contact', array( - '_controller' => 'AcmeDemoBundle:Contact:index', + '_controller' => 'AppBundle:Contact:index', ), array( '_locale' => 'en|fr|de', @@ -499,6 +511,11 @@ as the locale for the current request. You can now use the locale to create routes to other translated pages in your application. +.. tip:: + + Read :doc:`/cookbook/routing/service_container_parameters` to learn how to + avoid hardcoding the ``_locale`` requirement in all your routes. + Setting a default Locale ~~~~~~~~~~~~~~~~~~~~~~~~ @@ -521,8 +538,10 @@ the framework: + xsi:schemaLocation="http://symfony.com/schema/dic/services + http://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/symfony + http://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> @@ -546,8 +565,8 @@ the error messages is easy: simply create a translation resource for the To start, suppose you've created a plain-old-PHP object that you need to use somewhere in your application:: - // src/Acme/BlogBundle/Entity/Author.php - namespace Acme\BlogBundle\Entity; + // src/AppBundle/Entity/Author.php + namespace AppBundle\Entity; class Author { @@ -560,17 +579,9 @@ not empty, add the following: .. configuration-block:: - .. code-block:: yaml - - # src/Acme/BlogBundle/Resources/config/validation.yml - Acme\BlogBundle\Entity\Author: - properties: - name: - - NotBlank: { message: "author.name.not_blank" } - .. code-block:: php-annotations - // src/Acme/BlogBundle/Entity/Author.php + // src/AppBundle/Entity/Author.php use Symfony\Component\Validator\Constraints as Assert; class Author @@ -581,15 +592,24 @@ not empty, add the following: public $name; } + .. code-block:: yaml + + # src/AppBundle/Resources/config/validation.yml + AppBundle\Entity\Author: + properties: + name: + - NotBlank: { message: "author.name.not_blank" } + .. code-block:: xml - + + xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping + http://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd"> - + @@ -600,7 +620,7 @@ not empty, add the following: .. code-block:: php - // src/Acme/BlogBundle/Entity/Author.php + // src/AppBundle/Entity/Author.php // ... use Symfony\Component\Validator\Mapping\ClassMetadata; @@ -658,6 +678,177 @@ The translation of database content should be handled by Doctrine through the `Translatable Extension`_ or the `Translatable Behavior`_ (PHP 5.4+). For more information, see the documentation for these libraries. +Debugging Translations +---------------------- + +.. versionadded:: 2.5 + The ``debug:translation`` command was introduced in Symfony 2.5. + +.. versionadded:: 2.6 + Prior to Symfony 2.6, this command was called ``translation:debug``. + +When maintaining a bundle, you may use or remove the usage of a translation +message without updating all message catalogues. The ``debug:translation`` +command helps you to find these missing or unused translation messages for a +given locale. It shows you a table with the result when translating the +message in the given locale and the result when the fallback would be used. +On top of that, it also shows you when the translation is the same as the +fallback translation (this could indicate that the message was not correctly +translated). + +Thanks to the messages extractors, the command will detect the translation +tag or filter usages in Twig templates: + +.. code-block:: jinja + + {% trans %}Symfony2 is great{% endtrans %} + + {{ 'Symfony2 is great'|trans }} + + {{ 'Symfony2 is great'|transchoice(1) }} + + {% transchoice 1 %}Symfony2 is great{% endtranschoice %} + +It will also detect the following translator usages in PHP templates: + +.. code-block:: php + + $view['translator']->trans("Symfony2 is great"); + + $view['translator']->transChoice('Symfony2 is great', 1); + +.. caution:: + + The extractors are not able to inspect the messages translated outside templates which means + that translator usages in form labels or inside your controllers won't be detected. + Dynamic translations involving variables or expressions are not detected in templates, + which means this example won't be analyzed: + + .. code-block:: jinja + + {% set message = 'Symfony2 is great' %} + {{ message|trans }} + +Suppose your application's default_locale is ``fr`` and you have configured ``en`` as the fallback locale +(see :ref:`book-translation-configuration` and :ref:`book-translation-fallback` for how to configure these). +And suppose you've already setup some translations for the ``fr`` locale inside an AcmeDemoBundle: + +.. configuration-block:: + + .. code-block:: xml + + + + + + + + Symfony2 is great + J'aime Symfony2 + + + + + + + .. code-block:: yaml + + # src/Acme/AcmeDemoBundle/Resources/translations/messages.fr.yml + Symfony2 is great: J'aime Symfony2 + + .. code-block:: php + + // src/Acme/AcmeDemoBundle/Resources/translations/messages.fr.php + return array( + 'Symfony2 is great' => 'J\'aime Symfony2', + ); + +and for the ``en`` locale: + +.. configuration-block:: + + .. code-block:: xml + + + + + + + + Symfony2 is great + Symfony2 is great + + + + + + .. code-block:: yaml + + # src/Acme/AcmeDemoBundle/Resources/translations/messages.en.yml + Symfony2 is great: Symfony2 is great + + .. code-block:: php + + // src/Acme/AcmeDemoBundle/Resources/translations/messages.en.php + return array( + 'Symfony2 is great' => 'Symfony2 is great', + ); + +To inspect all messages in the ``fr`` locale for the AcmeDemoBundle, run: + +.. code-block:: bash + + $ php app/console debug:translation fr AcmeDemoBundle + +You will get this output: + +.. image:: /images/book/translation/debug_1.png + :align: center + +It indicates that the message ``Symfony2 is great`` is unused because it is translated, +but you haven't used it anywhere yet. + +Now, if you translate the message in one of your templates, you will get this output: + +.. image:: /images/book/translation/debug_2.png + :align: center + +The state is empty which means the message is translated in the ``fr`` locale and used in one or more templates. + +If you delete the message ``Symfony2 is great`` from your translation file for the ``fr`` locale +and run the command, you will get: + +.. image:: /images/book/translation/debug_3.png + :align: center + +The state indicates the message is missing because it is not translated in the ``fr`` locale +but it is still used in the template. +Moreover, the message in the ``fr`` locale equals to the message in the ``en`` locale. +This is a special case because the untranslated message id equals its translation in the ``en`` locale. + +If you copy the content of the translation file in the ``en`` locale, to the translation file +in the ``fr`` locale and run the command, you will get: + +.. image:: /images/book/translation/debug_4.png + :align: center + +You can see that the translations of the message are identical in the ``fr`` and ``en`` locales +which means this message was probably copied from French to English and maybe you forgot to translate it. + +By default all domains are inspected, but it is possible to specify a single domain: + +.. code-block:: bash + + $ php app/console debug:translation en AcmeDemoBundle --domain=messages + +When bundles have a lot of messages, it is useful to display only the unused +or only the missing messages, by using the ``--only-unused`` or ``--only-missing`` switches: + +.. code-block:: bash + + $ php app/console debug:translation en AcmeDemoBundle --only-unused + $ php app/console debug:translation en AcmeDemoBundle --only-missing + Summary ------- diff --git a/book/validation.rst b/book/validation.rst index 3e0bfa7855f..8d34630e04d 100644 --- a/book/validation.rst +++ b/book/validation.rst @@ -31,7 +31,7 @@ your application:: } So far, this is just an ordinary class that serves some purpose inside your -application. The goal of validation is to tell you whether or not the data +application. The goal of validation is to tell you if the data of an object is valid. For this to work, you'll configure a list of rules (called :ref:`constraints `) that the object must follow in order to be valid. These rules can be specified via a number of @@ -112,7 +112,7 @@ Using the ``validator`` Service Next, to actually validate an ``Author`` object, use the ``validate`` method on the ``validator`` service (class :class:`Symfony\\Component\\Validator\\Validator`). The job of the ``validator`` is easy: to read the constraints (i.e. rules) -of a class and verify whether or not the data on the object satisfies those +of a class and verify if the data on the object satisfies those constraints. If validation fails, a non-empty list of errors (class :class:`Symfony\\Component\\Validator\\ConstraintViolationList`) is returned. Take this simple example from inside a controller:: @@ -192,7 +192,7 @@ Inside the template, you can output the list of errors exactly as needed:
    • getMessage() ?>
    • - +
    .. note:: @@ -303,13 +303,13 @@ to its class and then pass it to the ``validator`` service. Behind the scenes, a constraint is simply a PHP object that makes an assertive statement. In real life, a constraint could be: "The cake must not be burned". In Symfony, constraints are similar: they are assertions that a condition -is true. Given a value, a constraint will tell you whether or not that value +is true. Given a value, a constraint will tell you if that value adheres to the rules of the constraint. Supported Constraints ~~~~~~~~~~~~~~~~~~~~~ -Symfony packages a large number of the most commonly-needed constraints: +Symfony packages many of the most commonly-needed constraints: .. include:: /reference/constraints/map.rst.inc @@ -583,8 +583,11 @@ Getters Constraints can also be applied to the return value of a method. Symfony allows you to add a constraint to any public method whose name starts with -"get" or "is". In this guide, both of these types of methods are referred -to as "getters". +"get", "is" or "has". In this guide, these types of methods are referred to +as "getters". + +.. versionadded:: 2.5 + Support for methods starting with ``has`` was introduced in Symfony 2.5. The benefit of this technique is that it allows you to validate your object dynamically. For example, suppose you want to make sure that a password field @@ -665,9 +668,9 @@ Now, create the ``isPasswordLegal()`` method, and include the logic you need:: .. note:: The keen-eyed among you will have noticed that the prefix of the getter - ("get" or "is") is omitted in the mapping. This allows you to move the - constraint to a property with the same name later (or vice versa) without - changing your validation logic. + ("get", "is" or "has") is omitted in the mapping. This allows you to move + the constraint to a property with the same name later (or vice versa) + without changing your validation logic. .. _validation-class-target: @@ -686,8 +689,8 @@ Validation Groups ----------------- So far, you've been able to add constraints to a class and ask whether or -not that class passes all of the defined constraints. In some cases, however, -you'll need to validate an object against only *some* of the constraints +not that class passes all the defined constraints. In some cases, however, +you'll need to validate an object against only *some* constraints on that class. To do this, you can organize each constraint into one or more "validation groups", and then apply validation against just one group of constraints. @@ -809,21 +812,54 @@ user registers and when a user updates their contact information later: With this configuration, there are three validation groups: -* ``Default`` - contains the constraints in the current class and all - referenced classes that belong to no other group; +``Default`` + Contains the constraints in the current class and all referenced classes + that belong to no other group. + +``User`` + Equivalent to all constraints of the ``User`` object in the ``Default`` + group. This is always the name of the class. The difference between this + and ``Default`` is explained below. + +``registration`` + Contains the constraints on the ``email`` and ``password`` fields only. + +Constraints in the ``Default`` group of a class are the constraints that have either no +explicit group configured or that are configured to a group equal to the class name or +the string ``Default``. + +.. caution:: + + When validating *just* the User object, there is no difference between the ``Default`` group + and the ``User`` group. But, there is a difference if ``User`` has embedded objects. For example, + imagine ``User`` has an ``address`` property that contains some ``Address`` object and that + you've added the :doc:`/reference/constraints/Valid` constraint to this property so that it's + validated when you validate the ``User`` object. -* ``User`` - equivalent to all constraints of the ``User`` object in the - ``Default`` group; + If you validate ``User`` using the ``Default`` group, then any constraints on the ``Address`` + class that are in the ``Default`` group *will* be used. But, if you validate ``User`` using the + ``User`` validation group, then only constraints on the ``Address`` class with the ``User`` + group will be validated. -* ``registration`` - contains the constraints on the ``email`` and ``password`` - fields only. + In other words, the ``Default`` group and the class name group (e.g. ``User``) are identical, + except when the class is embedded in another object that's actually the one being validated. + + If you have inheritance (e.g. ``User extends BaseUser``) and you validate + with the class name of the subclass (i.e. ``User``), then all constraints + in the ``User`` and ``BaseUser`` will be validated. However, if you validate + using the base class (i.e. ``BaseUser``), then only the default constraints in + the ``BaseUser`` class will be validated. To tell the validator to use a specific group, pass one or more group names -as the second argument to the ``validate()`` method:: +as the third argument to the ``validate()`` method:: + + // If you're using the new 2.5 validation API (you probably are!) + $errors = $validator->validate($author, null, array('registration')); - $errors = $validator->validate($author, array('registration')); + // If you're using the old 2.4 validation API, pass the group names as the second argument + // $errors = $validator->validate($author, array('registration')); -If no groups are specified, all constraints that belong in group ``Default`` +If no groups are specified, all constraints that belong to the group ``Default`` will be applied. Of course, you'll usually work with validation indirectly through the form @@ -1186,10 +1222,19 @@ it looks like this:: $emailConstraint->message = 'Invalid email address'; // use the validator to validate the value + // If you're using the new 2.5 validation API (you probably are!) + $errorList = $this->get('validator')->validate( + $email, + $emailConstraint + ); + + // If you're using the old 2.4 validation API + /* $errorList = $this->get('validator')->validateValue( $email, $emailConstraint ); + */ if (count($errorList) == 0) { // this IS a valid email address, do something @@ -1203,13 +1248,13 @@ it looks like this:: // ... } -By calling ``validateValue`` on the validator, you can pass in a raw value and +By calling ``validate`` on the validator, you can pass in a raw value and the constraint object that you want to validate that value against. A full list of the available constraints - as well as the full class name for each constraint - is available in the :doc:`constraints reference ` section . -The ``validateValue`` method returns a :class:`Symfony\\Component\\Validator\\ConstraintViolationList` +The ``validate`` method returns a :class:`Symfony\\Component\\Validator\\ConstraintViolationList` object, which acts just like an array of errors. Each error in the collection is a :class:`Symfony\\Component\\Validator\\ConstraintViolation` object, which holds the error message on its ``getMessage`` method. diff --git a/bundles/index.rst b/bundles/index.rst deleted file mode 100644 index d8f1298f5d8..00000000000 --- a/bundles/index.rst +++ /dev/null @@ -1,13 +0,0 @@ -The Symfony Standard Edition Bundles -==================================== - -.. toctree:: - :hidden: - - SensioFrameworkExtraBundle/index - SensioGeneratorBundle/index - DoctrineFixturesBundle/index - DoctrineMigrationsBundle/index - DoctrineMongoDBBundle/index - -.. include:: /bundles/map.rst.inc diff --git a/bundles/map.rst.inc b/bundles/map.rst.inc deleted file mode 100644 index 92d742ce70c..00000000000 --- a/bundles/map.rst.inc +++ /dev/null @@ -1,5 +0,0 @@ -* :doc:`SensioFrameworkExtraBundle ` -* :doc:`SensioGeneratorBundle ` -* :doc:`DoctrineFixturesBundle ` -* :doc:`DoctrineMigrationsBundle ` -* :doc:`DoctrineMongoDBBundle ` diff --git a/changelog.rst b/changelog.rst index 1ff28341e50..7a213ebf0a2 100644 --- a/changelog.rst +++ b/changelog.rst @@ -13,6 +13,482 @@ documentation. Do you also want to participate in the Symfony Documentation? Take a look at the ":doc:`/contributing/documentation/overview`" article. +January, 2015 +------------- + +New Documentation +~~~~~~~~~~~~~~~~~ + +- `b32accb `_ minor #4935 Fix typos (ifdattic) +- `ad74169 `_ #4628 Varnish cookbook session cookie handling (dbu) +- `50c5a9e `_ #4895 Added configuration of the user provider (peterrehm) +- `4226fc2 `_ #4883 Global dump (nicolas-grekas) +- `3bb7b61 `_ #4645 Remove note that's no longer the case (thewilkybarkid) +- `3293286 `_ #4801 [Cookbook][cache][varnish] be more precise about version differences (dbu) +- `572bf3b `_ #4800 [Cookbook][Security] Hint about createToken can return null (xelaris) +- `528e8e1 `_ #4740 Use AppBundle whenever it's possible (javiereguiluz) +- `08e5ac9 `_ #4658 Debug formatter tweaks (weaverryan) +- `cfad26c `_ #4605 Adding a link to log things in the prod environment (weaverryan) +- `3643ec2 `_ #4723 [Cookbook][Security] document the new AuthenticationUtils (xabbuh) +- `9742b92 `_ #4761 [Cookbook][Security] don't output message from AuthenticationException (xabbuh) +- `a23e7d2 `_ #4643 How to override vendor directory location (gajdaw) +- `99aca45 `_ #4749 [2.3][Book][Security] Add isPasswordValid doc as in 2.6 (xelaris) +- `d9935a3 `_ #4141 Notes about caching pages with a CSRF Form (ricardclau) +- `207f2f0 `_ #4711 [Reference] Add default_locale config description (xelaris) +- `1b0fe77 `_ #4708 Change Apache php-fpm proxy configuration (TeLiXj) +- `7be0dc6 `_ #4681 adding note to assetic cache busting (weaverryan) +- `127ebc1 `_ #4650 Documented the characters that provoke a YAML escaping string (javiereguiluz) +- `0c0b708 `_ #4454 More concrete explanation of validation groups (peterrehm) +- `4fe4f65 `_ #4682 [Reference] document the `````2.5````` validation options (xabbuh) +- `144e5af `_ #4611 Adding a guide about upgrading (weaverryan) +- `01df3e7 `_ #4626 clean up cache invalidation information on the cache chapter (dbu) +- `5f7ef85 `_ #4651 Documented the security:check command (javiereguiluz) + +Fixed Documentation +~~~~~~~~~~~~~~~~~~~ + +- `ea51aeb `_ #4926 Finish #4505: Fixed composer create-project command (windows) (Epskampie) +- `b32accb `_ minor #4935 Fix typos (ifdattic) +- `7e84533 `_ #4886 [Best Pracitices] restore example in the "Service: No Class Parameter" section (u-voelkel) +- `a6b7d72 `_ #4861 Ifdattic's fixes (ifdattic) +- `8ef3477 `_ #4856 [Components][Debug] fix DebugClassLoader namespace (xabbuh) +- `b9359a2 `_ #4905 Update routing.rst (IlhamiD) +- `9fee9ee `_ #4746 Revert #4651 for 2.3 branch (xelaris) +- `5940d52 `_ #4735 [BestPractices] remove @Security annotation for Symfony 2.3 (xabbuh) +- `ce37b96 `_ #4771 [QuickTour] use the debug:router command name (xabbuh) +- `ffe3425 `_ #4765 [Book][Forms] avoid the request service where possible (xabbuh) +- `36f2e1f `_ #4757 [Components][ClassLoader] don't show deprecated usage of ``Yaml::parse()`` (xabbuh) +- `d8e8d75 `_ #4756 [Components][Config] don't show deprecated usage of ``Yaml::parse()`` (xabbuh) +- `b143754 `_ #4744 [Book][Security] Update code example to fit description (xelaris) +- `310f4ae `_ #4639 Update by_reference.rst.inc (docteurklein) + +Minor Documentation Changes +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- `2cff942 `_ #4878 [Book][Security] Remove out-dated anchor (xelaris) +- `a97646f `_ #4882 Remove horizontal scrollbar (ifdattic) +- `c24c787 `_ #4931 Remove horizontal scrollbar (ifdattic) +- `83696b8 `_ #4934 Fixes for 2.3 branch (ifdattic) +- `99d225b `_ #4943 Fixes for 2.3 branch (ifdattic) +- `3907af6 `_ #4944 Fix formatting (ifdattic) +- `137ba72 `_ #4945 Fixes for 2.3 branch (ifdattic) +- `5a53e87 `_ #4946 Remove horizontal scrollbar (ifdattic) +- `b32accb `_ #4935 Fix typos (ifdattic) +- `04090c0 `_ #4936 fixed typo (issei-m) +- `0fa9cbd `_ #4937 Keeping documentation consistent (thecatontheflat) +- `3921d70 `_ #4918 Quick proofread of the email cookbook (weaverryan) +- `768650e `_ #4932 Add missing comma in array (ifdattic) +- `418a73b `_ #4922 Fix typo: missing space (ifdattic) +- `30ecdde `_ #4921 Fixes for 2.5 branch (ifdattic) +- `d1103a8 `_ #4919 Fix code examples (ifdattic) +- `20d80c3 `_ #4916 Fixes for 2.3 branch (ifdattic) +- `d7acccf `_ #4914 Fix typo, remove horizontal scrollbar (ifdattic) +- `fc776ab `_ #4894 Align methods in YAML example (ifdattic) +- `bd279f6 `_ #4908 Set twig service as private (ifdattic) +- `37fd035 `_ #4899 Fix typo: looks => look (ifdattic) +- `fbaeecd `_ #4898 added Kévin Dunglas as a merger for the Serializer component (fabpot) +- `7c66a8b `_ #4893 Move annotations example to front (ifdattic) +- `2b7e5ee `_ #4891 fixed typo (acme -> app) (adiebler) +- `00981de `_ #4890 Fixed typo (beni0888) +- `dc87147 `_ #4876 Remove horizontal scrollbar (ifdattic) +- `f5f3c1b `_ #4865 Removed literals for bundle names (WouterJ) +- `33914c9 `_ #4859 [Components][EventDispatcher] don't explain deprecated `````getName()````` method (xabbuh) +- `9a6d7b9 `_ #4831 Update override.rst (ifdattic) +- `f9c2d69 `_ #4803 [Book][Translation] Added tip for routing params (xelaris) +- `2f41c9e `_ #4887 Typo (XitasoChris) +- `3774a37 `_ #4881 Remove 'acme' (ifdattic) +- `d85fa76 `_ #4880 Remove duplicate link, introduction.rst (Quberik) +- `6a15077 `_ #4874 Remove trailing whitespace (WouterJ) +- `80bef5a `_ #4873 [BestPractices] fix typo (xabbuh) +- `6cffa4e `_ #4866 Remove horizontal scrollbar (ifdattic) +- `65b0822 `_ #4798 Add version added note for the debug:event-dispatcher command (adamelso) +- `bcf1508 `_ #4785 [Book][Security] add back old anchors (xabbuh) +- `4143076 `_ #4872 [BestPractices] fix merge after removing @Security in 2.3 (xabbuh) +- `48835de `_ #4767 [2.6] Removed 2.4 versionadded as version is deprecated (WouterJ) +- `240a981 `_ #4764 [Reference][Forms] move cautions to make them visible (xabbuh) +- `cf3d38a `_ #4731 [Book][Testing] bump required PHPUnit version (xabbuh) +- `4f47dec `_ #4837 Monolog Cookbook Typo Fix: "allows to" should be "allows you to" (mattjanssen) +- `c454fd2 `_ #4857 Add custom link labels where Cookbook articles titles looked wrong (javiereguiluz) +- `17989fd `_ #4860 [Components][HttpKernel] replace API link for SwiftmailerBundle (xabbuh) +- `84839ba `_ #4829 Fix code example (ifdattic) +- `e347ec8 `_ #4819 Removed a leftover comma in security config sample (javiereguiluz) +- `11b9d23 `_ #4772 Tweaks to the new form csrf caching entry (weaverryan) +- `c04ed79 `_ #4848 Fix typo: BLOG => BLOB (ifdattic) +- `f9c1389 `_ #4845 Update security.rst (meelijane) +- `9680ec0 `_ #4844 Update routing.rst (bglamer) +- `c243d00 `_ #4843 Fixed typo (beni0888) +- `5b91653 `_ #4843 Fixed typo (beni0888) +- `13ffb83 `_ #4835 Fixed broken link (SofHad) +- `d2a67ac `_ #4826 Fixed 404 page (SofHad) +- `f34fc2d `_ #4825 Fixed the 404 not found error (SofHad) +- `467c538 `_ #4824 fix SQL: table names (e-moe) +- `91a89b7 `_ #4821 Fixed typo (SofHad) +- `f7179df `_ #4818 [Routing] Removed deprecated usage (WouterJ) +- `82bce29 `_ #4815 Update translation.rst (ifdattic) +- `892586b `_ #4808 Email message instantiation changed to a more 'symfonysh' way. (alebo) +- `e913808 `_ #4802 [Cookbook][Routing] Fixed typo (xelaris) +- `6522145 `_ #4799 Fix markup (WouterJ) +- `a42e5b6 `_ #4778 Update templating.rst (ifdattic) +- `bd7d246 `_ #4752 [Book][Validation] clarify group validation (xabbuh) +- `236c26f `_ #4796 Update service_container.rst (ifdattic) +- `f85c44c `_ #4795 Remove horizontal scrollbar (ifdattic) +- `45189bb `_ #4792 [BestPractices] add filename to codeblock (xelaris) +- `fccea1d `_ #4791 Fix heading level in form_login_setup.rst (xelaris) +- `74c3a35 `_ #4788 Controller is a callable (timglabisch) +- `eb56376 `_ #4781 [Serializer] Bad variable name in example (arno14) +- `28571fc `_ #4780 Add missing semicolon (NightFox7) +- `32bd0b1 `_ #4777 Update templating.rst (ifdattic) +- `dc5d8f8 `_ #4760 Update routing.rst (ifdattic) +- `4e880c1 `_ #4755 fix typo (xabbuh) +- `463c30b `_ #4751 [BestPractices] fix alignment of YAML values (xelaris) +- `1972757 `_ #4775 Corrected validation information on inheritance (peterrehm) +- `f4f8621 `_ #4762 [Cookbook][Configuration] update text to use SetHandler (not ProxyPassMatch) (xabbuh) +- `43543bb `_ #4748 Re-reading private service section (weaverryan) +- `e447e70 `_ #4743 [Book][Security] Fix typo and remove redundant sentence (xelaris) +- `97a9c7b `_ #4742 Formatting fix (WouterJ) +- `9819113 `_ #4702 Clarify tip for creating a new AppBundle (xelaris) +- `8f2fe87 `_ #4683 [Reference] update the configuration reference (xabbuh) +- `e889813 `_ #4677 Add exception to console exception log (adrienbrault) +- `9958c41 `_ #4656 Tried to clarify private services (WouterJ) +- `1d5966c `_ #4703 Fix representation (ifdattic) +- `aa9d982 `_ #4697 Set twig service as private (ifdattic) +- `ece2c81 `_ #4722 Improve readability (ifdattic) +- `dcc9516 `_ #4725 Remove horizontal scrollbar (ifdattic) +- `3eb14aa `_ #4727 Renamed example: "Acme\BlogBundle" -> "AppBundle" (muxator) +- `25dd825 `_ #4730 Fix typo: as => is (ifdattic) +- `760a441 `_ #4734 [BestPractices] add missing comma (xabbuh) +- `caa2be6 `_ #4737 [Book][Security] add missing versionadded directive (xabbuh) +- `8c1afb9 `_ #4738 [Contributing][Code] update year in license (xabbuh) +- `4ad72d0 `_ #4741 use the doc role for internal links (jms85, xabbuh) +- `57fdea6 `_ #4729 Fixed typo in factories.rst (nietonfir) + +December, 2014 +-------------- + +New Documentation +~~~~~~~~~~~~~~~~~ + +- `00a13d6 `_ #4606 Completely re-reading the security book (weaverryan) +- `aa88f99 `_ #4609 Adding details about the changes to the PdoSessionHandler in 2.6 (weaverryan) +- `bd65c3c `_ #4673 [Reference] add validation config reference section (xabbuh) +- `55a32cf `_ #4173 use a global Composer installation (xabbuh) +- `c5e409b `_ #4526 Deploy Symfony application on Platform.sh. (GuGuss) +- `ddd56ea `_ #4449 Added cache_busting to default asset config (GeertDD) +- `c837ea1 `_ #4665 Documented the console environment variables (javiereguiluz) +- `0e45e29 `_ #4655 Document new progressbar methods (javiereguiluz) +- `f4a7196 `_ #4627 Rewrite the varnish cookbook article (dbu) +- `92a186d `_ #4654 Rewritten from scratch the chapter about installing Symfony (javiereguiluz) +- `90ef4ec `_ #4580 Updated installation instructions to use the new Symfony Installer (javiereguiluz) +- `f591e6e `_ #4532 GetResponse*Events stop after a response was set (Lumbendil) +- `a09fd7b `_ #4485 Added documentation about the DebugFormatter helper (WouterJ) +- `d327bae `_ #4557 Update pdo_session_storage.rst (spbentz) +- `71495e8 `_ #4528 Update web_server_configuration.rst (thePanz) +- `3b9d60d `_ #4517 [Reference] document configurable PropertyAccessor arguments (xabbuh) +- `9b330ef `_ #4507 Comply with best practices, Round 2 (WouterJ) +- `39a36bc `_ #4405 Finish 3744 (mickaelandrieu, xabbuh) +- `5363542 `_ #4188 Updated documentation regarding the SecurityContext split (iltar) +- `f30f753 `_ #4050 [Translation] added logging capability. (aitboudad) +- `db35c42 `_ #4591 Instructions for setting SYMFONY_ENV on Heroku (dzuelke) +- `8bba316 `_ #4457 [RFC] Clarification on formatting for bangs (!) (bryanagee) + +Fixed Documentation +~~~~~~~~~~~~~~~~~~~ + +- `79db0b9 `_ #4699 Use new security.authorization_checker service (xelaris) +- `9c819b4 `_ #4713 [Security] Removed deprecated example about SecurityContext (iltar) +- `153565e `_ #4707 [Cookbook] Fix XML example for RTE (dunglas) +- `cad4d3f `_ #4582 Completed the needed context to successfully test commands with Helpers (peterrehm) +- `a137918 `_ #4641 Add missing autoload include in basic console application example (senkal) +- `0de8286 `_ #4513 [Contributing] update contribution guide for 2.7/3.0 (xabbuh) +- `8b611e2 `_ #4598 [ExpressionLanguage] add missing argument (xabbuh) +- `7ea4b10 `_ #4646 Update the_controller.rst (teggen) +- `a2ea256 `_ #4637 fixed StringExpressionLanguageProvider code example #4636 (danieleorler) +- `63be343 `_ #4630 [OptionsResolver] Fix namespace (xavren) +- `baf61a0 `_ #4623 [OptionsResolver] Fix Namespace link (xavren) +- `8246693 `_ #4613 Change refering block name from content to body (martin-cerny) +- `1750b9b `_ #4599 [Contributing] fix feature freeze dates (xabbuh) +- `8e2e988 `_ #4603 Replace form_enctype(form) with form_start(form). (xelaris) +- `7acf27c `_ #4552 required PHPUnit version in the docs should be updated to 4.2 (or later)... (jzawadzki) +- `df60ba7 `_ #4548 Remove ExpressionLanguage reference for 2.3 version (dangarzon) +- `727c92a `_ #4594 Missing attribute 'original' (Marcelsj) +- `97a9c43 `_ #4533 Add command to make symfony.phar executable. (xelaris) + +Minor Documentation Changes +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- `8bd694f `_ #4709 [Reference] fix wording (xabbuh) +- `1bd9ed4 `_ #4721 [Cookbook][Composer] fix note directive (xabbuh) +- `5055ef4 `_ #4715 Improve readability (ifdattic) +- `d3d6d22 `_ #4716 Fix typo: con => on (ifdattic) +- `afe8684 `_ #4720 Link fixed (kuldipem) +- `4b442a0 `_ #4695 Misc changes (ifdattic) +- `0db36ea `_ #4706 Fix typo: than in Twig => than Twig templates (ifdattic) +- `94b833e `_ #4679 General grammar and style fixes in the book (frne) +- `3f3464f `_ #4689 Update form_customization.rst (rodrigorigotti) +- `8d32393 `_ #4691 replace "or" with "," (timglabisch) +- `9b4d747 `_ #4670 Change PHPUnit link to avoid redirect to homepage (xelaris) +- `8ccffb0 `_ #4669 Harmonize PHPUnit version to 4.2 or above (xelaris) +- `84bf5e5 `_ #4667 Remove redundant "default" connection (xelaris) +- `ceca63f `_ #4653 update ordered list syntax (xabbuh) +- `459875b `_ #4550 Ref #3903 - Normalize methods listings (ternel) +- `87365fa `_ #4648 Update forms.rst (keefekwan) +- `70f2ae8 `_ #4640 [Book] link to the API documentation (xabbuh) +- `95fc487 `_ #4608 Removing some installation instructions (weaverryan) +- `96455e6 `_ #4539 Normalization of method listings (pedronofuentes) +- `bd44e6b `_ #4664 Spelling mistake tens to tons (albabar) +- `3b6341a `_ #4663 Removed double `````firewall_restriction````` entry (vlad-ghita) +- `815e0bf `_ #4551 Normalize the method listings on version 2.5 (pedronofuentes) +- `48cc9cd `_ #4647 Update controllers.rst (keefekwan) +- `2efed8c `_ #4660 Fix indentation of YAML example (xelaris) +- `b55ec30 `_ #4659 Fixed some code indentation (javiereguiluz) +- `18af18b `_ #4652 replace Symfony2 with Symfony (xabbuh) +- `a70c489 `_ #4649 Linked the PDO/DBAL Session article from the Doctrine category (javiereguiluz) +- `f672a66 `_ #4625 Added '-ing' title ending to unify titles look (kix) +- `9600950 `_ #4617 [Filesystem] filesystem headlines match method names (xabbuh) +- `8b006bb `_ #4607 [Best Practices] readd mistakenly removed label (xabbuh) +- `7dcce1b `_ #4585 When explaining how to install dependencies for running unit tests, (carlosbuenosvinos) +- `1c9270d `_ #4568 Update Symfony reference to 2.6 version (dangarzon) +- `33ca697 `_ #4561 Use the new build env on Travis (joshk) +- `107610e `_ #4531 [symfony] [Hackday] Fixed typos (pborreli) +- `3b1611d `_ #4519 remove service class parameters (xabbuh) +- `3bd17af `_ #4518 [Components][DependencyInjection] backport service factory improvements (xabbuh) +- `d203e5a `_ #4495 [Best Practices][Business Logic] link to a bundle's current (not master) docs (xabbuh) +- `0a9c146 `_ #4422 Fix typos in code (ifdattic) +- `4f0051d `_ #4574 fixed little typo (adridev) + +November, 2014 +-------------- + +New Documentation +~~~~~~~~~~~~~~~~~ + +- `33554fc `_ #4456 New validation API usage in Class Constraint Validator (skwi) +- `135aae6 `_ #4433 Completely re-reading the controller chapter (weaverryan) +- `f748378 `_ #4498 Use new factory syntax (WouterJ) +- `59f0374 `_ #4490 Documented ExpressionLanguage extensibility (WouterJ) +- `ed241ab `_ #4487 Documented html5 option (WouterJ) +- `48a5af3 `_ #4486 Renamed empty_value to placeholder (WouterJ) +- `422e0f1 `_ #4465 Modifying the best practice to use form_start() instead of
    `_ #4463 [BestPractices] Proposing that we make the service names *just* a little bit longer (weaverryan) +- `9a22865 `_ #4446 [Book][Templating] refer to the VarDumper component for dump() (xabbuh) +- `ed5c61f `_ #4411 Added a reference to the Bootstrap 3 form theme (javiereguiluz) +- `766e01f `_ #4169 [Components][Form] document $deep and $flatten of getErrors() (xabbuh) +- `1d88a1b `_ #4443 Added the release dates for the upcoming Symfony 3 versions (javiereguiluz) +- `3329bd2 `_ #4424 [#4243] Tweaks to the new var-dumper component (weaverryan, nicolas-grekas) +- `9caea6f `_ #4336 [Form] Add entity manager instance support for em option (egeloen) +- `f2ab245 `_ #4374 [WCM] Revamped the Quick Start tutorial (javiereguiluz) +- `2c190ed `_ #4427 Update most important book articles to follow the best practices (WouterJ) +- `12a09ab `_ #4377 Added interlinking and fixed install template for reusable bundles (WouterJ) +- `8259d71 `_ #4425 Updating component usage to use composer require (weaverryan) +- `0e80aba `_ #4369 [reference][configuration][security]Added key_length for pbkdf2 encoder (Guillaume-Rossignol) +- `d1afa4d `_ #4243 [WIP] var-dumper component (nicolas-grekas) +- `5165419 `_ #4295 [Security] Hidden front controller for Nginx (phansys) +- `23f790a `_ #4058 Skip console commands from event listeners (tPl0ch) +- `4b98d48 `_ #3386 [Translation] added method to expose collected message (Grygir) +- `242d4f6 `_ #4319 Documentation for debug:event-dispatcher command (matthieuauger) + +Fixed Documentation +~~~~~~~~~~~~~~~~~~~ + +- `9d599a0 `_ minor #4544 #4273 - fix doctrine version in How to Provide Model Classes for several Doctrine Implementations cookbook (ternel) +- `6aabece `_ #4273 - fix doctrine version in How to Provide Model Classes for several Doctrine Implementations cookbook +- `e96ebd3 `_ #4522 Add missing brackets to PropertyAccessor examples (loonytoons) +- `4f66d48 `_ #4506 SetDescription required on Product entities (yearofthegus) +- `85bf906 `_ #4444 fix elseif statement (MightyBranch) +- `ad14e78 `_ #4494 Updated the Symfony Installer installation instructions (javiereguiluz) +- `7cc4287 `_ #4442 replace doc role for bundle docs with external ref (xabbuh) +- `33bf462 `_ #4407 [Components][Console] array options need array default values (xabbuh) +- `2ab2e1f `_ #4342 Reworded a misleading Doctrine explanation (javiereguiluz) + +Minor Documentation Changes +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- `a109c4b `_ #4537 Update link to remove absolute URL (jms85, dangarzon) +- `05f5dba `_ #4536 Add Ryan Weaver as 10th core team member (ifdattic) +- `7b1ff2a `_ #4554 Changed url to PHP-CS-FIXER repository (jzawadzki) +- `9d599a0 `_ #4544 bug #4273 - fix doctrine version in How to Provide Model Classes for several Doctrine Implementations cookbook (ternel) +- `7b3500c `_ #4542 Update conventions.rst (csuarez) +- `5aaba1e `_ #4529 Best Practices: Update link title to match cookbook article title (dangarzon) +- `ab8e7f5 `_ #4530 Book: Update link title to match cookbook article title (dangarzon) +- `bf61658 `_ #4523 Add missing semicolons to PropertyAccess examples (loonytoons) +- `8beadce `_ #4496 [Book][Security] link to a bundle's current (not master) docs (xabbuh) +- `43809b1 `_ #4479 remove versionadded directives for old versions (xabbuh) +- `5db8386 `_ #4462 [Reference] Fixed lots of things using the review bot (WouterJ) +- `dbfaac1 `_ #4459 Fix up the final sentence to be a bit cleaner. (micheal) +- `3761e50 `_ #4514 [Contributing][Documentation] typo fix (xabbuh) +- `21afb4c `_ #4445 Removed unnecessary use statement (Alex Salguero) +- `3969fd6 `_ #4432 [Reference][Twig] tweaks to the Twig reference (xabbuh) +- `188dd1f `_ #4400 Continues #4307 (SamanShafigh, WouterJ) +- `c008733 `_ #4399 Explain form() and form_widget() in form customization (oopsFrogs, WouterJ) +- `2139754 `_ #4253 Adder and remover sidenote (kshishkin) +- `b81eb4d `_ #4488 Terrible mistake! Comma instead of semicolon... (nuvolapl) +- `0ee3ae7 `_ #4481 [Cookbook][Cache] add syntax highlighting for Varnish code blocks (xabbuh) +- `0577559 `_ #4418 use the C lexer for Varnish config examples (xabbuh) +- `97d8f61 `_ #4403 Improved naming (WouterJ) +- `6298595 `_ #4453 Fixed make file (WouterJ) +- `0c7dd72 `_ #4475 Fixed typos (pborreli) +- `b847b2d `_ #4480 Fix spelling (nurikabe) +- `0d91cc5 `_ #4461 Update doctrine.rst (guiguiboy) +- `81fc1c6 `_ #4448 [Book][HTTP Cache] moved inlined URL to the bottom of the file (xabbuh) +- `6995b07 `_ #4435 consistent table headlines (xabbuh) +- `0380d34 `_ #4447 [Book] tweaks to #4427 (xabbuh) +- `eb0d8ac `_ #4441 Updated first code-block``::`` bash (Nitaco) +- `41bc061 `_ #4106 removed references to documentation from external sources (fabpot, WouterJ) +- `c9a8dff `_ #4352 [Best Practices] update best practices index (xabbuh) +- `8a93c95 `_ #4437 Correct link to scopes page (mayeco) +- `91eb652 `_ #4438 Fix typo: Objected => Object (ifdattic) +- `5d6d0c2 `_ #4436 remove semicolons in PHP templates (xabbuh) +- `97c4b2e `_ #4434 remove unused label (xabbuh) +- `4be6786 `_ #4326 [Components][Form] Grammar improvement (fabschurt) +- `a27238e `_ #4313 Improved and fixed twig reference (WouterJ) +- `1ce9dc5 `_ #4398 A few small improvements to the EventDispatcher Component docs (GeertDD) +- `42abc66 `_ #4421 [Best Practices] removed unused links in business-logic (77web) +- `61c0bc5 `_ #4419 [DependencyInjection] Add missing space in code (michaelperrin) + +October, 2014 +------------- + +New Documentation +~~~~~~~~~~~~~~~~~ + +- `d7ef1c7 `_ #4348 Updated information about handling validation of embedded forms to Valid... (peterrehm) +- `691b13d `_ #4340 [Cookbook][Web Server] add sidebar for the built-in server in VMs (xabbuh) +- `bd85865 `_ #4299 [Serializer] Handle circular references. symfony/symfony#12098. (dunglas) +- `d79c48d `_ #4280 [Cookbook][Cache] Added config example for Varnish 4.0 (thierrymarianne) +- `5849f7f `_ #4168 [Components][Form] describe how to access form errors (xabbuh) +- `c10e9c1 `_ #4371 Added a code example for emailing on 4xx and 5xx errors without 404's (weaverryan) +- `1117741 `_ #4159 [WCM][OptionsResolver] Adjusted the OptionsResolver documentation to describe the 2.6 API (webmozart, peterrehm) +- `0c57939 `_ #4327 First import of the "Official Best Practices" book (javiereguiluz) +- `2cd6646 `_ #4293 Document error page preview (Symfony ~2.6) (mpdude) +- `142c826 `_ #4005 [Cookbook][Web server] description for running PHP's built-in web server in the background (xabbuh) +- `8dc90ef `_ #4224 [Components][HttpKernel] outline implications of the kernel.terminate event (xabbuh) +- `d3b5ba2 `_ #4085 [Component][Forms] add missing features introduced in 2.3 (xabbuh) +- `f433e64 `_ #4099 Composer installation verbosity tip (dannykopping) +- `f583a45 `_ #4204 [Reference][Constraints] validate `````null````` (Expression constraint in 2.6) (xabbuh) +- `925a162 `_ #4290 Updating library/bundle install docs to use "require" (weaverryan) +- `86c67e8 `_ #4233 2.5 Validation API changes (nicolassing, lashae, Rootie, weaverryan) +- `0f34bb8 `_ #3956 [Command] Added LockHelper (lyrixx) +- `278de83 `_ #3930 [Console] Add Process Helper documentation (romainneutron) +- `44f570b `_ #4294 Improve cookbook entry for error pages in 2.3~ (mpdude) +- `3b6c2b9 `_ #4269 [Cookbook][External Parameters] Enhance content (bicpi) +- `25a17fe `_ #4264 [#4003] A few more form_themes config changes (weaverryan) +- `5b65654 `_ #3912 [Security] Added remote_user firewall info and documentation for pre authenticated firewalls (Maxime Douailin, mdouailin) +- `62bafad `_ #4246 [Reference] add description for the `````validation_groups````` option (xabbuh) +- `5d505bb `_ #4206 Added note about ProgressBar changes (kbond) +- `c2342a7 `_ #4241 [Form] Added information about float choice lists (peterrehm) + +Fixed Documentation +~~~~~~~~~~~~~~~~~~~ + +- `dde6919 `_ #4390 Update custom_constraint.rst (luciantugui) +- `68a2c7b `_ #4381 Updated Valid constraint reference (inso) +- `dbb25b9 `_ #4379 [OptionsResolver] Fix wrong namespace in example (rybakit) +- `db01e57 `_ #4362 Missing apostrophe in source example. (astery) +- `d49d51f `_ #4350 Removed extra parenthesis (sivolobov) +- `e6d7d8f `_ #4315 Update choice.rst (odolbeau) +- `1b15d57 `_ #4300 [Components][PropertyAccess] Fix PropertyAccessorBuilder usage (Thierry Geindre) +- `061324f `_ #4297 [Cookbook][Doctrine] Fix typo in XML configuration for custom SQL functions (jdecool) +- `f81b7ad `_ #4292 Fixed broken external link to DemoController Test (danielsan) +- `9591a04 `_ #4284 change misleading language identifier (Kristof Van Cauwenbergh, kristofvc) + +Minor Documentation Changes +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- `217bf5f `_ #4353 [Cookbook][Controller] fix route prefix in PHP code example (xabbuh) +- `a4f7d51 `_ #4396 Corrected latin abbreviation (GeertDD) +- `ebf2927 `_ #4387 Inline condition removed for easier reading (acidjames) +- `aa70028 `_ #4375 Removed the redundant usage of layer. (micheal) +- `f3dd676 `_ #4394 update Sphinx extension submodule reference (xabbuh) +- `6406a27 `_ #4391 Removed unused use UsernameNotFoundException (boekkooi) +- `9e03f2d `_ #4388 Minor spelling fix (GeertDD) +- `4dfd607 `_ #4356 Remove incoherence between Doctrine and Propel introduction paragraphs (arnaugm) +- `1d71332 `_ #4344 [Templating] Added a sentence that explains what a Template Helper is (iltar) +- `22b9b27 `_ #4372 Tweaks after proofreading the 2.6 OptionsResolver stuff (weaverryan, WouterJ) +- `9a76309 `_ #4384 fix typo (kokoon) +- `eb752cc `_ #4363 Fixed sentence (WouterJ) +- `3e8aa59 `_ #4376 Cleaned up javascript code (flip111) +- `06e7c5f `_ #4364 changed submit button label (OskarStark) +- `d1810ca `_ #4357 fix Twig-extensions links (mhor) +- `e2e2915 `_ #4359 Added missing closing parenthesis to example. (mattjanssen) +- `f1bb8bb `_ #4358 Fixed link to documentation standards (sivolobov) +- `65c891d `_ #4355 Missing space (ErikSaunier) +- `7359cb4 `_ #4196 Clarified the bundle base template bit. (Veltar) +- `6ceb8cb `_ #4345 Correct capitalization for the Content-Type header (GeertDD) +- `3e4c92a `_ #4104 Use ${APACHE_LOG_DIR} instead of /var/log/apache2 (xamgreen) +- `3da0776 `_ #4338 ESI Variable Details Continuation (Farkie, weaverryan) +- `9e0e12d `_ Merge branch '2.5' +- `7f461d2 `_ #4325 [Components][Form] Correct a typo (fabschurt) +- `d162329 `_ #4276 [Components][HttpFoundation] Make a small grammatical adjustment (fabschurt) +- `459052d `_ Merge remote-tracking branch 'origin/2.4' into 2.5 +- `69bfac1 `_ #4322 [Components][DependencyInjection] Correct a typo: replace "then" by "the" (fabschurt) +- `8073239 `_ #4318 [Cookbook][Bundles] Correct a typo: remove unnecessary "the" word (fabschurt) +- `228111b `_ #4316 Remove horizontal scrollbar (ifdattic) +- `34e22d6 `_ #4317 Remove horizontal scrollbar and change event name to follow conventions (ifdattic) +- `090afab `_ #4287 support Varnish in configuration blocks (xabbuh) +- `1603463 `_ #4306 Improve readability (ifdattic) +- `e5fed9d `_ #4303 Fix spelling (nurikabe) +- `31d7905 `_ #4302 View documentation had a reference to the wrong twig template (milan) +- `ef11ef4 `_ #4250 Clarifying Bundle Best Practices is for *reusable* bundles (weaverryan) +- `430eabf `_ #4298 Book HTTP Fundamentals routing example fixed with routing.xml file (peterkokot) +- `a535c9f `_ #4285 Update security.rst (placid2000) +- `7ab6df9 `_ #4237 Finished #3886 (ahsio, WouterJ) +- `990b453 `_ #4245 [Contributing] tweaks to the contribution chapter (xabbuh) + +September, 2014 +--------------- + +New Documentation +~~~~~~~~~~~~~~~~~ + +- `e8a1501 `_ #4201 [Components][Process] `````mustRun()````` documentation (xabbuh) +- `eac0e51 `_ #4195 Added a note about the total deprecation of YUI (javiereguiluz) +- `e44c791 `_ #4047 Documented info method (WouterJ) +- `2962e14 `_ #4003 [Twig][Form] Moved twig.form.resources to a higher level (stefanosala) +- `d5d46ec `_ #4017 Clarify that route defaults don't need a placeholder (iamdto) +- `1d56da4 `_ #4239 Remove redundant references to trusting HttpCache (thewilkybarkid) +- `c306b68 `_ #4249 provide node path on configuration (desarrolla2) +- `9f0f14e `_ #4210 Move debug commands to debug namespace (matthieuauger) +- `9b4b36f `_ #4236 Javiereguiluz bundle install instructions (WouterJ) +- `ea068c2 `_ #4202 [Reference][Constraints] caution on `````null````` values in Expression constraint (xabbuh) +- `a578de9 `_ #4223 Revamped the documentation about "Contributing Docs" (javiereguiluz) +- `de60dbe `_ #4182 Added note about exporting SYMFONY_ENV (jpb0104) +- `a8dc2bf `_ #4166 Translation custom loaders (raulfraile) + +Fixed Documentation +~~~~~~~~~~~~~~~~~~~ + +- `c289ac8 `_ #4279 Double-quotes instead of single quotes (UnexpectedValueException in Windows 8) (galdiolo) +- `5500e0b `_ #4267 Fix error in bundle installation standard example (WouterJ) +- `082755d `_ #4240 [Components][EventDispatcher] fix ContainerAwareEventDispatcher definition (xabbuh) +- `2319d6a `_ #4213 Handle "constraints" option in form unit testing (sarcher) +- `c567707 `_ #4222 [Components][DependencyInjection] do not reference services in parameters (xabbuh) +- `02d1091 `_ #4209 Fix method for adding placholders in progressBar (danez) + +Minor Documentation Changes +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- `df16779 `_ #4226 add note about parameters in imports (xabbuh) +- `c332063 `_ #4278 Missing word in DependencyInjection => Types of Injection (fabschurt) +- `287c7bf `_ #4275 added Nicolas to the list of mergers for the new var dumper component (fabpot) +- `3a4e226 `_ #4263 Fixed typo (zebba) +- `187c255 `_ #4259 Added feature freeze dates for Symfony versions (javiereguiluz) +- `efc1436 `_ #4247 [Reference] link translation DIC tags to components section (xabbuh) +- `17addb1 `_ #4238 Finished #3924 (WouterJ) +- `19a0c35 `_ #4252 Removed unnecessary comma (allejo) +- `9fd91d6 `_ #4219 Cache needs be cleared (burki94) +- `025f02e `_ #4220 Added a note about the side effects of enabling both PHP and Twig (javiereguiluz) +- `46fcb67 `_ #4218 Caution that roles should start with ``ROLE_`` (jrjohnson) +- `78eea60 `_ #4077 Removed outdated translations from the official list (WouterJ) +- `2cf9e47 `_ #4171 Fixed version for composer install (zomberg) +- `5c62b36 `_ #4216 Update Collection.rst (azarzag) +- `8591b87 `_ #4215 Fixed code highlighting (WouterJ) +- `8f01195 `_ #4212 Missing backtick, thanks to @Baptouuuu (WouterJ) +- `f276e34 `_ #4205 replace "Symfony2" with "Symfony" (xabbuh) +- `6db13ac `_ #4208 Added a note about the lacking features of Yaml Component (javiereguiluz) +- `f8c6201 `_ #4200 Moved 'contributing' images to their own directory (javiereguiluz) +- `b4650fa `_ #4199 fix name of the Yaml component (xabbuh) +- `9d89bb0 `_ #4190 add link to form testing chapter in test section (xabbuh) + August, 2014 ------------ @@ -26,11 +502,15 @@ New Documentation - `041105c `_ #3883 Removed redundant POST request exclusion info (ryancastle) - `4f9fef6 `_ #4000 [Cookbook] add cookbook article for the server:run command (xabbuh) - `4ea4dfe `_ #3915 [Cookbook][Configuration] documentation of Apache + PHP-FPM (xabbuh) +- `79cb4f1 `_ #4069 document the namespace alias (dbu) - `08bed5f `_ #4128 Finished #3759 (WouterJ) - `4d5adaa `_ #4125 Added link to JSFiddle example (WouterJ) - `75bda4b `_ #4124 Rebased #3965 (WouterJ) +- `e2f13a4 `_ #4039 [DomCrawler] Added node name getter (fejese) +- `3f92d5f `_ #3966 [Cookbook][Controller] Add note about invokable controller services (kbond) - `fdb8a32 `_ #3950 [Components][EventDispatcher] describe the usage of the RegisterListenersPass (xabbuh) - `7e09383 `_ #3940 Updated docs for Monolog "swift" handler in cookbook. (phansys) +- `9d7c999 `_ #3895 [Validator] Support "maxSize" given in KiB (jeremy-derusse) - `8adfe98 `_ #3894 Rewrote Extension & Configuration docs (WouterJ) - `cafea43 `_ #3888 Updated the example used to explain page creation (javiereguiluz) - `df0cf68 `_ #3885 [RFR] Added "How to Organize Configuration Files" cookbook (javiereguiluz) @@ -57,6 +537,7 @@ Fixed Documentation - `3ffc20f `_ #4103 [Cookbook][Forms] fix PHP template file name (xabbuh) - `234fa36 `_ #4095 Fix php template (piotrantosik) - `01fb9f2 `_ #4093 See #4091 (dannykopping) +- `8f3a261 `_ #4092 See #4091 (dannykopping) - `7d39b03 `_ #4079 Fixed typo in filesystem component (kohkimakimoto) - `f0bde03 `_ #4075 Fixed typo in the yml validation (timothymctim) @@ -65,11 +546,13 @@ Minor Documentation Changes - `e9d317a `_ #4160 [Reference] consistent & complete config examples (xabbuh) - `3e68ee7 `_ #4152 Adding 'attr' option to the Textarea options list (ronanguilloux) +- `a7f3297 `_ #4136 [Reference] fix from suffix to prefix (xabbuh) - `c4eb628 `_ #4130 A set of small typos (Baptouuuu) - `236d8e0 `_ #4137 fixed directive syntax (WouterJ) - `6e90520 `_ #4135 [#3940] Adding php example for an array of emails (weaverryan) - `b37ee61 `_ #4132 Use proper way to reference a doc page for legacy sessions (Baptouuuu) - `189a123 `_ #4129 [Components] consistent & complete config examples (xabbuh) +- `5ab5246 `_ #4127 Second part of #3848 (WouterJ) - `46f3108 `_ #4126 Rebased #3848 (WouterJ) - `84e6e7f `_ #4114 [Book] consistent and complete config examples (xabbuh) - `03fcab1 `_ #4112 [Contributing][Documentation] add order of translation formats (xabbuh) @@ -84,6 +567,7 @@ Minor Documentation Changes - `7806aa7 `_ #4117 Added a note about the automatic handling of the memory spool in the CLI (stof) - `5959b6c `_ #4101 [Contributing] extended Symfony 2.4 maintenance (xabbuh) - `e2056ad `_ #4072 [Contributing][Code] add note on Symfony SE forks for bug reports (xabbuh) +- `b8687dd `_ #4091 Put version into quotes, otherwise it fails in ZSH (dannykopping) - `665c091 `_ #4087 Typo (tvlooy) - `f95bbf3 `_ #4023 [Cookbook][Security] usage of a non-default entity manager in an entity user provider (xabbuh) - `27b1003 `_ #4074 Fixed (again) a typo: Toolbet --> Toolbelt (javiereguiluz) @@ -100,8 +584,12 @@ New Documentation - `1b4c1c8 `_ #4045 Added a new "Deploying to Heroku Cloud" cookbook article (javiereguiluz) - `f943eee `_ #4009 Remove "Controllers extends ContainerAware" best practice (tgalopin) - `eae9ad0 `_ #3875 Added a note about customizing a form with more than one template (javiereguiluz) +- `2ae4f34 `_ #3746 [Validator] Disallow empty file in FileValidator (megazoll) +- `1938c2f `_ #3724 Updated ISBN validator docs (sprain) +- `7c71b18 `_ #2952 Enabling profiler in test (danieledangeli) - `d6787b7 `_ #3989 adde stof as a merger (fabpot) - `4a9e49e `_ #3946 DQL custom functions on doctrine reference page (healdropper) +- `2b2d9d3 `_ #3972 Added PSR-4 to Class Loaders list (dosten) Fixed Documentation ~~~~~~~~~~~~~~~~~~~ @@ -109,7 +597,10 @@ Fixed Documentation - `1b695b5 `_ #4063 fix parent form types (xabbuh) - `7901005 `_ #4048 $this->request replaced by $request (danielsan) - `f6123f1 `_ #4031 Update form_events.rst (redstar504) +- `99932cf `_ #4010 [Console] Fixed documentation for ProgressBar (VasekPurchart) - `06f8c31 `_ #4012 Fix xml route configuration for routing condition (xavierbriand) +- `a2a628f `_ #4025 added CVE 2014-4931 (fabpot) +- `a1435e5 `_ #3998 [Console] Fixed QuestionHelper examples (florianv) - `b32f9f2 `_ #3771 Fix function example in expression language component (raulfraile) - `eb813a5 `_ #3979 removed invalid processors option (ricoli) @@ -130,6 +621,9 @@ Minor Documentation Changes - `f5c2602 `_ #4036 Update page_creation.rst (redstar504) - `c2eda93 `_ #4034 Update internals.rst (redstar504) - `a5ad0df `_ #4035 Update version in Rework your Patch section (yguedidi) +- `eed8d64 `_ #4026 Updating Symfony version from 2.4 to 2.5 (danielsan) +- `12752c1 `_ #4013 Removed wrong reference to cookbook (gquemener) +- `ec832dc `_ #3994 [Console] Fix Console component $app to $this and use of getHelper() method (eko) - `d8b037a `_ #4019 Update twig_reference.rst (redstar504) - `7ea87e6 `_ #4016 Fixed the format of one letter-based list (javiereguiluz) - `579a873 `_ #4015 Fixed bad indenting (the list was treated as a blockquote) (javiereguiluz) @@ -158,11 +652,13 @@ New Documentation - `23b51c8 `_ #3901 Bootstraped the standards for "Files and Directories" (javiereguiluz) - `8931c36 `_ #3889 Fixed the section about getting services from a command (javiereguiluz) - `9fddab6 `_ #3877 Added a note about configuring several paths under the same namespace (javiereguiluz) +- `eadf281 `_ #3874 Updated the installation instructions for Symfony 2.5+ (javiereguiluz) Fixed Documentation ~~~~~~~~~~~~~~~~~~~ - `aeffd12 `_ #3961 Fixing php coding (mvhirsch) +- `84332ff `_ #3945 Fixed missing component name in namespaces (WouterJ) - `d8329dc `_ #3943 Fixing simple quotes in double quotes (ptitlazy) - `04f4318 `_ #3934 Move __construct after the repository assignment (cmodijk) - `0626f2b `_ #3897 Collection constraint (hhamon) @@ -178,13 +674,17 @@ Minor Documentation Changes - `fba083e `_ #3957 [Cookbook][Bundles] fix typos in the prepend extension chapter (xabbuh) - `c444b5d `_ #3948 update the Sphinx extensions to raise warnings when backslashes are not ... (xabbuh) - `8fef7b7 `_ #3938 [Contributing][Documentation] don't render the list inside a blockquote (xabbuh) +- `b7a03f8 `_ #3937 properly escape backslashes in class and method directives (xabbuh) +- `882471f `_ #3935 Typo (greg0ire) - `222a014 `_ #3933 render directory inside a code block (xabbuh) +- `0c2a9b3 `_ #3931 [Component][EventDispatcher] 2.5 specific documentation for the TraceableEventDispatcher (xabbuh) - `b31ea51 `_ #3929 Update custom_authentication_provider.rst (verschoof) - `7937864 `_ #3927 [Cookbook][Security] Explicit 'your_user_provider' configuration parameter (zefrog) - `26d00d0 `_ #3925 Fixed the indentation of two code blocks (javiereguiluz) - `351b2cf `_ #3922 update fabpot Sphinx extensions version (xabbuh) - `3ddbe1b `_ #3923 Fixed the headers of one table (javiereguiluz) - `35cbffc `_ #3920 [Components][Form] remove blank line to render the versionadded directive properly (xabbuh) +- `df9f31a `_ #3882 change version numbers in installation notes to be in line with the docu... (xabbuh) - `ed496ae `_ #3887 [Components][Form] add versionadded for the data collector form extension (xabbuh) - `36337e7 `_ #3906 Blockquote introductions (xabbuh) - `5e0e119 `_ #3899 [RFR] Misc. fixes mostly related to formatting issues (javiereguiluz) @@ -199,11 +699,14 @@ May, 2014 New Documentation ~~~~~~~~~~~~~~~~~ +- `4fd1b49 `_ #3753 [DependencyInjection] Add documentation about service decoration (romainneutron) +- `f913dd7 `_ #3603 [Serializer] Support for is.* getters in GetSetMethodNormalizer (tiraeth) - `e8511cb `_ #3776 Updated event_listener.rst (bfgasparin) - `af8c20f `_ #3818 [Form customization] added block_name example. (aitboudad) - `c788325 `_ #3841 [Cookbook][Logging] register processor per handler and per channel (xabbuh) - `979533a `_ #3839 document how to test actions (greg0ire) - `d8aaac3 `_ #3835 Updated framework.ide configuration (WouterJ) +- `a9648e8 `_ #3742 [2.5][Templating] Add documentation about generating versioned URLs (romainneutron) - `f665e14 `_ #3704 [Form] Added documentation for Form Events (csarrazi) - `14b9f14 `_ #3777 added docs for the core team (fabpot) @@ -217,6 +720,7 @@ Fixed Documentation - `4ed9a08 `_ #3830 Generate an APC prefix based on __FILE__ (trsteel88) - `9a65412 `_ #3840 Update dialoghelper.rst (jdecoster) - `1853fea `_ #3716 Fix issue #3712 (umpirsky) +- `baa9759 `_ #3791 Property access tweaks (weaverryan) - `80d70a4 `_ #3779 [Book][Security] constants are defined in the SecurityContextInterface (xabbuh) Minor Documentation Changes @@ -224,11 +728,13 @@ Minor Documentation Changes - `302fa82 `_ #3872 Update hostname_pattern.rst (sofany) - `50672f7 `_ #3867 fixed missing info about FosUserBundle. (aitboudad) +- `3e3004f `_ #3865 Fixed link. (aitboudad) - `b32ec15 `_ #3856 Update voters_data_permission.rst (MarcomTeam) - `bffe163 `_ #3859 Add filter cssrewrite (DOEO) - `f617ff8 `_ #3764 Update testing.rst (NAYZO) - `3792fee `_ #3858 Clarified Password Encoders example (WouterJ) - `663d68c `_ #3857 Added little bit information about the route name (WouterJ) +- `797cbd5 `_ #3794 Adds link to new QuestionHelper (weaverryan) - `4211bff `_ #3852 Fixed link and typo in type_guesser.rst (rpg600) - `78ae7ec `_ #3845 added link to /cookbook/security/force_https. (aitboudad) - `6c69362 `_ #3846 [Routing][Loader] added JMSI18nRoutingBundle (aitboudad) @@ -236,6 +742,9 @@ Minor Documentation Changes - `b0710bc `_ #3842 Update dialoghelper.rst (bijsterdee) - `9f1a354 `_ #3804 [Components][DependencyInjection] add note about a use case that requires to compile the container (xabbuh) - `d92c522 `_ #3769 Updated references to new Session() (scottwarren) +- `00f60a8 `_ #3837 More asset version details (weaverryan) +- `681ddc8 `_ #3843 [Changelog] fix literal positions (xabbuh) +- `1aa79d5 `_ #3834 fix the wording in versionadded directives (for the master branch) (xabbuh) - `7288a33 `_ #3789 [Reference][Forms] Improvements to the form type (xabbuh) - `72fae25 `_ #3790 [Reference][Forms] move versionadded directives for form options directly below the option's headline (xabbuh) - `b4d4ac3 `_ #3838 fix filename typo in cookbook/form/unit_testing.rst (hice3000) @@ -265,10 +774,15 @@ New Documentation ~~~~~~~~~~~~~~~~~ - `322972e `_ #3803 [Book][Validation] configuration examples for the GroupSequenceProvider (xabbuh) +- `9e129bc `_ #3752 [Console] Add documentation for QuestionHelper (romainneutron) +- `64a924d `_ #3756 [WCM][Console] Add Process Helper documentation (romainneutron) - `d4ca16a `_ #3743 Improve examples in parent services (WouterJ) +- `be4b9d3 `_ #3729 Added documentation for the new ``PropertyAccessor::isReadable()`` and ``isWritable()`` methods (webmozart) - `70a3893 `_ #3774 [Book][Internals] add description for the kernel.finish_request event (xabbuh) +- `1934720 `_ #3461 [Form] Deprecated max_length and pattern options (stefanosala) - `d611e77 `_ #3701 [Serializer] add documentation for serializer callbacks (cordoval) - `80c645c `_ #3719 Fixed event listeners priority (tony-co) +- `c062d81 `_ #3469 [Validator] - EmailConstraint reference (egulias) Fixed Documentation ~~~~~~~~~~~~~~~~~~~ @@ -300,6 +814,7 @@ Minor Documentation Changes - `703c2a6 `_ #3772 [Cookbook][Sessions] some language improvements (xabbuh) - `3d30b56 `_ #3773 modify Symfony CMF configuration values in the build process so that the... (xabbuh) - `cfd6d7c `_ #3758 [Book][Routing] Fixed typo on PHP version of a route definition (saro0h) +- `cedfdce `_ #3757 Fixed a typo in the request formats configuration page (gquemener) - `6bd134c `_ #3754 ignore more files and directories which are created when building the documentation (xabbuh) - `610462e `_ #3755 [Cookbook][Security] Firewall resitrction tweaks, fix markup, add to toc (xabbuh) - `0a21718 `_ #3695 Firewall backport (weaverryan) @@ -319,20 +834,33 @@ New Documentation - `3b640aa `_ #3644 made some small addition about our BC promise and semantic versioning (fabpot) - `2d1ecd9 `_ #3525 Update file_uploads.rst (juanmf) - `b1e8f56 `_ #3368 The host parameter has to be in defaults, not requirements (MarieMinasyan) +- `b34fb64 `_ #3619 [Validator] Uuid constraint reference (colinodell) +- `d7027c0 `_ #3418 [Validation] Add "hasser" support (bicpi) +- `4fd5fc1 `_ #3539 [Stopwatch] Describe retrieval of StopwatchEvent (jochenvdv) +- `1908a15 `_ #3696 [Console] Added standalone PSR-3 compliant logger (dunglas) - `c75b1a7 `_ #3621 [Console] Command as service (gnugat) - `00a462a `_ minor #3658 Fix PSR coding standards error (ifdattic) - `acf255d `_ #3328 [WIP] Travis integration (WouterJ) +- `450146e `_ #3681 Enhanced Firewall Restrictions docs (danez) - `3e7028d `_ #3659 [Internals] Complete notification description for kernel.terminate (bicpi) - `db3cde7 `_ #3124 Add note about the property attribute (Property Accessor) (raziel057) - `5965ec8 `_ #3420 [Cookbook][Configuration] add configuration cookbook handlig parameters in Configurator class (cordoval) +- `dcf8e6e `_ #3402 Added documentation about new requests formats configuration (gquemener) - `a1050eb `_ #3411 [Cookbook][Dynamic Form Modification] Add AJAX sample (bicpi) +- `842fd30 `_ #3683 [TwigBundle] Add documentation about generating absolute URL with the asset function (romainneutron) +- `fc1576a `_ #3664 [Process] Add doc for ``Process::disableOutput`` and ``Process::enableOutput`` (romainneutron) +- `3731e2e `_ #3686 Documentation of the new PSR-4 class loader. (derrabus) +- `5b915c2 `_ #3629 Added documentation for translation:debug (florianv) - `6951460 `_ #3601 Added documentation for missing ctype extension (slavafomin) +- `df63740 `_ #3627 added docs for the new Table console helper (fabpot) +- `96bd81b `_ #3626 added documentation for the new Symfony 2.5 progress bar (fabpot) - `b02c16a `_ #3565 added information on AuthenticationFailureHandlerInterface (samsamm777) - `2657ee7 `_ #3597 Document how to create a custom type guesser (WouterJ) - `5ad1599 `_ #3577 Development of custom error pages is impractical if you need to set kernel.debug=false (mpdude) - `3f4b319 `_ #3610 [HttpFoundation] Add doc for ``Request::getContent()`` method (bicpi) - `56bc266 `_ #3589 Finishing the Templating component docs (WouterJ) - `d881181 `_ #3588 Documented all form variables (WouterJ) +- `5cda1c7 `_ #3311 Use KernelTestCase instead of WebTestCase for testing code only requiring the Container (johnkary) - `e96e12d `_ #3234 [Cookbook] New cookbok: How to use the Cloud to send Emails (bicpi) - `d5d64ce `_ #3436 [Reference][Form Types] Add missing docs for "action" and "method" option (bicpi) - `3df34af `_ #3490 Tweaking Doctrine book chapter (WouterJ) @@ -341,6 +869,7 @@ New Documentation Fixed Documentation ~~~~~~~~~~~~~~~~~~~ +- `cad38ae `_ #3721 tweaks to the Console logger (xabbuh) - `06c56c1 `_ #3709 [Components][Security] Fix #3708 (bicpi) - `aadc61d `_ #3707 make method supportsClass() in custom voter compatible with the interface's documentation (xabbuh) - `65150f9 `_ #3637 Update render_without_controller.rst (94noni) @@ -365,13 +894,16 @@ Minor Documentation Changes - `aa9bb25 `_ #3636 Update security.rst (nomack84) - `78425c6 `_ #3722 add "Commands as Services" chapter to the cookbook's map (xabbuh) - `9f26da8 `_ #3720 [#3539] A backport of a sentence - the parts that apply to 2.3 (weaverryan) +- `4b611d6 `_ #3717 [master] Fixed versionadded blocks (WouterJ) - `5a3ba1b `_ #3715 change variable name to a better fitting one (xabbuh) - `499eb6c `_ #3714 [2.4] Versionadded consistency (WouterJ) - `e7580c0 `_ #3713 Updated versionadded directives to use "introduced" (WouterJ) - `e15afe0 `_ #3711 Simplified the Travis configuration (stof) +- `db1cda5 `_ #3700 [Cookbook][Security] Firewall restrictions tweaks (xabbuh) - `5035837 `_ #3706 Add support for nginx (guiditoito) - `00a462a `_ #3658 Fix PSR coding standards error (ifdattic) - `868de1e `_ #3698 Dynamic form modification cookbook: Fix inclusion of code (michaelperrin) +- `15a9d25 `_ #3697 [Console] Change Command namespaces (dunglas) - `41b2eb8 `_ #3693 Tweak to Absolute URL generation (weaverryan) - `bd473db `_ #3563 Add another tip to setup permissions (tony-co) - `67129b1 `_ #3611 [Reference][Forms] add an introductory table containing all options of the basic form type (xabbuh) @@ -385,15 +917,19 @@ Minor Documentation Changes - `12a6676 `_ #3640 [minor] fixed one typo and one formatting issue (javiereguiluz) - `9967b0c `_ #3638 [#3116] Fixing wrong table name - singular is used elsewhere (weaverryan) - `4fbf1cd `_ #3635 [QuickTour] close opened literals (xabbuh) +- `27b3410 `_ #3692 [Book][Translations] fixing a code block (xabbuh) - `2192c32 `_ #3650 Fixing some build errors (xabbuh) - `fa3f531 `_ #3677 [Reference][Forms] Remove variables section from tables (xabbuh) +- `cd6d1de `_ #3676 remove unnecessary code block directive (xabbuh) - `07822b8 `_ #3675 add missing code block directive (xabbuh) +- `739f43f `_ #3669 Fixed syntax highlighting (rvanlaarhoven) - `1f384bc `_ #3631 Added documentation for message option of the ``True`` constraint (naitsirch) - `f6a41b9 `_ #3630 Minor tweaks to form action/method (weaverryan) - `ae755e0 `_ #3628 Added anchor for permissions (WouterJ) - `6380113 `_ #3667 Update index.rst (NAYZO) - `97ef2f7 `_ #3566 Changes ACL permission setting hints (MicheleOnGit) - `9f7d742 `_ #3654 [Cookbook][Security] Fix VoterInterface signature (bicpi) +- `0a65b6f `_ #3608 [Reference][Forms] add versionadded directive for multiple option of file type (xabbuh) - `e34204e `_ #3605 Fixed a plural issue (benjaminpaap) - `e7d5a45 `_ #3599 [CHANGELOG] fix reference to contributing docs (xabbuh) - `3582bf1 `_ #3598 add changelog to hidden toctree (xabbuh) @@ -418,6 +954,8 @@ New Documentation - `9676f2c `_ #3523 [Components][EventDispatcher] describe that the event name and the event dispatcher are passed to even... (xabbuh) - `5c367b4 `_ #3517 Fixed OptionsResolver component docs (WouterJ) - `527c8b6 `_ #3496 Added a section about using named assets (vmattila) +- `8ccfe85 `_ #3491 Added doc for named encoders (tamirvs) +- `46377b2 `_ #3486 Documenting createAccessDeniedException() method (klaussilveira) Fixed Documentation ~~~~~~~~~~~~~~~~~~~ @@ -429,6 +967,7 @@ Fixed Documentation - `de71a51 `_ #3551 [Cookbook][Dynamic Form Modification] Fix sample code (rybakit) - `143db2f `_ #3550 Update introduction.rst (taavit) - `384538b `_ #3549 Fixed createPropertyAccessorBuilder usage (antonbabenko) +- `642e776 `_ #3544 Fix build errors (xabbuh) - `d275302 `_ #3541 Update generic_event.rst (Lumbendil) - `819949c `_ #3537 Add missing variable assignment (colinodell) - `d7e8262 `_ #3535 fix form type name. (yositani2002) @@ -462,6 +1001,7 @@ Minor Documentation Changes - `6a2a55b `_ #3579 Fix build errors (xabbuh) - `dce2e23 `_ #3532 Added tip for Entity Listeners (slavafomin) - `73adf8b `_ #3528 Clarify service parameters usages (WouterJ) +- `7e75b64 `_ #3533 Moving the new named algorithms into their own cookbook entry (weaverryan) - `f634600 `_ #3531 Remove horizontal scrolling in code block (ifdattic) - `9ba4fa7 `_ #3527 Changes to components domcrawler (ifdattic) - `8973c81 `_ #3526 Changes for Console component (ifdattic) @@ -484,12 +1024,16 @@ New Documentation ~~~~~~~~~~~~~~~~~ - `d52f3f8 `_ #3454 [Security] Add host option (ghostika) +- `11e079b `_ #3446 [WCM] Documented deprecation of the apache router. (jakzal) +- `0a0bf4c `_ #3437 Add info about callback in options resolver (marekkalnik) +- `6db5f23 `_ #3426 New Feature: Change the Default Command in the Console component (danielcsgomes) - `6b3c424 `_ #3428 Translation - Added info about JsonFileLoader added in 2.4 (singles) Fixed Documentation ~~~~~~~~~~~~~~~~~~~ - `fb22fa0 `_ #3456 remove duplicate label (xabbuh) +- `a87fe18 `_ #3470 Fixed typo (danielcsgomes) - `c205bc6 `_ #3468 enclose YAML string with double quotes to fix syntax highlighting (xabbuh) - `89963cc `_ #3463 Fix typos in cookbook/testing/database (ifdattic) - `e0a52ec `_ #3460 remove confusing outdated note on interactive rebasing (xabbuh) @@ -510,9 +1054,10 @@ Fixed Documentation Minor Documentation Changes ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- `b9bbe5d `_ #3499 Fix YAML syntax highlight + remove trailing whitespace (ifdattic) - `f285d93 `_ #3451 some language tweaks (AE, third-person perspective) (xabbuh) +- `b9bbe5d `_ #3499 Fix YAML syntax highlight + remove trailing whitespace (ifdattic) - `2b7e0f6 `_ #3497 Fix highlighting (WouterJ) +- `2746067 `_ #3472 Fixed `````versionadded````` inconsistencies in Symfony 2.5+ (danielcsgomes) - `a535ae0 `_ #3471 Fixed `````versionadded````` inconsistencies in Symfony 2.3 (danielcsgomes) - `f077a8e `_ #3465 change wording in versionadded example to be consistent with what we use... (xabbuh) - `f9f7548 `_ #3462 Replace ... with etc (ifdattic) diff --git a/components/class_loader/cache_class_loader.rst b/components/class_loader/cache_class_loader.rst index c9c1956d808..6a2c848051a 100644 --- a/components/class_loader/cache_class_loader.rst +++ b/components/class_loader/cache_class_loader.rst @@ -4,7 +4,7 @@ single: ClassLoader; Cache single: ClassLoader; XcacheClassLoader single: XCache; XcacheClassLoader - + Cache a Class Loader ==================== @@ -30,16 +30,16 @@ ApcClassLoader ``findFile()`` method using `APC`_:: require_once '/path/to/src/Symfony/Component/ClassLoader/ApcClassLoader.php'; - + // instance of a class that implements a findFile() method, like the ClassLoader $loader = ...; - + // sha1(__FILE__) generates an APC namespace prefix $cachedLoader = new ApcClassLoader(sha1(__FILE__), $loader); - + // register the cached class loader $cachedLoader->register(); - + // deactivate the original, non-cached loader if it was registered previously $loader->unregister(); @@ -50,16 +50,16 @@ XcacheClassLoader it is straightforward:: require_once '/path/to/src/Symfony/Component/ClassLoader/XcacheClassLoader.php'; - + // instance of a class that implements a findFile() method, like the ClassLoader $loader = ...; - + // sha1(__FILE__) generates an XCache namespace prefix $cachedLoader = new XcacheClassLoader(sha1(__FILE__), $loader); - + // register the cached class loader $cachedLoader->register(); - + // deactivate the original, non-cached loader if it was registered previously $loader->unregister(); diff --git a/components/class_loader/class_map_generator.rst b/components/class_loader/class_map_generator.rst index 320a5d2acd4..f947027f107 100644 --- a/components/class_loader/class_map_generator.rst +++ b/components/class_loader/class_map_generator.rst @@ -28,17 +28,14 @@ manually. For example, imagine a library with the following directory structure: These files contain the following classes: -=========================== ================ -File Class name -=========================== ================ -``library/bar/baz/Boo.php`` ``Acme\Bar\Baz`` ---------------------------- ---------------- -``library/bar/Foo.php`` ``Acme\Bar`` ---------------------------- ---------------- -``library/foo/bar/Foo.php`` ``Acme\Foo\Bar`` ---------------------------- ---------------- -``library/foo/Bar.php`` ``Acme\Foo`` -=========================== ================ +=========================== ================ +File Class Name +=========================== ================ +``library/bar/baz/Boo.php`` ``Acme\Bar\Baz`` +``library/bar/Foo.php`` ``Acme\Bar`` +``library/foo/bar/Foo.php`` ``Acme\Foo\Bar`` +``library/foo/Bar.php`` ``Acme\Foo`` +=========================== ================ To make your life easier, the ClassLoader component comes with a :class:`Symfony\\Component\\ClassLoader\\ClassMapGenerator` class that makes @@ -48,7 +45,7 @@ Generating a Class Map ---------------------- To generate the class map, simply pass the root directory of your class files -to the :method:`Symfony\\Component\\ClassLoader\\ClassMapGenerator::createMap`` +to the :method:`Symfony\\Component\\ClassLoader\\ClassMapGenerator::createMap` method:: use Symfony\Component\ClassLoader\ClassMapGenerator; @@ -118,7 +115,10 @@ the same as in the example above):: use Symfony\Component\ClassLoader\ClassMapGenerator; - ClassMapGenerator::dump(array(__DIR__.'/library/bar', __DIR__.'/library/foo'), __DIR__.'/class_map.php'); + ClassMapGenerator::dump( + array(__DIR__.'/library/bar', __DIR__.'/library/foo'), + __DIR__.'/class_map.php' + ); .. _`PSR-0`: http://www.php-fig.org/psr/psr-0 .. _`PSR-4`: http://www.php-fig.org/psr/psr-4 diff --git a/components/class_loader/debug_class_loader.rst b/components/class_loader/debug_class_loader.rst index c56d7ffe095..75bf66c707a 100644 --- a/components/class_loader/debug_class_loader.rst +++ b/components/class_loader/debug_class_loader.rst @@ -1,9 +1,11 @@ .. index:: single: ClassLoader; DebugClassLoader - + Debugging a Class Loader ======================== -Since Symfony 2.4, the ``DebugClassLoader`` of the Class Loader component is -deprecated. Use the -:doc:`DebugClassLoader provided by the Debug component `. +.. caution:: + + The ``DebugClassLoader`` from the ClassLoader component was deprecated + in Symfony 2.5 and will be removed in Symfony 3.0. Use the + :doc:`DebugClassLoader provided by the Debug component `. diff --git a/components/class_loader/index.rst b/components/class_loader/index.rst index 864bf77d734..9808f59c6d8 100644 --- a/components/class_loader/index.rst +++ b/components/class_loader/index.rst @@ -6,6 +6,7 @@ ClassLoader introduction class_loader + psr4_class_loader map_class_loader cache_class_loader debug_class_loader diff --git a/components/class_loader/introduction.rst b/components/class_loader/introduction.rst index ab8f1566a6e..9db1e7f41fa 100644 --- a/components/class_loader/introduction.rst +++ b/components/class_loader/introduction.rst @@ -12,11 +12,14 @@ Usage Whenever you reference a class that has not been required or included yet, PHP uses the `autoloading mechanism`_ to delegate the loading of a file defining -the class. Symfony provides two autoloaders, which are able to load your classes: +the class. Symfony provides three autoloaders, which are able to load your classes: * :doc:`/components/class_loader/class_loader`: loads classes that follow the `PSR-0` class naming standard; +* :doc:`/components/class_loader/psr4_class_loader`: loads classes that follow + the `PSR-4` class naming standard; + * :doc:`/components/class_loader/map_class_loader`: loads classes using a static map from class name to file path. diff --git a/components/class_loader/map_class_loader.rst b/components/class_loader/map_class_loader.rst index 0243550af68..0a157416040 100644 --- a/components/class_loader/map_class_loader.rst +++ b/components/class_loader/map_class_loader.rst @@ -1,6 +1,6 @@ .. index:: single: ClassLoader; MapClassLoader - + MapClassLoader ============== @@ -26,14 +26,14 @@ Using it is as easy as passing your mapping to its constructor when creating an instance of the ``MapClassLoader`` class:: require_once '/path/to/src/Symfony/Component/ClassLoader/MapClassLoader'; - + $mapping = array( 'Foo' => '/path/to/Foo', 'Bar' => '/path/to/Bar', ); - + $loader = new MapClassLoader($mapping); - + $loader->register(); .. _PSR-0: http://www.php-fig.org/psr/psr-0/ diff --git a/components/class_loader/psr4_class_loader.rst b/components/class_loader/psr4_class_loader.rst new file mode 100644 index 00000000000..33915b16a9e --- /dev/null +++ b/components/class_loader/psr4_class_loader.rst @@ -0,0 +1,67 @@ +.. index:: + single: ClassLoader; PSR-4 Class Loader + +The PSR-4 Class Loader +====================== + +.. versionadded:: 2.5 + The :class:`Symfony\\Component\\ClassLoader\\Psr4ClassLoader` was + introduced in Symfony 2.5. + +Libraries that follow the `PSR-4`_ standard can be loaded with the ``Psr4ClassLoader``. + +.. note:: + + If you manage your dependencies via Composer, you get a PSR-4 compatible + autoloader out of the box. Use this loader in environments where Composer + is not available. + +.. tip:: + + All Symfony components follow PSR-4. + +Usage +----- + +The following example demonstrates how you can use the +:class:`Symfony\\Component\\ClassLoader\\Psr4ClassLoader` autoloader to use +Symfony's Yaml component. Imagine, you downloaded both the ClassLoader and +Yaml component as ZIP packages and unpacked them to a ``libs`` directory. +The directory structure will look like this: + +.. code-block:: text + + libs/ + ClassLoader/ + Psr4ClassLoader.php + ... + Yaml/ + Yaml.php + ... + config.yml + demo.php + +In ``demo.php`` you are going to parse the ``config.yml`` file. To do that, you +first need to configure the ``Psr4ClassLoader``: + +.. code-block:: php + + use Symfony\Component\ClassLoader\Psr4ClassLoader; + use Symfony\Component\Yaml\Yaml; + + require __DIR__.'/lib/ClassLoader/Psr4ClassLoader.php'; + + $loader = new Psr4ClassLoader(); + $loader->addPrefix('Symfony\\Component\\Yaml\\', __DIR__.'/lib/Yaml'); + $loader->register(); + + $data = Yaml::parse(file_get_contents(__DIR__.'/config.yml')); + +First of all, the class loader is loaded manually using a ``require`` +statement, since there is no autoload mechanism yet. With the +:method:`Symfony\\Component\\ClassLoader\\Psr4ClassLoader::addPrefix` call, you +tell the class loader where to look for classes with the +``Symfony\Component\Yaml\`` namespace prefix. After registering the autoloader, +the Yaml component is ready to be used. + +.. _PSR-4: http://www.php-fig.org/psr/psr-4/ diff --git a/components/config/definition.rst b/components/config/definition.rst index 9c7b1f15f8d..af4a72338ee 100644 --- a/components/config/definition.rst +++ b/components/config/definition.rst @@ -99,7 +99,7 @@ node definition. Node type are available for: * scalar * boolean -* integer (new in 2.2) +* integer * float * enum * array @@ -111,9 +111,6 @@ and are created with ``node($name, $type)`` or their associated shortcut Numeric Node Constraints ~~~~~~~~~~~~~~~~~~~~~~~~ -.. versionadded:: 2.2 - The numeric (float and integer) nodes were introduced in Symfony 2.2. - Numeric nodes (float and integer) provide two extra constraints - :method:`Symfony\\Component\\Config\\Definition\\Builder::min` and :method:`Symfony\\Component\\Config\\Definition\\Builder::max` - @@ -293,13 +290,13 @@ method. The info will be printed as a comment when dumping the configuration tree. +.. versionadded:: 2.6 + Since Symfony 2.6, the info will also be added to the exception message + when an invalid type is given. + Optional Sections ----------------- -.. versionadded:: 2.2 - The ``canBeEnabled`` and ``canBeDisabled`` methods were introduced in - Symfony 2.2. - If you have entire sections which are optional and can be enabled/disabled, you can take advantage of the shortcut :method:`Symfony\\Component\\Config\\Definition\\Builder\\ArrayNodeDefinition::canBeEnabled` and @@ -503,7 +500,7 @@ By changing a string value into an associative array with ``name`` as the key:: ->arrayNode('connection') ->beforeNormalization() ->ifString() - ->then(function($v) { return array('name'=> $v); }) + ->then(function ($v) { return array('name' => $v); }) ->end() ->children() ->scalarNode('name')->isRequired() @@ -573,8 +570,8 @@ Otherwise the result is a clean array of configuration values:: use Symfony\Component\Config\Definition\Processor; use Acme\DatabaseConfiguration; - $config1 = Yaml::parse(__DIR__.'/src/Matthias/config/config.yml'); - $config2 = Yaml::parse(__DIR__.'/src/Matthias/config/config_extra.yml'); + $config1 = Yaml::parse(file_get_contents(__DIR__.'/src/Matthias/config/config.yml')); + $config2 = Yaml::parse(file_get_contents(__DIR__.'/src/Matthias/config/config_extra.yml')); $configs = array($config1, $config2); diff --git a/components/config/resources.rst b/components/config/resources.rst index bcc47aa02e1..a1d46565803 100644 --- a/components/config/resources.rst +++ b/components/config/resources.rst @@ -39,7 +39,7 @@ class, which allows for recursively importing other resources:: { public function load($resource, $type = null) { - $configValues = Yaml::parse($resource); + $configValues = Yaml::parse(file_get_contents($resource)); // ... handle the config values diff --git a/components/console/changing_default_command.rst b/components/console/changing_default_command.rst new file mode 100644 index 00000000000..590cf3577e1 --- /dev/null +++ b/components/console/changing_default_command.rst @@ -0,0 +1,67 @@ +.. index:: + single: Console; Changing the Default Command + +Changing the Default Command +============================ + +.. versionadded:: 2.5 + The :method:`Symfony\\Component\\Console\\Application::setDefaultCommand` + method was introduced in Symfony 2.5. + +The Console component will always run the ``ListCommand`` when no command name is +passed. In order to change the default command you just need to pass the command +name to the ``setDefaultCommand`` method:: + + namespace Acme\Console\Command; + + use Symfony\Component\Console\Command\Command; + use Symfony\Component\Console\Input\InputInterface; + use Symfony\Component\Console\Output\OutputInterface; + + class HelloWorldCommand extends Command + { + protected function configure() + { + $this->setName('hello:world') + ->setDescription('Outputs \'Hello World\''); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $output->writeln('Hello World'); + } + } + +Executing the application and changing the default Command:: + + // application.php + + use Acme\Console\Command\HelloWorldCommand; + use Symfony\Component\Console\Application; + + $command = new HelloWorldCommand(); + $application = new Application(); + $application->add($command); + $application->setDefaultCommand($command->getName()); + $application->run(); + +Test the new default console command by running the following: + +.. code-block:: bash + + $ php application.php + +This will print the following to the command line: + +.. code-block:: text + + Hello World + +.. tip:: + + This feature has a limitation: you cannot use it with any Command arguments. + +Learn More! +----------- + +* :doc:`/components/console/single_command_tool` diff --git a/components/console/console_arguments.rst b/components/console/console_arguments.rst new file mode 100644 index 00000000000..fa56c141353 --- /dev/null +++ b/components/console/console_arguments.rst @@ -0,0 +1,90 @@ +.. index:: + single: Console; Console arguments + +Understanding how Console Arguments Are Handled +=============================================== + +It can be difficult to understand the way arguments are handled by the console application. +The Symfony Console application, like many other CLI utility tools, follows the behavior +described in the `docopt`_ standards. + +Have a look at the following command that has three options:: + + namespace Acme\Console\Command; + + use Symfony\Component\Console\Command\Command; + use Symfony\Component\Console\Input\InputArgument; + use Symfony\Component\Console\Input\InputInterface; + use Symfony\Component\Console\Input\InputOption; + use Symfony\Component\Console\Output\OutputInterface; + + class DemoArgsCommand extends Command + { + protected function configure() + { + $this + ->setName('demo:args') + ->setDescription('Describe args behaviors') + ->setDefinition( + new InputDefinition(array( + new InputOption('foo', 'f'), + new InputOption('bar', 'b', InputOption::VALUE_REQUIRED), + new InputOption('cat', 'c', InputOption::VALUE_OPTIONAL), + )) + ); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + // ... + } + } + +Since the ``foo`` option doesn't accept a value, it will be either ``false`` +(when it is not passed to the command) or ``true`` (when ``--foo`` was passed +by the user). The value of the ``bar`` option (and its ``b`` shortcut respectively) +is required. It can be separated from the option name either by spaces or +``=`` characters. The ``cat`` option (and its ``c`` shortcut) behaves similar +except that it doesn't require a value. Have a look at the following table +to get an overview of the possible ways to pass options: + +===================== ========= =========== ============ +Input ``foo`` ``bar`` ``cat`` +===================== ========= =========== ============ +``--bar=Hello`` ``false`` ``"Hello"`` ``null`` +``--bar Hello`` ``false`` ``"Hello"`` ``null`` +``-b=Hello`` ``false`` ``"Hello"`` ``null`` +``-b Hello`` ``false`` ``"Hello"`` ``null`` +``-bHello`` ``false`` ``"Hello"`` ``null`` +``-fcWorld -b Hello`` ``true`` ``"Hello"`` ``"World"`` +``-cfWorld -b Hello`` ``false`` ``"Hello"`` ``"fWorld"`` +``-cbWorld`` ``false`` ``null`` ``"bWorld"`` +===================== ========= =========== ============ + +Things get a little bit more tricky when the command also accepts an optional +argument:: + + // ... + + new InputDefinition(array( + // ... + new InputArgument('arg', InputArgument::OPTIONAL), + )); + +You might have to use the special ``--`` separator to separate options from +arguments. Have a look at the fifth example in the following table where it +is used to tell the command that ``World`` is the value for ``arg`` and not +the value of the optional ``cat`` option: + +============================== ================= =========== =========== +Input ``bar`` ``cat`` ``arg`` +============================== ================= =========== =========== +``--bar Hello`` ``"Hello"`` ``null`` ``null`` +``--bar Hello World`` ``"Hello"`` ``null`` ``"World"`` +``--bar "Hello World"`` ``"Hello World"`` ``null`` ``null`` +``--bar Hello --cat World`` ``"Hello"`` ``"World"`` ``null`` +``--bar Hello --cat -- World`` ``"Hello"`` ``null`` ``"World"`` +``-b Hello -c World`` ``"Hello"`` ``"World"`` ``null`` +============================== ================= =========== =========== + +.. _docopt: http://docopt.org/ diff --git a/components/console/events.rst b/components/console/events.rst index ff7048138e8..8dac89520a0 100644 --- a/components/console/events.rst +++ b/components/console/events.rst @@ -51,6 +51,39 @@ dispatched. Listeners receive a $application = $command->getApplication(); }); +Disable Commands inside Listeners +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 2.6 + Disabling commands inside listeners was introduced in Symfony 2.6. + +Using the +:method:`Symfony\\Component\\Console\\Event\\ConsoleCommandEvent::disableCommand` +method, you can disable a command inside a listener. The application +will then *not* execute the command, but instead will return the code ``113`` +(defined in ``ConsoleCommandEvent::RETURN_CODE_DISABLED``). This code is one +of the `reserved exit codes`_ for console commands that conform with the +C/C++ standard.:: + + use Symfony\Component\Console\Event\ConsoleCommandEvent; + use Symfony\Component\Console\ConsoleEvents; + + $dispatcher->addListener(ConsoleEvents::COMMAND, function (ConsoleCommandEvent $event) { + // get the command to be executed + $command = $event->getCommand(); + + // ... check if the command can be executed + + // disable the command, this will result in the command being skipped + // and code 113 being returned from the Application + $event->disableCommand(); + + // it is possible to enable the command in a later listener + if (!$event->commandShouldRun()) { + $event->enableCommand(); + } + }); + The ``ConsoleEvents::TERMINATE`` Event -------------------------------------- @@ -118,3 +151,5 @@ Listeners receive a // change the exception to another one $event->setException(new \LogicException('Caught exception', $exitCode, $event->getException())); }); + +.. _`reserved exit codes`: http://www.tldp.org/LDP/abs/html/exitcodes.html diff --git a/components/console/helpers/debug_formatter.rst b/components/console/helpers/debug_formatter.rst new file mode 100644 index 00000000000..885c89ab69c --- /dev/null +++ b/components/console/helpers/debug_formatter.rst @@ -0,0 +1,145 @@ +.. index:: + single: Console Helpers; DebugFormatter Helper + +Debug Formatter Helper +====================== + +.. versionadded:: 2.6 + The Debug Formatter helper was introduced in Symfony 2.6. + +The :class:`Symfony\\Component\\Console\\Helper\\DebugFormatterHelper` provides +functions to output debug information when running an external program, for +instance a process or HTTP request. For example, if you used it to output +the results of running ``ls -la`` on a UNIX system, it might output something +like this: + +.. image:: /images/components/console/debug_formatter.png + :align: center + +Using the debug_formatter +------------------------- + +The formatter is included in the default helper set and you can get it by +calling :method:`Symfony\\Component\\Console\\Command\\Command::getHelper`:: + + $debugFormatter = $this->getHelper('debug_formatter'); + +The formatter accepts strings and returns a formatted string, which you then +output to the console (or even log the information or do anything else). + +All methods of this helper have an identifier as the first argument. This is a +unique value for each program. This way, the helper can debug information for +multiple programs at the same time. When using the +:doc:`Process component `, you probably want to use +:phpfunction:`spl_object_hash`. + +.. tip:: + + This information is often too verbose to be shown by default. You can use + :ref:`verbosity levels ` to only show it when in + debugging mode (``-vvv``). + +Starting a Program +------------------ + +As soon as you start a program, you can use +:method:`Symfony\\Component\\Console\\Helper\\DebugFormatterHelper::start` to +display information that the program is started:: + + // ... + $process = new Process(...); + + $output->writeln($debugFormatter->start( + spl_object_hash($process), + 'Some process description' + )); + + $process->run(); + +This will output: + +.. code-block:: text + + RUN Some process description + +You can tweak the prefix using the third argument:: + + $output->writeln($debugFormatter->start( + spl_object_hash($process), + 'Some process description', + 'STARTED' + )); + // will output: + // STARTED Some process description + +Output Progress Information +--------------------------- + +Some programs give output while they are running. This information can be shown +using +:method:`Symfony\\Component\\Console\\Helper\\DebugFormatterHelper::progress`:: + + use Symfony\Component\Process\Process; + + // ... + $process = new Process(...); + + $process->run(function ($type, $buffer) use ($output, $debugFormatter, $process) { + $output->writeln( + $debugFormatter->progress( + spl_object_hash($process), + $buffer, + Process::ERR === $type + ) + ); + }); + // ... + +In case of success, this will output: + +.. code-block:: text + + OUT The output of the process + +And this in case of failure: + +.. code-block:: text + + ERR The output of the process + +The third argument is a boolean which tells the function if the output is error +output or not. When ``true``, the output is considered error output. + +The fourth and fifth argument allow you to override the prefix for the normal +output and error output respectively. + +Stopping a Program +------------------ + +When a program is stopped, you can use +:method:`Symfony\\Component\\Console\\Helper\\DebugFormatterHelper::run` to +notify this to the users:: + + // ... + $output->writeln( + $debugFormatter->stop( + spl_object_hash($process), + 'Some command description', + $process->isSuccessfull() + ) + ); + +This will output: + +.. code-block:: text + + RES Some command description + +In case of failure, this will be in red and in case of success it will be green. + +Using multiple Programs +----------------------- + +As said before, you can also use the helper to display more programs at the +same time. Information about different programs will be shown in different +colors, to make it clear which output belongs to which command. diff --git a/components/console/helpers/dialoghelper.rst b/components/console/helpers/dialoghelper.rst index 09b3e215033..e55226cf108 100644 --- a/components/console/helpers/dialoghelper.rst +++ b/components/console/helpers/dialoghelper.rst @@ -4,6 +4,13 @@ Dialog Helper ============= +.. caution:: + + The Dialog Helper was deprecated in Symfony 2.5 and will be removed in + Symfony 3.0. You should now use the + :doc:`Question Helper ` instead, + which is simpler to use. + The :class:`Symfony\\Component\\Console\\Helper\\DialogHelper` provides functions to ask the user for more information. It is included in the default helper set, which you can get by calling @@ -54,14 +61,11 @@ if you want to know a bundle name, you can add this to your command:: The user will be asked "Please enter the name of the bundle". They can type some name which will be returned by the :method:`Symfony\\Component\\Console\\Helper\\DialogHelper::ask` method. -If they leave it empty, the default value (``AcmeDemoBundle`` here) is returned. +If they leave it empty, the default value (AcmeDemoBundle here) is returned. Autocompletion ~~~~~~~~~~~~~~ -.. versionadded:: 2.2 - Autocompletion for questions was introduced in Symfony 2.2. - You can also specify an array of potential answers for a given question. These will be autocompleted as the user types:: @@ -77,9 +81,6 @@ will be autocompleted as the user types:: Hiding the User's Response ~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. versionadded:: 2.2 - The ``askHiddenResponse`` method was introduced in Symfony 2.2. - You can also ask a question and hide the response. This is particularly convenient for passwords:: @@ -149,9 +150,6 @@ be able to proceed if their input is valid. Validating a Hidden Response ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. versionadded:: 2.2 - The ``askHiddenResponseAndValidate`` method was introduced in Symfony 2.2. - You can also ask and validate a hidden response:: $dialog = $this->getHelper('dialog'); @@ -178,10 +176,6 @@ some reason, pass true as the fifth argument. Let the User Choose from a List of Answers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. versionadded:: 2.2 - The :method:`Symfony\\Component\\Console\\Helper\\DialogHelper::select` method - was introduced in Symfony 2.2. - If you have a predefined set of answers the user can choose from, you could use the ``ask`` method described above or, to make sure the user provided a correct answer, the ``askAndValidate`` method. Both have @@ -236,7 +230,7 @@ this set the seventh argument to ``true``:: true // enable multiselect ); - $selectedColors = array_map(function($c) use ($colors) { + $selectedColors = array_map(function ($c) use ($colors) { return $colors[$c]; }, $selected); @@ -253,18 +247,23 @@ Testing a Command which Expects Input If you want to write a unit test for a command which expects some kind of input from the command line, you need to overwrite the HelperSet used by the command:: + use Symfony\Component\Console\Application; use Symfony\Component\Console\Helper\DialogHelper; use Symfony\Component\Console\Helper\HelperSet; + use Symfony\Component\Console\Tester\CommandTester; // ... public function testExecute() { // ... + $application = new Application(); + $application->add(new MyCommand()); + $command = $application->find('my:command:name'); $commandTester = new CommandTester($command); $dialog = $command->getHelper('dialog'); $dialog->setInputStream($this->getInputStream("Test\n")); - // Equals to a user inputing "Test" and hitting ENTER + // Equals to a user inputting "Test" and hitting ENTER // If you need to enter a confirmation, "yes\n" will work $commandTester->execute(array('command' => $command->getName())); @@ -285,3 +284,8 @@ By setting the input stream of the ``DialogHelper``, you imitate what the console would do internally with all user input through the cli. This way you can test any user interaction (even complex ones) by passing an appropriate input stream. + +.. seealso:: + + You find more information about testing commands in the console component + docs about :ref:`testing console commands `. diff --git a/components/console/helpers/index.rst b/components/console/helpers/index.rst index 1c95bc47057..bf51dc40c92 100644 --- a/components/console/helpers/index.rst +++ b/components/console/helpers/index.rst @@ -9,8 +9,13 @@ The Console Helpers dialoghelper formatterhelper + processhelper + progressbar progresshelper + questionhelper + table tablehelper + debug_formatter The Console component comes with some useful helpers. These helpers contain function to ease some common tasks. diff --git a/components/console/helpers/map.rst.inc b/components/console/helpers/map.rst.inc index 60b32c03975..d0913db4033 100644 --- a/components/console/helpers/map.rst.inc +++ b/components/console/helpers/map.rst.inc @@ -1,4 +1,9 @@ -* :doc:`/components/console/helpers/dialoghelper` +* :doc:`/components/console/helpers/dialoghelper` (deprecated as of 2.5) * :doc:`/components/console/helpers/formatterhelper` -* :doc:`/components/console/helpers/progresshelper` -* :doc:`/components/console/helpers/tablehelper` +* :doc:`/components/console/helpers/processhelper` +* :doc:`/components/console/helpers/progressbar` +* :doc:`/components/console/helpers/progresshelper` (deprecated as of 2.5) +* :doc:`/components/console/helpers/questionhelper` +* :doc:`/components/console/helpers/table` +* :doc:`/components/console/helpers/tablehelper` (deprecated as of 2.5) +* :doc:`/components/console/helpers/debug_formatter` (new in 2.6) diff --git a/components/console/helpers/processhelper.rst b/components/console/helpers/processhelper.rst new file mode 100644 index 00000000000..5494ca0137f --- /dev/null +++ b/components/console/helpers/processhelper.rst @@ -0,0 +1,84 @@ +.. index:: + single: Console Helpers; Process Helper + +Process Helper +============== + +.. versionadded:: 2.6 + The Process Helper was introduced in Symfony 2.6. + +The Process Helper shows processes as they're running and reports +useful information about process status. + +To display process details, use the :class:`Symfony\\Component\\Console\\Helper\\ProcessHelper` +and run your command with verbosity. For example, running the following code with +a very verbose verbosity (e.g. -vv):: + + use Symfony\Component\Process\ProcessBuilder; + + $helper = $this->getHelper('process'); + $process = ProcessBuilder::create(array('figlet', 'Symfony'))->getProcess(); + + $helper->run($output, $process); + +will result in this output: + +.. image:: /images/components/console/process-helper-verbose.png + +It will result in more detailed output with debug verbosity (e.g. ``-vvv``): + +.. image:: /images/components/console/process-helper-debug.png + +In case the process fails, debugging is easier: + +.. image:: /images/components/console/process-helper-error-debug.png + +Arguments +--------- + +There are three ways to use the process helper: + +* Using a command line string:: + + // ... + $helper->run($output, 'figlet Symfony'); + +* An array of arguments:: + + // ... + $helper->run($output, array('figlet', 'Symfony')); + + .. note:: + + When running the helper against an array of arguments, be aware that + these will be automatically escaped. + +* Passing a :class:`Symfony\\Component\\Process\\Process` instance:: + + use Symfony\Component\Process\ProcessBuilder; + + // ... + $process = ProcessBuilder::create(array('figlet', 'Symfony'))->getProcess(); + + $helper->run($output, $process); + +Customized Display +------------------ + +You can display a customized error message using the third argument of the +:method:`Symfony\\Component\\Console\\Helper\\ProcessHelper::run` method:: + + $helper->run($output, $process, 'The process failed :('); + +A custom process callback can be passed as the fourth argument. Refer to the +:doc:`Process Component ` for callback documentation:: + + use Symfony\Component\Process\Process; + + $helper->run($output, $process, 'The process failed :(', function ($type, $data) { + if (Process::ERR === $type) { + // ... do something with the stderr output + } else { + // ... do something with the stdout + } + }); diff --git a/components/console/helpers/progressbar.rst b/components/console/helpers/progressbar.rst new file mode 100644 index 00000000000..1b8edf309cc --- /dev/null +++ b/components/console/helpers/progressbar.rst @@ -0,0 +1,337 @@ +.. index:: + single: Console Helpers; Progress Bar + +Progress Bar +============ + +.. versionadded:: 2.5 + The Progress Bar feature was introduced in Symfony 2.5 as a replacement for + the :doc:`Progress Helper `. + +When executing longer-running commands, it may be helpful to show progress +information, which updates as your command runs: + +.. image:: /images/components/console/progressbar.gif + +To display progress details, use the +:class:`Symfony\\Component\\Console\\Helper\\ProgressBar`, pass it a total +number of units, and advance the progress as the command executes:: + + use Symfony\Component\Console\Helper\ProgressBar; + + // create a new progress bar (50 units) + $progress = new ProgressBar($output, 50); + + // start and displays the progress bar + $progress->start(); + + $i = 0; + while ($i++ < 50) { + // ... do some work + + // advance the progress bar 1 unit + $progress->advance(); + + // you can also advance the progress bar by more than 1 unit + // $progress->advance(3); + } + + // ensure that the progress bar is at 100% + $progress->finish(); + +Instead of advancing the bar by a number of steps (with the +:method:`Symfony\\Component\\Console\\Helper\\ProgressBar::advance` method), +you can also set the current progress by calling the +:method:`Symfony\\Component\\Console\\Helper\\ProgressBar::setProgress` method. + +.. versionadded:: 2.6 + The ``setProgress()`` method was called ``setCurrent()`` prior to Symfony 2.6. + +.. caution:: + + Prior to version 2.6, the progress bar only works if your platform + supports ANSI codes; on other platforms, no output is generated. + +.. versionadded:: 2.6 + If your platform doesn't support ANSI codes, updates to the progress + bar are added as new lines. To prevent the output from being flooded, + adjust the + :method:`Symfony\\Component\\Console\\Helper\\ProgressBar::setRedrawFrequency` + accordingly. By default, when using a ``max``, the redraw frequency + is set to *10%* of your ``max``. + +If you don't know the number of steps in advance, just omit the steps argument +when creating the :class:`Symfony\\Component\\Console\\Helper\\ProgressBar` +instance:: + + $progress = new ProgressBar($output); + +The progress will then be displayed as a throbber: + +.. code-block:: text + + # no max steps (displays it like a throbber) + 0 [>---------------------------] + 5 [----->----------------------] + 5 [============================] + + # max steps defined + 0/3 [>---------------------------] 0% + 1/3 [=========>------------------] 33% + 3/3 [============================] 100% + +Whenever your task is finished, don't forget to call +:method:`Symfony\\Component\\Console\\Helper\\ProgressBar::finish` to ensure +that the progress bar display is refreshed with a 100% completion. + +.. note:: + + If you want to output something while the progress bar is running, + call :method:`Symfony\\Component\\Console\\Helper\\ProgressBar::clear` first. + After you're done, call + :method:`Symfony\\Component\\Console\\Helper\\ProgressBar::display` + to show the progress bar again. + +Customizing the Progress Bar +---------------------------- + +Built-in Formats +~~~~~~~~~~~~~~~~ + +By default, the information rendered on a progress bar depends on the current +level of verbosity of the ``OutputInterface`` instance: + +.. code-block:: text + + # OutputInterface::VERBOSITY_NORMAL (CLI with no verbosity flag) + 0/3 [>---------------------------] 0% + 1/3 [=========>------------------] 33% + 3/3 [============================] 100% + + # OutputInterface::VERBOSITY_VERBOSE (-v) + 0/3 [>---------------------------] 0% 1 sec + 1/3 [=========>------------------] 33% 1 sec + 3/3 [============================] 100% 1 sec + + # OutputInterface::VERBOSITY_VERY_VERBOSE (-vv) + 0/3 [>---------------------------] 0% 1 sec + 1/3 [=========>------------------] 33% 1 sec + 3/3 [============================] 100% 1 sec + + # OutputInterface::VERBOSITY_DEBUG (-vvv) + 0/3 [>---------------------------] 0% 1 sec/1 sec 1.0 MB + 1/3 [=========>------------------] 33% 1 sec/1 sec 1.0 MB + 3/3 [============================] 100% 1 sec/1 sec 1.0 MB + +.. note:: + + If you call a command with the quiet flag (``-q``), the progress bar won't + be displayed. + +Instead of relying on the verbosity mode of the current command, you can also +force a format via ``setFormat()``:: + + $bar->setFormat('verbose'); + +The built-in formats are the following: + +* ``normal`` +* ``verbose`` +* ``very_verbose`` +* ``debug`` + +If you don't set the number of steps for your progress bar, use the ``_nomax`` +variants: + +* ``normal_nomax`` +* ``verbose_nomax`` +* ``very_verbose_nomax`` +* ``debug_nomax`` + +Custom Formats +~~~~~~~~~~~~~~ + +Instead of using the built-in formats, you can also set your own:: + + $bar->setFormat('%bar%'); + +This sets the format to only display the progress bar itself: + +.. code-block:: text + + >--------------------------- + =========>------------------ + ============================ + +A progress bar format is a string that contains specific placeholders (a name +enclosed with the ``%`` character); the placeholders are replaced based on the +current progress of the bar. Here is a list of the built-in placeholders: + +* ``current``: The current step; +* ``max``: The maximum number of steps (or 0 if no max is defined); +* ``bar``: The bar itself; +* ``percent``: The percentage of completion (not available if no max is defined); +* ``elapsed``: The time elapsed since the start of the progress bar; +* ``remaining``: The remaining time to complete the task (not available if no max is defined); +* ``estimated``: The estimated time to complete the task (not available if no max is defined); +* ``memory``: The current memory usage; +* ``message``: The current message attached to the progress bar. + +For instance, here is how you could set the format to be the same as the +``debug`` one:: + + $bar->setFormat(' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%'); + +Notice the ``:6s`` part added to some placeholders? That's how you can tweak +the appearance of the bar (formatting and alignment). The part after the colon +(``:``) is used to set the ``sprintf`` format of the string. + +The ``message`` placeholder is a bit special as you must set the value +yourself:: + + $bar->setMessage('Task starts'); + $bar->start(); + + $bar->setMessage('Task in progress...'); + $bar->advance(); + + // ... + + $bar->setMessage('Task is finished'); + $bar->finish(); + +Instead of setting the format for a given instance of a progress bar, you can +also define global formats:: + + ProgressBar::setFormatDefinition('minimal', 'Progress: %percent%%'); + + $bar = new ProgressBar($output, 3); + $bar->setFormat('minimal'); + +This code defines a new ``minimal`` format that you can then use for your +progress bars: + +.. code-block:: text + + Progress: 0% + Progress: 33% + Progress: 100% + +.. tip:: + + It is almost always better to redefine built-in formats instead of creating + new ones as that allows the display to automatically vary based on the + verbosity flag of the command. + +When defining a new style that contains placeholders that are only available +when the maximum number of steps is known, you should create a ``_nomax`` +variant:: + + ProgressBar::setFormatDefinition('minimal', '%percent%% %remaining%'); + ProgressBar::setFormatDefinition('minimal_nomax', '%percent%%'); + + $bar = new ProgressBar($output); + $bar->setFormat('minimal'); + +When displaying the progress bar, the format will automatically be set to +``minimal_nomax`` if the bar does not have a maximum number of steps like in +the example above. + +.. tip:: + + A format can contain any valid ANSI codes and can also use the + Symfony-specific way to set colors:: + + ProgressBar::setFormatDefinition( + 'minimal', + '%percent%\033[32m%\033[0m %remaining%' + ); + +.. note:: + + A format can span more than one line; that's very useful when you want to + display more contextual information alongside the progress bar (see the + example at the beginning of this article). + +Bar Settings +~~~~~~~~~~~~ + +Amongst the placeholders, ``bar`` is a bit special as all the characters used +to display it can be customized:: + + // the finished part of the bar + $progress->setBarCharacter('='); + + // the unfinished part of the bar + $progress->setEmptyBarCharacter(' '); + + // the progress character + $progress->setProgressCharacter('|'); + + // the bar width + $progress->setBarWidth(50); + +.. caution:: + + For performance reasons, be careful if you set the total number of steps + to a high number. For example, if you're iterating over a large number of + items, consider setting the redraw frequency to a higher value by calling + :method:`Symfony\\Component\\Console\\Helper\\ProgressBar::setRedrawFrequency`, + so it updates on only some iterations:: + + $progress = new ProgressBar($output, 50000); + $progress->start(); + + // update every 100 iterations + $progress->setRedrawFrequency(100); + + $i = 0; + while ($i++ < 50000) { + // ... do some work + + $progress->advance(); + } + +Custom Placeholders +~~~~~~~~~~~~~~~~~~~ + +If you want to display some information that depends on the progress bar +display that are not available in the list of built-in placeholders, you can +create your own. Let's see how you can create a ``remaining_steps`` placeholder +that displays the number of remaining steps:: + + ProgressBar::setPlaceholderFormatterDefinition( + 'remaining_steps', + function (ProgressBar $bar, OutputInterface $output) { + return $bar->getMaxSteps() - $bar->getProgress(); + } + ); + +.. versionadded:: 2.6 + The ``getProgress()`` method was called ``getStep()`` prior to Symfony 2.6. + +Custom Messages +~~~~~~~~~~~~~~~ + +The ``%message%`` placeholder allows you to specify a custom message to be +displayed with the progress bar. But if you need more than one, just define +your own:: + + $bar->setMessage('Task starts'); + $bar->setMessage('', 'filename'); + $bar->start(); + + $bar->setMessage('Task is in progress...'); + while ($file = array_pop($files)) { + $bar->setMessage($filename, 'filename'); + $bar->advance(); + } + + $bar->setMessage('Task is finished'); + $bar->setMessage('', 'filename'); + $bar->finish(); + +For the ``filename`` to be part of the progress bar, just add the +``%filename%`` placeholder in your format:: + + $bar->setFormat(" %message%\n %step%/%max%\n Working on %filename%"); diff --git a/components/console/helpers/progresshelper.rst b/components/console/helpers/progresshelper.rst index c7bc437860e..7d858d21691 100644 --- a/components/console/helpers/progresshelper.rst +++ b/components/console/helpers/progresshelper.rst @@ -4,14 +4,15 @@ Progress Helper =============== -.. versionadded:: 2.2 - The ``progress`` helper was introduced in Symfony 2.2. - .. versionadded:: 2.3 The ``setCurrent`` method was introduced in Symfony 2.3. -.. versionadded:: 2.4 - The ``clear`` method was introduced in Symfony 2.4. +.. caution:: + + The Progress Helper was deprecated in Symfony 2.5 and will be removed in + Symfony 3.0. You should now use the + :doc:`Progress Bar ` instead which + is more powerful. When executing longer-running commands, it may be helpful to show progress information, which updates as your command runs: @@ -28,7 +29,7 @@ pass it a total number of units, and advance the progress as your command execut while ($i++ < 50) { // ... do some work - // advance the progress bar 1 unit + // advances the progress bar 1 unit $progress->advance(); } @@ -82,7 +83,7 @@ To see other available options, check the API documentation for $progress->start($output, 50000); - // update every 100 iterations + // updates every 100 iterations $progress->setRedrawFrequency(100); $i = 0; diff --git a/components/console/helpers/questionhelper.rst b/components/console/helpers/questionhelper.rst new file mode 100644 index 00000000000..1d972a933c5 --- /dev/null +++ b/components/console/helpers/questionhelper.rst @@ -0,0 +1,266 @@ +.. index:: + single: Console Helpers; Question Helper + +Question Helper +=============== + +.. versionadded:: 2.5 + The Question Helper was introduced in Symfony 2.5. + +The :class:`Symfony\\Component\\Console\\Helper\\QuestionHelper` provides +functions to ask the user for more information. It is included in the default +helper set, which you can get by calling +:method:`Symfony\\Component\\Console\\Command\\Command::getHelperSet`:: + + $helper = $this->getHelper('question'); + +The Question Helper has a single method +:method:`Symfony\\Component\\Console\\Command\\Command::ask` that needs an +:class:`Symfony\\Component\\Console\\Output\\InputInterface` instance as the +first argument, an :class:`Symfony\\Component\\Console\\Output\\OutputInterface` +instance as the second argument and a +:class:`Symfony\\Component\\Console\\Question\\Question` as last argument. + +Asking the User for Confirmation +-------------------------------- + +Suppose you want to confirm an action before actually executing it. Add +the following to your command:: + + use Symfony\Component\Console\Question\ConfirmationQuestion; + // ... + + $helper = $this->getHelper('question'); + $question = new ConfirmationQuestion('Continue with this action?', false); + + if (!$helper->ask($input, $output, $question)) { + return; + } + +In this case, the user will be asked "Continue with this action?". If the user +answers with ``y`` it returns ``true`` or ``false`` if they answer with ``n``. +The second argument to +:method:`Symfony\\Component\\Console\\Question\\ConfirmationQuestion::__construct` +is the default value to return if the user doesn't enter any input. Any other +input will ask the same question again. + +Asking the User for Information +------------------------------- + +You can also ask a question with more than a simple yes/no answer. For instance, +if you want to know a bundle name, you can add this to your command:: + + use Symfony\Component\Console\Question\Question; + // ... + + $question = new Question('Please enter the name of the bundle', 'AcmeDemoBundle'); + + $bundle = $helper->ask($input, $output, $question); + +The user will be asked "Please enter the name of the bundle". They can type +some name which will be returned by the +:method:`Symfony\\Component\\Console\\Helper\\QuestionHelper::ask` method. +If they leave it empty, the default value (``AcmeDemoBundle`` here) is returned. + +Let the User Choose from a List of Answers +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you have a predefined set of answers the user can choose from, you +could use a :class:`Symfony\\Component\\Console\\Question\\ChoiceQuestion` +which makes sure that the user can only enter a valid string +from a predefined list:: + + use Symfony\Component\Console\Question\ChoiceQuestion; + // ... + + $helper = $this->getHelper('question'); + $question = new ChoiceQuestion( + 'Please select your favorite color (defaults to red)', + array('red', 'blue', 'yellow'), + 0 + ); + $question->setErrorMessage('Color %s is invalid.'); + + $color = $helper->ask($input, $output, $question); + $output->writeln('You have just selected: '.$color); + + // ... do something with the color + +The option which should be selected by default is provided with the third +argument of the constructor. The default is ``null``, which means that no +option is the default one. + +If the user enters an invalid string, an error message is shown and the user +is asked to provide the answer another time, until they enter a valid string +or reach the maximum number of attempts. The default value for the maximum number +of attempts is ``null``, which means infinite number of attempts. You can define +your own error message using +:method:`Symfony\\Component\\Console\\Question\\ChoiceQuestion::setErrorMessage`. + +Multiple Choices +................ + +Sometimes, multiple answers can be given. The ``ChoiceQuestion`` provides this +feature using comma separated values. This is disabled by default, to enable +this use :method:`Symfony\\Component\\Console\\Question\\ChoiceQuestion::setMultiselect`:: + + use Symfony\Component\Console\Question\ChoiceQuestion; + // ... + + $helper = $this->getHelper('question'); + $question = new ChoiceQuestion( + 'Please select your favorite colors (defaults to red and blue)', + array('red', 'blue', 'yellow'), + '0,1' + ); + $question->setMultiselect(true); + + $colors = $helper->ask($input, $output, $question); + $output->writeln('You have just selected: ' . implode(', ', $colors)); + +Now, when the user enters ``1,2``, the result will be: +``You have just selected: blue, yellow``. + +If the user does not enter anything, the result will be: +``You have just selected: red, blue``. + +Autocompletion +~~~~~~~~~~~~~~ + +You can also specify an array of potential answers for a given question. These +will be autocompleted as the user types:: + + use Symfony\Component\Console\Question\Question; + // ... + + $bundles = array('AcmeDemoBundle', 'AcmeBlogBundle', 'AcmeStoreBundle'); + $question = new Question('Please enter the name of a bundle', 'FooBundle'); + $question->setAutocompleterValues($bundles); + + $name = $helper->ask($input, $output, $question); + +Hiding the User's Response +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can also ask a question and hide the response. This is particularly +convenient for passwords:: + + use Symfony\Component\Console\Question\Question; + // ... + + $question = new Question('What is the database password?'); + $question->setHidden(true); + $question->setHiddenFallback(false); + + $password = $helper->ask($input, $output, $question); + +.. caution:: + + When you ask for a hidden response, Symfony will use either a binary, change + stty mode or use another trick to hide the response. If none is available, + it will fallback and allow the response to be visible unless you set this + behavior to ``false`` using + :method:`Symfony\\Component\\Console\\Question\\Question::setHiddenFallback` + like in the example above. In this case, a ``RuntimeException`` + would be thrown. + +Validating the Answer +--------------------- + +You can even validate the answer. For instance, in a previous example you asked +for the bundle name. Following the Symfony naming conventions, it should +be suffixed with ``Bundle``. You can validate that by using the +:method:`Symfony\\Component\\Console\\Question\\Question::setValidator` +method:: + + use Symfony\Component\Console\Question\Question; + // ... + + $question = new Question('Please enter the name of the bundle', 'AcmeDemoBundle'); + $question->setValidator(function ($answer) { + if ('Bundle' !== substr($answer, -6)) { + throw new \RuntimeException( + 'The name of the bundle should be suffixed with \'Bundle\'' + ); + } + return $answer; + }); + $question->setMaxAttempts(2); + + $name = $helper->ask($input, $output, $question); + +The ``$validator`` is a callback which handles the validation. It should +throw an exception if there is something wrong. The exception message is displayed +in the console, so it is a good practice to put some useful information in it. The +callback function should also return the value of the user's input if the validation +was successful. + +You can set the max number of times to ask with the +:method:`Symfony\\Component\\Console\\Question\\Question::setMaxAttempts` method. +If you reach this max number it will use the default value. Using ``null`` means +the amount of attempts is infinite. The user will be asked as long as they provide an +invalid answer and will only be able to proceed if their input is valid. + +Validating a Hidden Response +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can also use a validator with a hidden question:: + + use Symfony\Component\Console\Question\Question; + // ... + + $helper = $this->getHelper('question'); + + $question = new Question('Please enter your password'); + $question->setValidator(function ($value) { + if (trim($value) == '') { + throw new \Exception('The password can not be empty'); + } + + return $value; + }); + $question->setHidden(true); + $question->setMaxAttempts(20); + + $password = $helper->ask($input, $output, $question); + + +Testing a Command that Expects Input +------------------------------------ + +If you want to write a unit test for a command which expects some kind of input +from the command line, you need to set the helper input stream:: + + use Symfony\Component\Console\Helper\QuestionHelper; + use Symfony\Component\Console\Helper\HelperSet; + use Symfony\Component\Console\Tester\CommandTester; + + // ... + public function testExecute() + { + // ... + $commandTester = new CommandTester($command); + + $helper = $command->getHelper('question'); + $helper->setInputStream($this->getInputStream('Test\\n')); + // Equals to a user inputting "Test" and hitting ENTER + // If you need to enter a confirmation, "yes\n" will work + + $commandTester->execute(array('command' => $command->getName())); + + // $this->assertRegExp('/.../', $commandTester->getDisplay()); + } + + protected function getInputStream($input) + { + $stream = fopen('php://memory', 'r+', false); + fputs($stream, $input); + rewind($stream); + + return $stream; + } + +By setting the input stream of the ``QuestionHelper``, you imitate what the +console would do internally with all user input through the cli. This way +you can test any user interaction (even complex ones) by passing an appropriate +input stream. diff --git a/components/console/helpers/table.rst b/components/console/helpers/table.rst new file mode 100644 index 00000000000..0f3511fe040 --- /dev/null +++ b/components/console/helpers/table.rst @@ -0,0 +1,142 @@ +.. index:: + single: Console Helpers; Table + +Table +===== + +.. versionadded:: 2.5 + The ``Table`` class was introduced in Symfony 2.5 as a replacement for the + :doc:`Table Helper `. + +When building a console application it may be useful to display tabular data: + +.. code-block:: text + + +---------------+--------------------------+------------------+ + | ISBN | Title | Author | + +---------------+--------------------------+------------------+ + | 99921-58-10-7 | Divine Comedy | Dante Alighieri | + | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | + | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien | + | 80-902734-1-6 | And Then There Were None | Agatha Christie | + +---------------+--------------------------+------------------+ + +To display a table, use :class:`Symfony\\Component\\Console\\Helper\\Table`, +set the headers, set the rows and then render the table:: + + use Symfony\Component\Console\Helper\Table; + + $table = new Table($output); + $table + ->setHeaders(array('ISBN', 'Title', 'Author')) + ->setRows(array( + array('99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'), + array('9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens'), + array('960-425-059-0', 'The Lord of the Rings', 'J. R. R. Tolkien'), + array('80-902734-1-6', 'And Then There Were None', 'Agatha Christie'), + )) + ; + $table->render(); + +You can add a table separator anywhere in the output by passing an instance of +:class:`Symfony\\Component\\Console\\Helper\\TableSeparator` as a row:: + + use Symfony\Component\Console\Helper\TableSeparator; + + $table->setRows(array( + array('99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'), + array('9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens'), + new TableSeparator(), + array('960-425-059-0', 'The Lord of the Rings', 'J. R. R. Tolkien'), + array('80-902734-1-6', 'And Then There Were None', 'Agatha Christie'), + )); + +.. code-block:: text + + +---------------+--------------------------+------------------+ + | ISBN | Title | Author | + +---------------+--------------------------+------------------+ + | 99921-58-10-7 | Divine Comedy | Dante Alighieri | + | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | + +---------------+--------------------------+------------------+ + | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien | + | 80-902734-1-6 | And Then There Were None | Agatha Christie | + +---------------+--------------------------+------------------+ + +The table style can be changed to any built-in styles via +:method:`Symfony\\Component\\Console\\Helper\\Table::setStyle`:: + + // same as calling nothing + $table->setStyle('default'); + + // changes the default style to compact + $table->setStyle('compact'); + $table->render(); + +This code results in: + +.. code-block:: text + + ISBN Title Author + 99921-58-10-7 Divine Comedy Dante Alighieri + 9971-5-0210-0 A Tale of Two Cities Charles Dickens + 960-425-059-0 The Lord of the Rings J. R. R. Tolkien + 80-902734-1-6 And Then There Were None Agatha Christie + +You can also set the style to ``borderless``:: + + $table->setStyle('borderless'); + $table->render(); + +which outputs: + +.. code-block:: text + + =============== ========================== ================== + ISBN Title Author + =============== ========================== ================== + 99921-58-10-7 Divine Comedy Dante Alighieri + 9971-5-0210-0 A Tale of Two Cities Charles Dickens + 960-425-059-0 The Lord of the Rings J. R. R. Tolkien + 80-902734-1-6 And Then There Were None Agatha Christie + =============== ========================== ================== + +If the built-in styles do not fit your need, define your own:: + + use Symfony\Component\Console\Helper\TableStyle; + + // by default, this is based on the default style + $style = new TableStyle(); + + // customize the style + $style + ->setHorizontalBorderChar('|') + ->setVerticalBorderChar('-') + ->setCrossingChar(' ') + ; + + // use the style for this table + $table->setStyle($style); + +Here is a full list of things you can customize: + +* :method:`Symfony\\Component\\Console\\Helper\\TableStyle::setPaddingChar` +* :method:`Symfony\\Component\\Console\\Helper\\TableStyle::setHorizontalBorderChar` +* :method:`Symfony\\Component\\Console\\Helper\\TableStyle::setVerticalBorderChar` +* :method:`Symfony\\Component\\Console\\Helper\\TableStyle::setCrossingChar` +* :method:`Symfony\\Component\\Console\\Helper\\TableStyle::setCellHeaderFormat` +* :method:`Symfony\\Component\\Console\\Helper\\TableStyle::setCellRowFormat` +* :method:`Symfony\\Component\\Console\\Helper\\TableStyle::setBorderFormat` +* :method:`Symfony\\Component\\Console\\Helper\\TableStyle::setPadType` + +.. tip:: + + You can also register a style globally:: + + // register the style under the colorful name + Table::setStyleDefinition('colorful', $style); + + // use it for a table + $table->setStyle('colorful'); + + This method can also be used to override a built-in style. diff --git a/components/console/helpers/tablehelper.rst b/components/console/helpers/tablehelper.rst index e852796a187..0e508b4d4d6 100644 --- a/components/console/helpers/tablehelper.rst +++ b/components/console/helpers/tablehelper.rst @@ -7,6 +7,13 @@ Table Helper .. versionadded:: 2.3 The ``table`` helper was introduced in Symfony 2.3. +.. caution:: + + The Table Helper was deprecated in Symfony 2.5 and will be removed in + Symfony 3.0. You should now use the + :doc:`Table ` class instead which is + more powerful. + When building a console application it may be useful to display tabular data: .. image:: /images/components/console/table.png @@ -32,9 +39,6 @@ table rendering: using named layouts or by customizing rendering options. Customize Table Layout using Named Layouts ------------------------------------------ -.. versionadded:: 2.4 - The ``TableHelper::LAYOUT_COMPACT`` layout was introduced in Symfony 2.4. - The Table helper ships with three preconfigured table layouts: * ``TableHelper::LAYOUT_DEFAULT`` diff --git a/components/console/index.rst b/components/console/index.rst index c814942d018..1ae77d50566 100644 --- a/components/console/index.rst +++ b/components/console/index.rst @@ -6,6 +6,9 @@ Console introduction usage + changing_default_command single_command_tool + console_arguments events + logger helpers/index diff --git a/components/console/introduction.rst b/components/console/introduction.rst index 6182690864d..18861b5831d 100644 --- a/components/console/introduction.rst +++ b/components/console/introduction.rst @@ -88,6 +88,8 @@ an ``Application`` and adds commands to it:: writeln(...); } -.. versionadded:: 2.4 - The :method:`Symfony\\Component\\Console\\Output\\Output::isQuiet`, - :method:`Symfony\\Component\\Console\\Output\\Output::isVerbose`, - :method:`Symfony\\Component\\Console\\Output\\Output::isVeryVerbose` and - :method:`Symfony\\Component\\Console\\Output\\Output::isDebug` - methods were introduced in Symfony 2.4 - There are also more semantic methods you can use to test for each of the verbosity levels:: @@ -330,7 +325,7 @@ declare a one-letter shortcut that you can call with a single dash like .. tip:: It is also possible to make an option *optionally* accept a value (so that - ``--yell`` or ``--yell=loud`` work). Options can also be configured to + ``--yell``, ``--yell=loud`` or ``--yell loud`` work). Options can also be configured to accept an array of values. For example, add a new option to the command that can be used to specify @@ -392,11 +387,11 @@ You can combine ``VALUE_IS_ARRAY`` with ``VALUE_REQUIRED`` or ``VALUE_OPTIONAL`` $this // ... ->addOption( - 'iterations', + 'colors', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, - 'How many times should the message be printed?', - 1 + 'Which colors do you like?', + array('blue', 'red') ); Console Helpers @@ -405,10 +400,12 @@ Console Helpers The console component also contains a set of "helpers" - different small tools capable of helping you with different tasks: -* :doc:`/components/console/helpers/dialoghelper`: interactively ask the user for information +* :doc:`/components/console/helpers/questionhelper`: interactively ask the user for information * :doc:`/components/console/helpers/formatterhelper`: customize the output colorization -* :doc:`/components/console/helpers/progresshelper`: shows a progress bar -* :doc:`/components/console/helpers/tablehelper`: displays tabular data as a table +* :doc:`/components/console/helpers/progressbar`: shows a progress bar +* :doc:`/components/console/helpers/table`: displays tabular data as a table + +.. _component-console-testing-commands: Testing Commands ---------------- @@ -462,9 +459,11 @@ method:: $command = $application->find('demo:greet'); $commandTester = new CommandTester($command); - $commandTester->execute( - array('command' => $command->getName(), 'name' => 'Fabien', '--iterations' => 5) - ); + $commandTester->execute(array( + 'command' => $command->getName(), + 'name' => 'Fabien', + '--iterations' => 5, + )); $this->assertRegExp('/Fabien/', $commandTester->getDisplay()); } @@ -529,7 +528,9 @@ Learn More! * :doc:`/components/console/usage` * :doc:`/components/console/single_command_tool` +* :doc:`/components/console/changing_default_command` * :doc:`/components/console/events` +* :doc:`/components/console/console_arguments` .. _Packagist: https://packagist.org/packages/symfony/console .. _ANSICON: https://github.com/adoxa/ansicon/releases diff --git a/components/console/logger.rst b/components/console/logger.rst new file mode 100644 index 00000000000..43951c63130 --- /dev/null +++ b/components/console/logger.rst @@ -0,0 +1,108 @@ +.. index:: + single: Console; Logger + +Using the Logger +================ + +.. versionadded:: 2.5 + The :class:`Symfony\\Component\\Console\\Logger\\ConsoleLogger` was + introduced in Symfony 2.5. + +The Console component comes with a standalone logger complying with the +`PSR-3`_ standard. Depending on the verbosity setting, log messages will +be sent to the :class:`Symfony\\Component\\Console\\Output\\OutputInterface` +instance passed as a parameter to the constructor. + +The logger does not have any external dependency except ``php-fig/log``. +This is useful for console applications and commands needing a lightweight +PSR-3 compliant logger:: + + namespace Acme; + + use Psr\Log\LoggerInterface; + + class MyDependency + { + private $logger; + + public function __construct(LoggerInterface $logger) + { + $this->logger = $logger; + } + + public function doStuff() + { + $this->logger->info('I love Tony Vairelles\' hairdresser.'); + } + } + +You can rely on the logger to use this dependency inside a command:: + + namespace Acme\Console\Command; + + use Acme\MyDependency; + use Symfony\Component\Console\Command\Command; + use Symfony\Component\Console\Input\InputInterface; + use Symfony\Component\Console\Output\OutputInterface; + use Symfony\Component\Console\Logger\ConsoleLogger; + + class MyCommand extends Command + { + protected function configure() + { + $this + ->setName('my:command') + ->setDescription( + 'Use an external dependency requiring a PSR-3 logger' + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $logger = new ConsoleLogger($output); + + $myDependency = new MyDependency($logger); + $myDependency->doStuff(); + } + } + +The dependency will use the instance of +:class:`Symfony\\Component\\Console\\Logger\\ConsoleLogger` as logger. +Log messages emitted will be displayed on the console output. + +Verbosity +--------- + +Depending on the verbosity level that the command is run, messages may or +may not be sent to the :class:`Symfony\\Component\\Console\\Output\\OutputInterface` +instance. + +By default, the console logger behaves like the +:doc:`Monolog's Console Handler `. +The association between the log level and the verbosity can be configured +through the second parameter of the :class:`Symfony\\Component\\Console\\ConsoleLogger` +constructor:: + + // ... + $verbosityLevelMap = array( + LogLevel::NOTICE => OutputInterface::VERBOSITY_NORMAL, + LogLevel::INFO => OutputInterface::VERBOSITY_NORMAL, + ); + $logger = new ConsoleLogger($output, $verbosityLevelMap); + +Color +----- + +The logger outputs the log messages formatted with a color reflecting their +level. This behavior is configurable through the third parameter of the +constructor:: + + // ... + $formatLevelMap = array( + LogLevel::CRITICAL => self::INFO, + LogLevel::DEBUG => self::ERROR, + ); + $logger = new ConsoleLogger($output, array(), $formatLevelMap); + +.. _PSR-3: http://www.php-fig.org/psr/psr-3/ diff --git a/components/console/single_command_tool.rst b/components/console/single_command_tool.rst index 5082575def6..609f7a0c2e3 100644 --- a/components/console/single_command_tool.rst +++ b/components/console/single_command_tool.rst @@ -1,5 +1,5 @@ .. index:: - single: Console; Single command application + single: Console; Single command application Building a single Command Application ===================================== diff --git a/components/css_selector.rst b/components/css_selector.rst index b62c58d94a3..2eaf063169c 100644 --- a/components/css_selector.rst +++ b/components/css_selector.rst @@ -76,7 +76,7 @@ web-browser. * link-state selectors: ``:link``, ``:visited``, ``:target`` * selectors based on user action: ``:hover``, ``:focus``, ``:active`` -* UI-state selectors: ``:invalid``, ``:indeterminate`` (however, ``:enabled``, +* UI-state selectors: ``:invalid``, ``:indeterminate`` (however, ``:enabled``, ``:disabled``, ``:checked`` and ``:unchecked`` are available) Pseudo-elements (``:before``, ``:after``, ``:first-line``, diff --git a/components/debug/class_loader.rst b/components/debug/class_loader.rst index 34a06bce3e7..ff906101ed1 100644 --- a/components/debug/class_loader.rst +++ b/components/debug/class_loader.rst @@ -5,10 +5,6 @@ Debugging a Class Loader ======================== -.. versionadded:: 2.4 - The ``DebugClassLoader`` of the Debug component was introduced in Symfony 2.4. - Previously, it was located in the ClassLoader component. - The :class:`Symfony\\Component\\Debug\\DebugClassLoader` attempts to throw more helpful exceptions when a class isn't found by the registered autoloaders. All autoloaders that implement a ``findFile()`` method are replaced @@ -17,6 +13,6 @@ with a ``DebugClassLoader`` wrapper. Using the ``DebugClassLoader`` is as easy as calling its static :method:`Symfony\\Component\\Debug\\DebugClassLoader::enable` method:: - use Symfony\Component\ClassLoader\DebugClassLoader; + use Symfony\Component\Debug\DebugClassLoader; DebugClassLoader::enable(); diff --git a/components/debug/introduction.rst b/components/debug/introduction.rst index 8f3fc17102e..b5aa07ea9e8 100644 --- a/components/debug/introduction.rst +++ b/components/debug/introduction.rst @@ -16,8 +16,8 @@ Installation You can install the component in many different ways: -* Use the official Git repository (https://github.com/symfony/Debug); -* :doc:`Install it via Composer ` (``symfony/debug`` on `Packagist`_). +* :doc:`Install it via Composer ` (``symfony/debug`` on `Packagist`_); +* Use the official Git repository (https://github.com/symfony/Debug). Usage ----- diff --git a/components/dependency_injection/_imports-parameters-note.rst.inc b/components/dependency_injection/_imports-parameters-note.rst.inc index 3815b54e09b..2f35ec5bdef 100644 --- a/components/dependency_injection/_imports-parameters-note.rst.inc +++ b/components/dependency_injection/_imports-parameters-note.rst.inc @@ -18,7 +18,8 @@ + xsi:schemaLocation="http://symfony.com/schema/dic/services + http://symfony.com/schema/dic/services/services-1.0.xsd"> diff --git a/components/dependency_injection/advanced.rst b/components/dependency_injection/advanced.rst index 2d07d0eab13..cbf50ff4fae 100644 --- a/components/dependency_injection/advanced.rst +++ b/components/dependency_injection/advanced.rst @@ -10,26 +10,19 @@ Marking Services as public / private When defining services, you'll usually want to be able to access these definitions within your application code. These services are called ``public``. For example, the ``doctrine`` service registered with the container when using the DoctrineBundle -is a public service as you can access it via:: +is a public service. This means that you can fetch it from the container +using the ``get()`` method:: $doctrine = $container->get('doctrine'); -However, there are use-cases when you don't want a service to be public. This -is common when a service is only defined because it could be used as an -argument for another service. +In some cases, a service *only* exists to be injected into another service +and is *not* intended to be fetched directly from the container as shown +above. .. _inlined-private-services: -.. note:: - - If you use a private service as an argument to only one other service, - this will result in an inlined instantiation (e.g. ``new PrivateFooBar()``) - inside this other service, making it publicly unavailable at runtime. - -Simply said: A service will be private when you do not want to access it -directly from your code. - -Here is an example: +In these cases, to get a minor performance boost, you can set the service +to be *not* public (i.e. private): .. configuration-block:: @@ -60,10 +53,19 @@ Here is an example: $definition->setPublic(false); $container->setDefinition('foo', $definition); -Now that the service is private, you *cannot* call:: +What makes private services special is that, if they are only injected once, +they are converted from services to inlined instantiations (e.g. ``new PrivateThing()``). +This increases the container's performance. + +Now that the service is private, you *should not* fetch the service directly +from the container:: $container->get('foo'); +This *may or may not work*, depending on if the service could be inlined. +Simply said: A service can be marked as private if you do not want to access +it directly from your code. + However, if a service has been marked as private, you can still alias it (see below) to access this service (via the alias). @@ -221,3 +223,95 @@ the service itself gets loaded. To do so, you can use the ``file`` directive. Notice that Symfony will internally call the PHP statement ``require_once``, which means that your file will be included only once per request. + +Decorating Services +------------------- + +.. versionadded:: 2.5 + Decorated services were introduced in Symfony 2.5. + +When overriding an existing definition, the old service is lost: + +.. code-block:: php + + $container->register('foo', 'FooService'); + + // this is going to replace the old definition with the new one + // old definition is lost + $container->register('foo', 'CustomFooService'); + +Most of the time, that's exactly what you want to do. But sometimes, +you might want to decorate the old one instead. In this case, the +old service should be kept around to be able to reference it in the +new one. This configuration replaces ``foo`` with a new one, but keeps +a reference of the old one as ``bar.inner``: + +.. configuration-block:: + + .. code-block:: yaml + + bar: + public: false + class: stdClass + decorates: foo + arguments: ["@bar.inner"] + + .. code-block:: xml + + + + + + .. code-block:: php + + use Symfony\Component\DependencyInjection\Reference; + + $container->register('bar', 'stdClass') + ->addArgument(new Reference('bar.inner')) + ->setPublic(false) + ->setDecoratedService('foo'); + +Here is what's going on here: the ``setDecoratedService()`` method tells +the container that the ``bar`` service should replace the ``foo`` service, +renaming ``foo`` to ``bar.inner``. +By convention, the old ``foo`` service is going to be renamed ``bar.inner``, +so you can inject it into your new service. + +.. note:: + The generated inner id is based on the id of the decorator service + (``bar`` here), not of the decorated service (``foo`` here). This is + mandatory to allow several decorators on the same service (they need to have + different generated inner ids). + + Most of the time, the decorator should be declared private, as you will not + need to retrieve it as ``bar`` from the container. The visibility of the + decorated ``foo`` service (which is an alias for ``bar``) will still be the + same as the original ``foo`` visibility. + +You can change the inner service name if you want to: + +.. configuration-block:: + + .. code-block:: yaml + + bar: + class: stdClass + public: false + decorates: foo + decoration_inner_name: bar.wooz + arguments: ["@bar.wooz"] + + .. code-block:: xml + + + + + + .. code-block:: php + + use Symfony\Component\DependencyInjection\Reference; + + $container->register('bar', 'stdClass') + ->addArgument(new Reference('bar.wooz')) + ->setPublic(false) + ->setDecoratedService('foo', 'bar.wooz'); diff --git a/components/dependency_injection/compilation.rst b/components/dependency_injection/compilation.rst index f3c074106cf..da9adee4f83 100644 --- a/components/dependency_injection/compilation.rst +++ b/components/dependency_injection/compilation.rst @@ -277,10 +277,6 @@ but also load a secondary one only if a certain parameter is set:: Prepending Configuration Passed to the Extension ------------------------------------------------ -.. versionadded:: 2.2 - The ability to prepend the configuration of a bundle was introduced in - Symfony 2.2. - An Extension can prepend the configuration of any Bundle before the ``load()`` method is called by implementing :class:`Symfony\\Component\\DependencyInjection\\Extension\\PrependExtensionInterface`:: diff --git a/components/dependency_injection/definitions.rst b/components/dependency_injection/definitions.rst index 46ed34ab36a..c9d1e72cb3d 100644 --- a/components/dependency_injection/definitions.rst +++ b/components/dependency_injection/definitions.rst @@ -40,7 +40,7 @@ Creating a new Definition ~~~~~~~~~~~~~~~~~~~~~~~~~ If you need to create a new definition rather than manipulate one retrieved -from then container then the definition class is :class:`Symfony\\Component\\DependencyInjection\\Definition`. +from the container then the definition class is :class:`Symfony\\Component\\DependencyInjection\\Definition`. Class ~~~~~ diff --git a/components/dependency_injection/factories.rst b/components/dependency_injection/factories.rst index 9f74efa50d5..56acbe9a7f1 100644 --- a/components/dependency_injection/factories.rst +++ b/components/dependency_injection/factories.rst @@ -4,6 +4,11 @@ Using a Factory to Create Services ================================== +.. versionadded:: 2.6 + The new :method:`Symfony\\Component\\DependencyInjection\\Definition::setFactory` + method was introduced in Symfony 2.6. Refer to older versions for the + syntax for factories prior to 2.6. + Symfony's Service Container provides a powerful way of controlling the creation of objects, allowing you to specify arguments passed to the constructor as well as calling methods and setting parameters. Sometimes, however, this @@ -15,9 +20,9 @@ the class. Suppose you have a factory that configures and returns a new ``NewsletterManager`` object:: - class NewsletterFactory + class NewsletterManagerFactory { - public function get() + public static function createNewsletterManager() { $newsletterManager = new NewsletterManager(); @@ -28,22 +33,17 @@ object:: } To make the ``NewsletterManager`` object available as a service, you can -configure the service container to use the ``NewsletterFactory`` factory -class: +configure the service container to use the +``NewsletterFactory::createNewsletterManager()`` factory method: .. configuration-block:: .. code-block:: yaml - parameters: - # ... - newsletter_manager.class: NewsletterManager - newsletter_factory.class: NewsletterFactory services: newsletter_manager: - class: "%newsletter_manager.class%" - factory_class: "%newsletter_factory.class%" - factory_method: get + class: NewsletterManager + factory: [NewsletterManagerFactory, createNewsletterManager] .. code-block:: xml @@ -52,18 +52,10 @@ class: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> - - - NewsletterManager - NewsletterFactory - - - + + + @@ -72,35 +64,26 @@ class: use Symfony\Component\DependencyInjection\Definition; // ... - $container->setParameter('newsletter_manager.class', 'NewsletterManager'); - $container->setParameter('newsletter_factory.class', 'NewsletterFactory'); - - $definition = new Definition('%newsletter_manager.class%'); - $definition->setFactoryClass('%newsletter_factory.class%'); - $definition->setFactoryMethod('get'); + $definition = new Definition('NewsletterManager'); + $definition->setFactory(array('NewsletterManagerFactory', 'createNewsletterManager')); $container->setDefinition('newsletter_manager', $definition); -When you specify the class to use for the factory (via ``factory_class``) -the method will be called statically. If the factory itself should be instantiated -and the resulting object's method called, configure the factory itself as a service. -In this case, the method (e.g. get) should be changed to be non-static: +Now, the method will be called statically. If the factory class itself should +be instantiated and the resulting object's method called, configure the factory +itself as a service. In this case, the method (e.g. get) should be changed to +be non-static. .. configuration-block:: .. code-block:: yaml - parameters: - # ... - newsletter_manager.class: NewsletterManager - newsletter_factory.class: NewsletterFactory services: - newsletter_factory: - class: "%newsletter_factory.class%" + newsletter_manager.factory: + class: NewsletterManagerFactory newsletter_manager: - class: "%newsletter_manager.class%" - factory_service: newsletter_factory - factory_method: get + class: NewsletterManager + factory: ["@newsletter_manager.factory", createNewsletterManager] .. code-block:: xml @@ -109,70 +92,48 @@ In this case, the method (e.g. get) should be changed to be non-static: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> - - - NewsletterManager - NewsletterFactory - - - + - + + + .. code-block:: php + use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Definition; // ... - $container->setParameter('newsletter_manager.class', 'NewsletterManager'); - $container->setParameter('newsletter_factory.class', 'NewsletterFactory'); + $container->register('newsletter_manager.factory', 'NewsletterManagerFactory'); - $container->setDefinition('newsletter_factory', new Definition( - '%newsletter_factory.class%' + $newsletterManager = new Definition(); + $newsletterManager->setFactory(array( + new Reference('newsletter_manager.factory'), + 'createNewsletterManager' )); - $container->setDefinition('newsletter_manager', new Definition( - '%newsletter_manager.class%' - ))->setFactoryService( - 'newsletter_factory' - )->setFactoryMethod( - 'get' - ); - -.. note:: - - The factory service is specified by its id name and not a reference to - the service itself. So, you do not need to use the @ syntax for this in - YAML configurations. + $container->setDefinition('newsletter_manager', $newsletterManager); Passing Arguments to the Factory Method --------------------------------------- If you need to pass arguments to the factory method, you can use the ``arguments`` -options inside the service container. For example, suppose the ``get`` method -in the previous example takes the ``templating`` service as an argument: +options inside the service container. For example, suppose the ``createNewsletterManager`` +method in the previous example takes the ``templating`` service as an argument: .. configuration-block:: .. code-block:: yaml - parameters: - # ... - newsletter_manager.class: NewsletterManager - newsletter_factory.class: NewsletterFactory services: - newsletter_factory: - class: "%newsletter_factory.class%" + newsletter_manager.factory: + class: NewsletterManagerFactory + newsletter_manager: - class: "%newsletter_manager.class%" - factory_service: newsletter_factory - factory_method: get + class: NewsletterManager + factory: ["@newsletter_manager.factory", createNewsletterManager] arguments: - "@templating" @@ -183,42 +144,30 @@ in the previous example takes the ``templating`` service as an argument: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> - - - NewsletterManager - NewsletterFactory - - - + - - - + + + .. code-block:: php + use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Definition; // ... - $container->setParameter('newsletter_manager.class', 'NewsletterManager'); - $container->setParameter('newsletter_factory.class', 'NewsletterFactory'); + $container->register('newsletter_manager.factory', 'NewsletterManagerFactory'); - $container->setDefinition('newsletter_factory', new Definition( - '%newsletter_factory.class%' - )); - $container->setDefinition('newsletter_manager', new Definition( - '%newsletter_manager.class%', + $newsletterManager = new Definition( + 'NewsletterManager', array(new Reference('templating')) - ))->setFactoryService( - 'newsletter_factory' - )->setFactoryMethod( - 'get' ); + $newsletterManager->setFactory(array( + new Reference('newsletter_manager.factory'), + 'createNewsletterManager' + )); + $container->setDefinition('newsletter_manager', $newsletterManager); diff --git a/components/dependency_injection/lazy_services.rst b/components/dependency_injection/lazy_services.rst index a2f29d69ff4..2af8471b054 100644 --- a/components/dependency_injection/lazy_services.rst +++ b/components/dependency_injection/lazy_services.rst @@ -30,21 +30,19 @@ the `ProxyManager bridge`_: .. code-block:: bash - $ php composer.phar require symfony/proxy-manager-bridge:2.3.* + $ composer require symfony/proxy-manager-bridge:~2.3 .. note:: If you're using the full-stack framework, the proxy manager bridge is already - included but the actual proxy manager needs to be included. Therefore add + included but the actual proxy manager needs to be included. So, run: - .. code-block:: json + .. code-block:: bash - "require": { - "ocramius/proxy-manager": "0.5.*" - } + $ php composer.phar require ocramius/proxy-manager:~0.5 - to your ``composer.json``. Afterwards compile your container and check - to make sure that you get a proxy for your lazy services. + Afterwards compile your container and check to make sure that you get + a proxy for your lazy services. Configuration ------------- diff --git a/components/dependency_injection/parameters.rst b/components/dependency_injection/parameters.rst index 9b47e5141b9..1ceb887a138 100644 --- a/components/dependency_injection/parameters.rst +++ b/components/dependency_injection/parameters.rst @@ -140,52 +140,6 @@ rather than being tied up and hidden with the service definition: If you were using this elsewhere as well, then you would only need to change the parameter value in one place if needed. -You can also use the parameters in the service definition, for example, -making the class of a service a parameter: - -.. configuration-block:: - - .. code-block:: yaml - - parameters: - mailer.transport: sendmail - mailer.class: Mailer - - services: - mailer: - class: "%mailer.class%" - arguments: ["%mailer.transport%"] - - .. code-block:: xml - - - - - - sendmail - Mailer - - - - - %mailer.transport% - - - - - .. code-block:: php - - use Symfony\Component\DependencyInjection\Reference; - - $container->setParameter('mailer.transport', 'sendmail'); - $container->setParameter('mailer.class', 'Mailer'); - - $container - ->register('mailer', '%mailer.class%') - ->addArgument('%mailer.transport%'); - .. note:: The percent sign inside a parameter or argument, as part of the string, must diff --git a/components/dependency_injection/parentservices.rst b/components/dependency_injection/parentservices.rst index 3158738fa15..b7729ab1f50 100644 --- a/components/dependency_injection/parentservices.rst +++ b/components/dependency_injection/parentservices.rst @@ -52,11 +52,6 @@ The service config for these classes would look something like this: .. code-block:: yaml - parameters: - # ... - newsletter_manager.class: NewsletterManager - greeting_card_manager.class: GreetingCardManager - services: my_mailer: # ... @@ -65,13 +60,13 @@ The service config for these classes would look something like this: # ... newsletter_manager: - class: "%newsletter_manager.class%" + class: NewsletterManager calls: - [setMailer, ["@my_mailer"]] - [setEmailFormatter, ["@my_email_formatter"]] greeting_card_manager: - class: "%greeting_card_manager.class%" + class: "GreetingCardManager" calls: - [setMailer, ["@my_mailer"]] - [setEmailFormatter, ["@my_email_formatter"]] @@ -83,12 +78,6 @@ The service config for these classes would look something like this: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> - - - NewsletterManager - GreetingCardManager - - @@ -98,7 +87,7 @@ The service config for these classes would look something like this: - + @@ -107,7 +96,7 @@ The service config for these classes would look something like this: - + @@ -124,14 +113,11 @@ The service config for these classes would look something like this: use Symfony\Component\DependencyInjection\Reference; // ... - $container->setParameter('newsletter_manager.class', 'NewsletterManager'); - $container->setParameter('greeting_card_manager.class', 'GreetingCardManager'); - $container->register('my_mailer', ...); $container->register('my_email_formatter', ...); $container - ->register('newsletter_manager', '%newsletter_manager.class%') + ->register('newsletter_manager', 'NewsletterManager') ->addMethodCall('setMailer', array( new Reference('my_mailer'), )) @@ -141,7 +127,7 @@ The service config for these classes would look something like this: ; $container - ->register('greeting_card_manager', '%greeting_card_manager.class%') + ->register('greeting_card_manager', 'GreetingCardManager') ->addMethodCall('setMailer', array( new Reference('my_mailer'), )) @@ -208,11 +194,11 @@ a parent for a service. - [setEmailFormatter, ["@my_email_formatter"]] newsletter_manager: - class: "%newsletter_manager.class%" + class: "NewsletterManager" parent: mail_manager greeting_card_manager: - class: "%greeting_card_manager.class%" + class: "GreetingCardManager" parent: mail_manager .. code-block:: xml @@ -237,12 +223,12 @@ a parent for a service. @@ -267,11 +253,11 @@ a parent for a service. $container->setDefinition('mail_manager', $mailManager); $newsletterManager = new DefinitionDecorator('mail_manager'); - $newsletterManager->setClass('%newsletter_manager.class%'); + $newsletterManager->setClass('NewsletterManager'); $container->setDefinition('newsletter_manager', $newsletterManager); $greetingCardManager = new DefinitionDecorator('mail_manager'); - $greetingCardManager->setClass('%greeting_card_manager.class%'); + $greetingCardManager->setClass('GreetingCardManager'); $container->setDefinition('greeting_card_manager', $greetingCardManager); In this context, having a ``parent`` service implies that the arguments and @@ -336,13 +322,13 @@ to the ``NewsletterManager`` class, the config would look like this: - [setEmailFormatter, ["@my_email_formatter"]] newsletter_manager: - class: "%newsletter_manager.class%" + class: "NewsletterManager" parent: mail_manager calls: - [setMailer, ["@my_alternative_mailer"]] greeting_card_manager: - class: "%greeting_card_manager.class%" + class: "GreetingCardManager" parent: mail_manager .. code-block:: xml @@ -371,7 +357,7 @@ to the ``NewsletterManager`` class, the config would look like this: @@ -381,7 +367,7 @@ to the ``NewsletterManager`` class, the config would look like this: @@ -408,7 +394,7 @@ to the ``NewsletterManager`` class, the config would look like this: $container->setDefinition('mail_manager', $mailManager); $newsletterManager = new DefinitionDecorator('mail_manager'); - $newsletterManager->setClass('%newsletter_manager.class%'); + $newsletterManager->setClass('NewsletterManager'); ->addMethodCall('setMailer', array( new Reference('my_alternative_mailer'), )) @@ -416,7 +402,7 @@ to the ``NewsletterManager`` class, the config would look like this: $container->setDefinition('newsletter_manager', $newsletterManager); $greetingCardManager = new DefinitionDecorator('mail_manager'); - $greetingCardManager->setClass('%greeting_card_manager.class%'); + $greetingCardManager->setClass('GreetingCardManager'); $container->setDefinition('greeting_card_manager', $greetingCardManager); The ``GreetingCardManager`` will receive the same dependencies as before, diff --git a/components/dependency_injection/tags.rst b/components/dependency_injection/tags.rst index ab523a10a77..b8442c3b933 100644 --- a/components/dependency_injection/tags.rst +++ b/components/dependency_injection/tags.rst @@ -39,12 +39,9 @@ Then, define the chain as a service: .. code-block:: yaml - parameters: - acme_mailer.transport_chain.class: TransportChain - services: acme_mailer.transport_chain: - class: "%acme_mailer.transport_chain.class%" + class: TransportChain .. code-block:: xml @@ -53,12 +50,8 @@ Then, define the chain as a service: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> - - TransportChain - - - + @@ -66,9 +59,7 @@ Then, define the chain as a service: use Symfony\Component\DependencyInjection\Definition; - $container->setParameter('acme_mailer.transport_chain.class', 'TransportChain'); - - $container->setDefinition('acme_mailer.transport_chain', new Definition('%acme_mailer.transport_chain.class%')); + $container->setDefinition('acme_mailer.transport_chain', new Definition('TransportChain')); Define Services with a custom Tag --------------------------------- @@ -153,7 +144,7 @@ custom tag:: $taggedServices = $container->findTaggedServiceIds( 'acme_mailer.transport' ); - foreach ($taggedServices as $id => $attributes) { + foreach ($taggedServices as $id => $tags) { $definition->addMethodCall( 'addTransport', array(new Reference($id)) @@ -178,7 +169,7 @@ run when the container is compiled:: use Symfony\Component\DependencyInjection\ContainerBuilder; $container = new ContainerBuilder(); - $container->addCompilerPass(new TransportCompilerPass); + $container->addCompilerPass(new TransportCompilerPass()); .. note:: @@ -211,7 +202,7 @@ To begin with, change the ``TransportChain`` class:: public function getTransport($alias) { if (array_key_exists($alias, $this->transports)) { - return $this->transports[$alias]; + return $this->transports[$alias]; } } } @@ -291,8 +282,8 @@ use this, update the compiler:: $taggedServices = $container->findTaggedServiceIds( 'acme_mailer.transport' ); - foreach ($taggedServices as $id => $tagAttributes) { - foreach ($tagAttributes as $attributes) { + foreach ($taggedServices as $id => $tags) { + foreach ($tags as $attributes) { $definition->addMethodCall( 'addTransport', array(new Reference($id), $attributes["alias"]) @@ -302,7 +293,7 @@ use this, update the compiler:: } } -The trickiest part is the ``$attributes`` variable. Because you can use the -same tag many times on the same service (e.g. you could theoretically tag -the same service 5 times with the ``acme_mailer.transport`` tag), ``$attributes`` -is an array of the tag information for each tag on that service. +The double loop may be confusing. This is because a service can have more than one +tag. You tag a service twice or more with the ``acme_mailer.transport`` tag. The +second foreach loop iterates over the ``acme_mailer.transport`` tags set for the +current service and gives you the attributes. diff --git a/components/dom_crawler.rst b/components/dom_crawler.rst index 2d17126b89f..c9809e28331 100644 --- a/components/dom_crawler.rst +++ b/components/dom_crawler.rst @@ -102,10 +102,6 @@ Both the :method:`Symfony\\Component\\DomCrawler\\Crawler::filterXPath` and XML namespaces, which can be either automatically discovered or registered explicitly. -.. versionadded:: 2.4 - Auto discovery and explicit registration of namespaces was introduced - in Symfony 2.4. - Consider the XML below: .. code-block:: xml @@ -193,6 +189,15 @@ Get all the child or parent nodes:: Accessing Node Values ~~~~~~~~~~~~~~~~~~~~~ +.. versionadded:: 2.6 + The :method:`Symfony\\Component\\DomCrawler\\Crawler::nodeName` + method was introduced in Symfony 2.6. + +Access the node name (HTML tag name) of the first node of the current selection (eg. "p" or "div"):: + + // will return the node name (HTML tag name) of the first child element under + $tag = $crawler->filterXPath('//body/*')->nodeName(); + Access the value of the first node of the current selection:: $message = $crawler->filterXPath('//body/p')->text(); @@ -447,10 +452,6 @@ directly:: Selecting Invalid Choice Values ............................... -.. versionadded:: 2.4 - The :method:`Symfony\\Component\\DomCrawler\\Form::disableValidation` - method was introduced in Symfony 2.4. - By default, choice fields (select, radio) have internal validation activated to prevent you from setting invalid values. If you want to be able to set invalid values, you can use the ``disableValidation()`` method on either diff --git a/components/event_dispatcher/introduction.rst b/components/event_dispatcher/introduction.rst index 4775280a54f..573061b0556 100644 --- a/components/event_dispatcher/introduction.rst +++ b/components/event_dispatcher/introduction.rst @@ -12,7 +12,7 @@ The EventDispatcher Component Introduction ------------ -Objected Oriented code has gone a long way to ensuring code extensibility. By +Object Oriented code has gone a long way to ensuring code extensibility. By creating classes that have well defined responsibilities, your code becomes more flexible and a developer can extend them with subclasses to modify their behaviors. But if they want to share the changes with other developers who have @@ -134,7 +134,7 @@ Connecting Listeners To take advantage of an existing event, you need to connect a listener to the dispatcher so that it can be notified when the event is dispatched. A call to -the dispatcher ``addListener()`` method associates any valid PHP callable to +the dispatcher's ``addListener()`` method associates any valid PHP callable to an event:: $listener = new AcmeListener(); @@ -158,7 +158,7 @@ The ``addListener()`` method takes up to three arguments: A `PHP callable`_ is a PHP variable that can be used by the ``call_user_func()`` function and returns ``true`` when passed to the ``is_callable()`` function. It can be a ``\Closure`` instance, an object - implementing an __invoke method (which is what closures are in fact), + implementing an ``__invoke`` method (which is what closures are in fact), a string representing a function, or an array representing an object method or a class method. @@ -489,10 +489,6 @@ which returns a boolean value:: EventDispatcher aware Events and Listeners ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. versionadded:: 2.4 - Since Symfony 2.4, the current event name and the ``EventDispatcher`` - itself are passed to the listeners as additional arguments. - The ``EventDispatcher`` always passes the dispatched event, the event's name and a reference to itself to the listeners. This can be used in some advanced usages of the ``EventDispatcher`` like dispatching other events in listeners, @@ -591,7 +587,7 @@ Dispatcher Shortcuts The :method:`EventDispatcher::dispatch ` method always returns an :class:`Symfony\\Component\\EventDispatcher\\Event` -object. This allows for various shortcuts. For example if one does not need +object. This allows for various shortcuts. For example, if one does not need a custom event object, one can simply rely on a plain :class:`Symfony\\Component\\EventDispatcher\\Event` object. You do not even need to pass this to the dispatcher as it will create one by default unless you @@ -626,22 +622,21 @@ and so on... Event Name Introspection ~~~~~~~~~~~~~~~~~~~~~~~~ -Since the ``EventDispatcher`` already knows the name of the event when dispatching -it, the event name is also injected into the -:class:`Symfony\\Component\\EventDispatcher\\Event` objects, making it available -to event listeners via the :method:`Symfony\\Component\\EventDispatcher\\Event::getName` -method. +.. versionadded:: 2.4 + Before Symfony 2.4, the event name and the event dispatcher had to be + requested from the ``Event`` instance. These methods are now deprecated. -The event name, (as with any other data in a custom event object) can be used as -part of the listener's processing logic:: +The ``EventDispatcher`` instance, as well as the name of the event that is +dispatched, are passed as arguments to the listener:: use Symfony\Component\EventDispatcher\Event; + use Symfony\Component\EventDispatcher\EventDispatcherInterface; class Foo { - public function myEventListener(Event $event) + public function myEventListener(Event $event, $eventName, EventDispatcherInterface $dispatcher) { - echo $event->getName(); + echo $eventName; } } diff --git a/components/event_dispatcher/traceable_dispatcher.rst b/components/event_dispatcher/traceable_dispatcher.rst index 6b1cb19db86..103c35150e1 100644 --- a/components/event_dispatcher/traceable_dispatcher.rst +++ b/components/event_dispatcher/traceable_dispatcher.rst @@ -5,19 +5,26 @@ The Traceable Event Dispatcher ============================== -The :class:`Symfony\\Component\\HttpKernel\\Debug\\TraceableEventDispatcher` +.. versionadded:: 2.5 + The ``TraceableEventDispatcher`` class was moved to the EventDispatcher + component in Symfony 2.5. Before, it was located in the HttpKernel component. + +The :class:`Symfony\\Component\\EventDispatcher\\Debug\\TraceableEventDispatcher` is an event dispatcher that wraps any other event dispatcher and can then be used to determine which event listeners have been called by the dispatcher. Pass the event dispatcher to be wrapped and an instance of the :class:`Symfony\\Component\\Stopwatch\\Stopwatch` to its constructor:: - use Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher; + use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher; use Symfony\Component\Stopwatch\Stopwatch; // the event dispatcher to debug $eventDispatcher = ...; - $traceableEventDispatcher = new TraceableEventDispatcher($eventDispatcher, new Stopwatch()); + $traceableEventDispatcher = new TraceableEventDispatcher( + $eventDispatcher, + new Stopwatch() + ); Now, the ``TraceableEventDispatcher`` can be used like any other event dispatcher to register event listeners and dispatch events:: @@ -27,11 +34,15 @@ to register event listeners and dispatch events:: // register an event listener $eventListener = ...; $priority = ...; - $traceableEventDispatcher->addListener('the-event-name', $eventListener, $priority); + $traceableEventDispatcher->addListener( + 'event.the_name', + $eventListener, + $priority + ); // dispatch an event $event = ...; - $traceableEventDispatcher->dispatch('the-event-name', $event); + $traceableEventDispatcher->dispatch('event.the_name', $event); After your application has been processed, you can use the :method:`Symfony\\Component\\EventDispatcher\\Debug\\TraceableEventDispatcherInterface::getCalledListeners` diff --git a/components/expression_language/caching.rst b/components/expression_language/caching.rst index 3ea35752e2f..d8724d49fbf 100644 --- a/components/expression_language/caching.rst +++ b/components/expression_language/caching.rst @@ -51,10 +51,10 @@ Using Parsed and Serialized Expressions Both ``evaluate()`` and ``compile()`` can handle ``ParsedExpression`` and ``SerializedParsedExpression``:: - use Symfony\Component\ExpressionLanguage\ParsedExpression; // ... - $expression = new ParsedExpression($language->parse('1 + 4')); + // the parse() method returns a ParsedExpression + $expression = $language->parse('1 + 4', array()); echo $language->evaluate($expression); // prints 5 @@ -64,7 +64,7 @@ Both ``evaluate()`` and ``compile()`` can handle ``ParsedExpression`` and // ... $expression = new SerializedParsedExpression( - serialize($language->parse('1 + 4')) + serialize($language->parse('1 + 4', array())) ); echo $language->evaluate($expression); // prints 5 diff --git a/components/expression_language/extending.rst b/components/expression_language/extending.rst index 5f1f743d631..4aded7c5aeb 100644 --- a/components/expression_language/extending.rst +++ b/components/expression_language/extending.rst @@ -51,31 +51,78 @@ an ``arguments`` variable as their first argument, which is equal to the second argument to ``evaluate()`` or ``compile()`` (e.g. the "values" when evaluating or the "names" if compiling). -Creating a new ExpressionLanguage Class ---------------------------------------- +.. _components-expression-language-provider: -When you use the ``ExpressionLanguage`` class in your library, it's recommend -to create a new ``ExpressionLanguage`` class and register the functions there. -Override ``registerFunctions`` to add your own functions:: +Using Expression Providers +-------------------------- - namespace Acme\AwesomeLib\ExpressionLanguage; +.. versionadded:: 2.6 + Expression providers were introduced in Symfony 2.6. - use Symfony\Component\ExpressionLanguage\ExpressionLanguage as BaseExpressionLanguage; +When you use the ``ExpressionLanguage`` class in your library, you often want +to add custom functions. To do so, you can create a new expression provider by +creating a class that implements +:class:`Symfony\\Component\\ExpressionLanguage\\ExpressionFunctionProviderInterface`. - class ExpressionLanguage extends BaseExpressionLanguage +This interface requires one method: +:method:`Symfony\\Component\\ExpressionLanguage\\ExpressionFunctionProviderInterface::getFunctions`, +which returns an array of expression functions (instances of +:class:`Symfony\\Component\\ExpressionLanguage\\ExpressionFunction`) to +register. + +.. code-block:: php + + use Symfony\Component\ExpressionLanguage\ExpressionFunction; + use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; + + class StringExpressionLanguageProvider implements ExpressionFunctionProviderInterface { - protected function registerFunctions() + public function getFunctions() { - parent::registerFunctions(); // do not forget to also register core functions + return array( + new ExpressionFunction('lowercase', function ($str) { + return sprintf('(is_string(%1$s) ? strtolower(%1$s) : %1$s)', $str); + }, function ($arguments, $str) { + if (!is_string($str)) { + return $str; + } + + return strtolower($str); + }), + ); + } + } + +You can register providers using +:method:`Symfony\\Component\\ExpressionLanguage\\ExpressionLanguage::registerProvider` +or by using the second argument of the constructor:: + + use Symfony\Component\ExpressionLanguage\ExpressionLanguage; + + // using the constructor + $language = new ExpressionLanguage(null, array( + new StringExpressionLanguageProvider(), + // ... + )); + + // using registerProvider() + $language->registerProvider(new StringExpressionLanguageProvider()); + +.. tip:: - $this->register('lowercase', function ($str) { - return sprintf('(is_string(%1$s) ? strtolower(%1$s) : %1$s)', $str); - }, function ($arguments, $str) { - if (!is_string($str)) { - return $str; - } + It is recommended to create your own ``ExpressionLanguage`` class in your + library. Now you can add the extension by overriding the constructor:: - return strtolower($str); - }); + use Symfony\Component\ExpressionLanguage\ExpressionLanguage as BaseExpressionLanguage; + use Symfony\Component\ExpressionLanguage\ParserCache\ParserCacheInterface; + + class ExpressionLanguage extends BaseExpressionLanguage + { + public function __construct(ParserCacheInterface $parser = null, array $providers = array()) + { + // prepend the default provider to let users override it easily + array_unshift($providers, new StringExpressionLanguageProvider()); + + parent::__construct($parser, $providers); + } } - } diff --git a/components/expression_language/introduction.rst b/components/expression_language/introduction.rst index a53c8aa960d..b14e37d5504 100644 --- a/components/expression_language/introduction.rst +++ b/components/expression_language/introduction.rst @@ -9,9 +9,6 @@ The ExpressionLanguage Component evaluate expressions. An expression is a one-liner that returns a value (mostly, but not limited to, Booleans). -.. versionadded:: 2.4 - The ExpressionLanguage component was introduced in Symfony 2.4. - Installation ------------ diff --git a/components/expression_language/syntax.rst b/components/expression_language/syntax.rst index 49dd43b42cd..56ec1da1173 100644 --- a/components/expression_language/syntax.rst +++ b/components/expression_language/syntax.rst @@ -259,7 +259,7 @@ For example:: $inGroup = $language->evaluate( 'user.group in ["human_resources", "marketing"]', array( - 'user' => $user + 'user' => $user, ) ); diff --git a/components/filesystem/index.rst b/components/filesystem/index.rst new file mode 100644 index 00000000000..e643f5a90f4 --- /dev/null +++ b/components/filesystem/index.rst @@ -0,0 +1,8 @@ +Filesystem +========== + +.. toctree:: + :maxdepth: 2 + + introduction + lock_handler diff --git a/components/filesystem.rst b/components/filesystem/introduction.rst similarity index 93% rename from components/filesystem.rst rename to components/filesystem/introduction.rst index 076d7c38d1f..fa7c0afbf06 100644 --- a/components/filesystem.rst +++ b/components/filesystem/introduction.rst @@ -6,9 +6,10 @@ The Filesystem Component The Filesystem component provides basic utilities for the filesystem. -.. versionadded:: 2.1 - The Filesystem component was introduced in Symfony 2.1. Previously, the - ``Filesystem`` class was located in the HttpKernel component. +.. tip:: + + A lock handler feature was introduce in symfony 2.6. + :doc:`See the documentation for more information `. Installation ------------ @@ -30,15 +31,11 @@ endpoint for filesystem operations:: $fs = new Filesystem(); try { - $fs->mkdir('/tmp/random/dir/' . mt_rand()); + $fs->mkdir('/tmp/random/dir/'.mt_rand()); } catch (IOExceptionInterface $e) { echo "An error occurred while creating your directory at ".$e->getPath(); } -.. versionadded:: 2.4 - The ``IOExceptionInterface`` and its ``getPath`` method were introduced in - Symfony 2.4. Prior to 2.4, you would catch the ``IOException`` class. - .. note:: Methods :method:`Symfony\\Component\\Filesystem\\Filesystem::mkdir`, @@ -51,11 +48,11 @@ endpoint for filesystem operations:: string, an array or any object implementing :phpclass:`Traversable` as the target argument. -Mkdir +mkdir ~~~~~ :method:`Symfony\\Component\\Filesystem\\Filesystem::mkdir` creates a directory. -On posix filesystems, directories are created with a default mode value +On POSIX filesystems, directories are created with a default mode value `0777`. You can use the second argument to set your own mode:: $fs->mkdir('/tmp/photos', 0700); @@ -65,7 +62,7 @@ On posix filesystems, directories are created with a default mode value You can pass an array or any :phpclass:`Traversable` object as the first argument. -Exists +exists ~~~~~~ :method:`Symfony\\Component\\Filesystem\\Filesystem::exists` checks for the @@ -82,7 +79,7 @@ presence of all files or directories and returns ``false`` if a file is missing: You can pass an array or any :phpclass:`Traversable` object as the first argument. -Copy +copy ~~~~ :method:`Symfony\\Component\\Filesystem\\Filesystem::copy` is used to copy @@ -96,7 +93,7 @@ the third boolean argument:: // image.jpg will be overridden $fs->copy('image-ICC.jpg', 'image.jpg', true); -Touch +touch ~~~~~ :method:`Symfony\\Component\\Filesystem\\Filesystem::touch` sets access and @@ -115,7 +112,7 @@ your own with the second argument. The third argument is the access time:: You can pass an array or any :phpclass:`Traversable` object as the first argument. -Chown +chown ~~~~~ :method:`Symfony\\Component\\Filesystem\\Filesystem::chown` is used to change @@ -131,7 +128,7 @@ the owner of a file. The third argument is a boolean recursive option:: You can pass an array or any :phpclass:`Traversable` object as the first argument. -Chgrp +chgrp ~~~~~ :method:`Symfony\\Component\\Filesystem\\Filesystem::chgrp` is used to change @@ -147,7 +144,7 @@ the group of a file. The third argument is a boolean recursive option:: You can pass an array or any :phpclass:`Traversable` object as the first argument. -Chmod +chmod ~~~~~ :method:`Symfony\\Component\\Filesystem\\Filesystem::chmod` is used to change @@ -163,7 +160,7 @@ the mode of a file. The fourth argument is a boolean recursive option:: You can pass an array or any :phpclass:`Traversable` object as the first argument. -Remove +remove ~~~~~~ :method:`Symfony\\Component\\Filesystem\\Filesystem::remove` is used to remove @@ -176,7 +173,7 @@ files, symlinks, directories easily:: You can pass an array or any :phpclass:`Traversable` object as the first argument. -Rename +rename ~~~~~~ :method:`Symfony\\Component\\Filesystem\\Filesystem::rename` is used to rename diff --git a/components/filesystem/lock_handler.rst b/components/filesystem/lock_handler.rst new file mode 100644 index 00000000000..d57889190ef --- /dev/null +++ b/components/filesystem/lock_handler.rst @@ -0,0 +1,69 @@ +LockHandler +=========== + +.. versionadded:: 2.6 + The lock handler feature was introduced in Symfony 2.6 + +What is a Lock? +--------------- + +File locking is a mechanism that restricts access to a computer file by allowing +only one user or process access at any specific time. This mechanism was +introduced a few decades ago for mainframes, but continues being useful for +modern applications. + +Symfony provides a LockHelper to help you use locks in your project. + +Usage +----- + +.. caution:: + + The lock handler only works if you're using just one server. If you have + several hosts, you must not use this helper. + +A lock can be used, for example, to allow only one instance of a command to run. + +.. code-block:: php + + use Symfony\Component\Filesystem\LockHandler; + + $lockHandler = new LockHandler('hello.lock'); + if (!$lockHandler->lock()) { + // the resource "hello" is already locked by another process + + return 0; + } + +The first argument of the constructor is a string that it will use as part of +the name of the file used to create the lock on the local filesystem. A best +practice for Symfony commands is to use the command name, such as ``acme:my-command``. +``LockHandler`` sanitizes the contents of the string before creating +the file, so you can pass any value for this argument. + +.. tip:: + + The ``.lock`` extension is optional, but it's a common practice to include + it. This will make it easier to find lock files on the filesystem. Moreover, + to avoid name collisions, ``LockHandler`` also appends a hash to the name of + the lock file. + +By default, the lock will be created in the temporary directory, but you can +optionally select the directory where locks are created by passing it as the +second argument of the constructor. + +The :method:`Symfony\\Component\\Filesystem\\LockHandler::lock` method tries to +acquire the lock. If the lock is acquired, the method returns ``true``, +``false`` otherwise. If the ``lock`` method is called several times on the same +instance it will always return ``true`` if the lock was acquired on the first +call. + +You can pass an optional blocking argument as the first argument to the +``lock()`` method, which defaults to ``false``. If this is set to ``true``, your +PHP code will wait indefinitely until the lock is released by another process. + +The resource is automatically released by PHP at the end of the script. In +addition, you can invoke the +:method:`Symfony\\Component\\Filesystem\\LockHandler::release` method to release +the lock explicitly. Once it's released, any other process can lock the +resource. diff --git a/components/finder.rst b/components/finder.rst index 621d98afe81..f3e8d5e8310 100644 --- a/components/finder.rst +++ b/components/finder.rst @@ -82,9 +82,6 @@ Search in several locations by chaining calls to $finder->files()->in(__DIR__)->in('/elsewhere'); -.. versionadded:: 2.2 - Wildcard support was introduced in version 2.2. - Use wildcard characters to search in the directories matching a pattern:: $finder->in('src/Symfony/*/*/Resources'); @@ -206,9 +203,6 @@ The ``notContains()`` method excludes files containing given pattern:: Path ~~~~ -.. versionadded:: 2.2 - The ``path()`` and ``notPath()`` methods were introduced in Symfony 2.2. - Restrict files and directories by path with the :method:`Symfony\\Component\\Finder\\Finder::path` method:: @@ -308,7 +302,8 @@ The contents of returned files can be read with foreach ($finder as $file) { $contents = $file->getContents(); - ... + + // ... } .. _strtotime: http://www.php.net/manual/en/datetime.formats.php diff --git a/components/form/form_events.rst b/components/form/form_events.rst index 8fb66a75f1c..3f30d596eff 100644 --- a/components/form/form_events.rst +++ b/components/form/form_events.rst @@ -61,15 +61,13 @@ The ``FormEvents::PRE_SET_DATA`` event is dispatched at the beginning of the :ref:`Form Events Information Table` -+-----------------+-----------+ -| Data type | Value | -+=================+===========+ -| Model data | ``null`` | -+-----------------+-----------+ -| Normalized data | ``null`` | -+-----------------+-----------+ -| View data | ``null`` | -+-----------------+-----------+ +=============== ======== +Data Type Value +=============== ======== +Model data ``null`` +Normalized data ``null`` +View data ``null`` +=============== ======== .. caution:: @@ -98,15 +96,13 @@ the form. :ref:`Form Events Information Table` -+-----------------+------------------------------------------------------+ -| Data type | Value | -+=================+======================================================+ -| Model data | Model data injected into ``setData()`` | -+-----------------+------------------------------------------------------+ -| Normalized data | Model data transformed using a model transformer | -+-----------------+------------------------------------------------------+ -| View data | Normalized data transformed using a view transformer | -+-----------------+------------------------------------------------------+ +=============== ==================================================== +Data Type Value +=============== ==================================================== +Model data Model data injected into ``setData()`` +Normalized data Model data transformed using a model transformer +View data Normalized data transformed using a view transformer +=============== ==================================================== .. sidebar:: ``FormEvents::POST_SET_DATA`` in the Form component @@ -138,20 +134,18 @@ The ``FormEvents::PRE_SUBMIT`` event is dispatched at the beginning of the It can be used to: -* Change data from the request, before submitting the data to the form. +* Change data from the request, before submitting the data to the form; * Add or remove form fields, before submitting the data to the form. :ref:`Form Events Information Table` -+-----------------+------------------------------------------+ -| Data type | Value | -+=================+==========================================+ -| Model data | Same as in ``FormEvents::POST_SET_DATA`` | -+-----------------+------------------------------------------+ -| Normalized data | Same as in ``FormEvents::POST_SET_DATA`` | -+-----------------+------------------------------------------+ -| View data | Same as in ``FormEvents::POST_SET_DATA`` | -+-----------------+------------------------------------------+ +=============== ======================================== +Data Type Value +=============== ======================================== +Model data Same as in ``FormEvents::POST_SET_DATA`` +Normalized data Same as in ``FormEvents::POST_SET_DATA`` +View data Same as in ``FormEvents::POST_SET_DATA`` +=============== ======================================== .. sidebar:: ``FormEvents::PRE_SUBMIT`` in the Form component @@ -173,15 +167,13 @@ It can be used to change data from the normalized representation of the data. :ref:`Form Events Information Table` -+-----------------+-------------------------------------------------------------------------------------+ -| Data type | Value | -+=================+=====================================================================================+ -| Model data | Same as in ``FormEvents::POST_SET_DATA`` | -+-----------------+-------------------------------------------------------------------------------------+ -| Normalized data | Data from the request reverse-transformed from the request using a view transformer | -+-----------------+-------------------------------------------------------------------------------------+ -| View data | Same as in ``FormEvents::POST_SET_DATA`` | -+-----------------+-------------------------------------------------------------------------------------+ +=============== =================================================================================== +Data Type Value +=============== =================================================================================== +Model data Same as in ``FormEvents::POST_SET_DATA`` +Normalized data Data from the request reverse-transformed from the request using a view transformer +View data Same as in ``FormEvents::POST_SET_DATA`` +=============== =================================================================================== .. caution:: @@ -205,15 +197,13 @@ It can be used to fetch data after denormalization. :ref:`Form Events Information Table` -+-----------------+---------------------------------------------------------------+ -| Data type | Value | -+=================+===============================================================+ -| Model data | Normalized data reverse-transformed using a model transformer | -+-----------------+---------------------------------------------------------------+ -| Normalized data | Same as in ``FormEvents::POST_SUBMIT`` | -+-----------------+---------------------------------------------------------------+ -| View data | Normalized data transformed using a view transformer | -+-----------------+---------------------------------------------------------------+ +=============== ============================================================= +Data Type Value +=============== ============================================================= +Model data Normalized data reverse-transformed using a model transformer +Normalized data Same as in ``FormEvents::POST_SUBMIT`` +View data Normalized data transformed using a view transformer +=============== ============================================================= .. caution:: @@ -248,19 +238,15 @@ processed. .. _component-form-event-table: -+------------------------+-------------------------------+------------------+ -| Name | ``FormEvents`` Constant | Event's data | -+========================+===============================+==================+ -| ``form.pre_set_data`` | ``FormEvents::PRE_SET_DATA`` | Model data | -+------------------------+-------------------------------+------------------+ -| ``form.post_set_data`` | ``FormEvents::POST_SET_DATA`` | Model data | -+------------------------+-------------------------------+------------------+ -| ``form.pre_bind`` | ``FormEvents::PRE_SUBMIT`` | Request data | -+------------------------+-------------------------------+------------------+ -| ``form.bind`` | ``FormEvents::SUBMIT`` | Normalized data | -+------------------------+-------------------------------+------------------+ -| ``form.post_bind`` | ``FormEvents::POST_SUBMIT`` | View data | -+------------------------+-------------------------------+------------------+ +====================== ============================= =============== +Name ``FormEvents`` Constant Event's Data +====================== ============================= =============== +``form.pre_set_data`` ``FormEvents::PRE_SET_DATA`` Model data +``form.post_set_data`` ``FormEvents::POST_SET_DATA`` Model data +``form.pre_bind`` ``FormEvents::PRE_SUBMIT`` Request data +``form.bind`` ``FormEvents::SUBMIT`` Normalized data +``form.post_bind`` ``FormEvents::POST_SUBMIT`` View data +====================== ============================= =============== .. versionadded:: 2.3 Before Symfony 2.3, ``FormEvents::PRE_SUBMIT``, ``FormEvents::SUBMIT`` @@ -323,7 +309,10 @@ callback for better readability:: { $builder->add('username', 'text'); $builder->add('show_email', 'checkbox'); - $builder->addEventListener(FormEvents::PRE_SET_DATA, array($this, 'onPreSetData')); + $builder->addEventListener( + FormEvents::PRE_SET_DATA, + array($this, 'onPreSetData') + ); } public function onPreSetData(FormEvent $event) diff --git a/components/form/introduction.rst b/components/form/introduction.rst index 21f2d669eb3..7321a1029a1 100644 --- a/components/form/introduction.rst +++ b/components/form/introduction.rst @@ -78,6 +78,12 @@ Behind the scenes, this uses a :class:`Symfony\\Component\\Form\\NativeRequestHa object to read data off of the correct PHP superglobals (i.e. ``$_POST`` or ``$_GET``) based on the HTTP method configured on the form (POST is default). +.. seealso:: + + If you need more control over exactly when your form is submitted or which + data is passed to it, you can use the :method:`Symfony\\Component\\Form\\FormInterface::submit` + for this. Read more about it :ref:`in the cookbook `. + .. sidebar:: Integration with the HttpFoundation Component If you use the HttpFoundation component, then you should add the @@ -137,7 +143,7 @@ and validated when binding the form. .. tip:: - If you're not using the HttpFoundation component, load use + If you're not using the HttpFoundation component, you can use :class:`Symfony\\Component\\Form\\Extension\\Csrf\\CsrfProvider\\DefaultCsrfProvider` instead, which relies on PHP's native session handling:: @@ -167,7 +173,7 @@ line to your ``composer.json`` file: } The TwigBridge integration provides you with several :doc:`Twig Functions ` -that help you render each the HTML widget, label and error for each field +that help you render the HTML widget, label and error for each field (as well as a few other things). To configure the integration, you'll need to bootstrap or access Twig and add the :class:`Symfony\\Bridge\\Twig\\Extension\\FormExtension`:: @@ -180,17 +186,17 @@ to bootstrap or access Twig and add the :class:`Symfony\\Bridge\\Twig\\Extension // this file comes with TwigBridge $defaultFormTheme = 'form_div_layout.html.twig'; - $vendorDir = realpath(__DIR__ . '/../vendor'); + $vendorDir = realpath(__DIR__.'/../vendor'); // the path to TwigBridge so Twig can locate the // form_div_layout.html.twig file $vendorTwigBridgeDir = - $vendorDir . '/symfony/twig-bridge/Symfony/Bridge/Twig'; + $vendorDir.'/symfony/twig-bridge/Symfony/Bridge/Twig'; // the path to your other templates - $viewsDir = realpath(__DIR__ . '/../views'); + $viewsDir = realpath(__DIR__.'/../views'); $twig = new Twig_Environment(new Twig_Loader_Filesystem(array( $viewsDir, - $vendorTwigBridgeDir . '/Resources/views/Form', + $vendorTwigBridgeDir.'/Resources/views/Form', ))); $formEngine = new TwigRendererEngine(array($defaultFormTheme)); $formEngine->setEnvironment($twig); @@ -309,10 +315,10 @@ Your integration with the Validation component will look something like this:: use Symfony\Component\Form\Extension\Validator\ValidatorExtension; use Symfony\Component\Validator\Validation; - $vendorDir = realpath(__DIR__ . '/../vendor'); - $vendorFormDir = $vendorDir . '/symfony/form/Symfony/Component/Form'; + $vendorDir = realpath(__DIR__.'/../vendor'); + $vendorFormDir = $vendorDir.'/symfony/form/Symfony/Component/Form'; $vendorValidatorDir = - $vendorDir . '/symfony/validator/Symfony/Component/Validator'; + $vendorDir.'/symfony/validator/Symfony/Component/Validator'; // create the validator - details will vary $validator = Validation::createValidator(); @@ -320,13 +326,13 @@ Your integration with the Validation component will look something like this:: // there are built-in translations for the core error messages $translator->addResource( 'xlf', - $vendorFormDir . '/Resources/translations/validators.en.xlf', + $vendorFormDir.'/Resources/translations/validators.en.xlf', 'en', 'validators' ); $translator->addResource( 'xlf', - $vendorValidatorDir . '/Resources/translations/validators.en.xlf', + $vendorValidatorDir.'/Resources/translations/validators.en.xlf', 'en', 'validators' ); @@ -489,6 +495,43 @@ 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. +Changing a Form's Method and Action +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 2.3 + The ability to configure the form method and action was introduced in + Symfony 2.3. + +By default, a form is submitted to the same URI that rendered the form with +an HTTP POST request. This behavior can be changed using the :ref:`form-option-action` +and :ref:`form-option-method` options (the ``method`` option is also used +by ``handleRequest()`` to determine whether a form has been submitted): + +.. configuration-block:: + + .. code-block:: php-standalone + + $formBuilder = $formFactory->createBuilder('form', null, array( + 'action' => '/search', + 'method' => 'GET', + )); + + // ... + + .. code-block:: php-symfony + + // ... + + public function searchAction() + { + $formBuilder = $this->createFormBuilder('form', null, array( + 'action' => '/search', + 'method' => 'GET', + )); + + // ... + } + .. _component-form-intro-handling-submission: Handling Form Submissions @@ -617,6 +660,51 @@ and the errors will display next to the fields on error. For a list of all of the built-in validation constraints, see :doc:`/reference/constraints`. +Accessing Form Errors +~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 2.5 + Before Symfony 2.5, ``getErrors()`` returned an array of ``FormError`` + objects. The return value was changed to ``FormErrorIterator`` in Symfony + 2.5. + +.. versionadded:: 2.5 + The ``$deep`` and ``$flatten`` arguments were introduced in Symfony 2.5. + +You can use the :method:`Symfony\\Component\\Form\\FormInterface::getErrors` +method to access the list of errors. It returns a +:class:`Symfony\\Component\\Form\\FormErrorIterator` instance:: + + $form = ...; + + // ... + + // a FormErrorIterator instance, but only errors attached to this + // form level (e.g. "global errors) + $errors = $form->getErrors(); + + // a FormErrorIterator instance, but only errors attached to the + // "firstName" field + $errors = $form['firstName']->getErrors(); + + // a FormErrorIterator instance in a flattened structure + // use getOrigin() to determine the form causing the error + $errors = $form->getErrors(true); + + // a FormErrorIterator instance representing the form tree structure + $errors = $form->getErrors(true, false); + +.. tip:: + + In older Symfony versions, ``getErrors()`` returned an array. To use the + errors the same way in Symfony 2.5 or newer, you have to pass them to + PHP's :phpfunction:`iterator_to_array` function:: + + $errorsAsArray = iterator_to_array($form->getErrors()); + + This is useful, for example, if you want to use PHP's ``array_`` function + on the form errors. + .. _Packagist: https://packagist.org/packages/symfony/form .. _Twig: http://twig.sensiolabs.org .. _`Twig Configuration`: http://twig.sensiolabs.org/doc/intro.html diff --git a/components/form/type_guesser.rst b/components/form/type_guesser.rst index 819e835ca8e..ae5f305a5c9 100644 --- a/components/form/type_guesser.rst +++ b/components/form/type_guesser.rst @@ -46,7 +46,7 @@ Start by creating the class and these methods. Next, you'll learn how to fill ea use Symfony\Component\Form\FormTypeGuesserInterface; - class PhpdocTypeGuesser implements FormTypeGuesserInterface + class PHPDocTypeGuesser implements FormTypeGuesserInterface { public function guessType($class, $property) { @@ -92,7 +92,7 @@ With this knowledge, you can easily implement the ``guessType`` method of the use Symfony\Component\Form\Guess\Guess; use Symfony\Component\Form\Guess\TypeGuess; - class PhpdocTypeGuesser implements FormTypeGuesserInterface + class PHPDocTypeGuesser implements FormTypeGuesserInterface { public function guessType($class, $property) { diff --git a/components/http_foundation/introduction.rst b/components/http_foundation/introduction.rst index 2c42b9931ea..6f188957b87 100644 --- a/components/http_foundation/introduction.rst +++ b/components/http_foundation/introduction.rst @@ -90,47 +90,47 @@ instance (or a sub-class of), which is a data holder class: All :class:`Symfony\\Component\\HttpFoundation\\ParameterBag` instances have methods to retrieve and update its data: -* :method:`Symfony\\Component\\HttpFoundation\\ParameterBag::all`: Returns - the parameters; +:method:`Symfony\\Component\\HttpFoundation\\ParameterBag::all` + Returns the parameters. -* :method:`Symfony\\Component\\HttpFoundation\\ParameterBag::keys`: Returns - the parameter keys; +:method:`Symfony\\Component\\HttpFoundation\\ParameterBag::keys` + Returns the parameter keys. -* :method:`Symfony\\Component\\HttpFoundation\\ParameterBag::replace`: - Replaces the current parameters by a new set; +:method:`Symfony\\Component\\HttpFoundation\\ParameterBag::replace` + Replaces the current parameters by a new set. -* :method:`Symfony\\Component\\HttpFoundation\\ParameterBag::add`: Adds - parameters; +:method:`Symfony\\Component\\HttpFoundation\\ParameterBag::add` + Adds parameters. -* :method:`Symfony\\Component\\HttpFoundation\\ParameterBag::get`: Returns a - parameter by name; +:method:`Symfony\\Component\\HttpFoundation\\ParameterBag::get` + Returns a parameter by name. -* :method:`Symfony\\Component\\HttpFoundation\\ParameterBag::set`: Sets a - parameter by name; +:method:`Symfony\\Component\\HttpFoundation\\ParameterBag::set` + Sets a parameter by name. -* :method:`Symfony\\Component\\HttpFoundation\\ParameterBag::has`: Returns - ``true`` if the parameter is defined; +:method:`Symfony\\Component\\HttpFoundation\\ParameterBag::has` + Returns ``true`` if the parameter is defined. -* :method:`Symfony\\Component\\HttpFoundation\\ParameterBag::remove`: Removes - a parameter. +:method:`Symfony\\Component\\HttpFoundation\\ParameterBag::remove` + Removes a parameter. The :class:`Symfony\\Component\\HttpFoundation\\ParameterBag` instance also has some methods to filter the input values: -* :method:`Symfony\\Component\\HttpFoundation\\ParameterBag::getAlpha`: Returns - the alphabetic characters of the parameter value; +:method:`Symfony\\Component\\HttpFoundation\\ParameterBag::getAlpha` + Returns the alphabetic characters of the parameter value; -* :method:`Symfony\\Component\\HttpFoundation\\ParameterBag::getAlnum`: Returns - the alphabetic characters and digits of the parameter value; +:method:`Symfony\\Component\\HttpFoundation\\ParameterBag::getAlnum` + Returns the alphabetic characters and digits of the parameter value; -* :method:`Symfony\\Component\\HttpFoundation\\ParameterBag::getDigits`: Returns - the digits of the parameter value; +:method:`Symfony\\Component\\HttpFoundation\\ParameterBag::getDigits` + Returns the digits of the parameter value; -* :method:`Symfony\\Component\\HttpFoundation\\ParameterBag::getInt`: Returns the - parameter value converted to integer; +:method:`Symfony\\Component\\HttpFoundation\\ParameterBag::getInt` + Returns the parameter value converted to integer; -* :method:`Symfony\\Component\\HttpFoundation\\ParameterBag::filter`: Filters the - parameter by using the PHP :phpfunction:`filter_var` function. +:method:`Symfony\\Component\\HttpFoundation\\ParameterBag::filter` + Filters the parameter by using the PHP :phpfunction:`filter_var` function. All getters takes up to three arguments: the first one is the parameter name and the second one is the default value to return if the parameter does not @@ -241,25 +241,21 @@ Accessing `Accept-*` Headers Data You can easily access basic data extracted from ``Accept-*`` headers by using the following methods: -* :method:`Symfony\\Component\\HttpFoundation\\Request::getAcceptableContentTypes`: - returns the list of accepted content types ordered by descending quality; +:method:`Symfony\\Component\\HttpFoundation\\Request::getAcceptableContentTypes` + Returns the list of accepted content types ordered by descending quality. -* :method:`Symfony\\Component\\HttpFoundation\\Request::getLanguages`: - returns the list of accepted languages ordered by descending quality; +:method:`Symfony\\Component\\HttpFoundation\\Request::getLanguages` + Returns the list of accepted languages ordered by descending quality. -* :method:`Symfony\\Component\\HttpFoundation\\Request::getCharsets`: - returns the list of accepted charsets ordered by descending quality; +:method:`Symfony\\Component\\HttpFoundation\\Request::getCharsets` + Returns the list of accepted charsets ordered by descending quality. -* :method:`Symfony\\Component\\HttpFoundation\\Request::getEncodings`: - returns the list of accepted charsets ordered by descending quality; +:method:`Symfony\\Component\\HttpFoundation\\Request::getEncodings` + Returns the list of accepted encodings ordered by descending quality. .. versionadded:: 2.4 The ``getEncodings()`` method was introduced in Symfony 2.4. -.. versionadded:: 2.2 - The :class:`Symfony\\Component\\HttpFoundation\\AcceptHeader` class was - introduced in Symfony 2.2. - If you need to get full access to parsed data from ``Accept``, ``Accept-Language``, ``Accept-Charset`` or ``Accept-Encoding``, you can use :class:`Symfony\\Component\\HttpFoundation\\AcceptHeader` utility class:: @@ -288,10 +284,6 @@ for more information about them. Overriding the Request ~~~~~~~~~~~~~~~~~~~~~~ -.. versionadded:: 2.4 - The :method:`Symfony\\Component\\HttpFoundation\\Request::setFactory` - method was introduced in Symfony 2.4. - The ``Request`` class should not be overridden as it is a data object that represents an HTTP message. But when moving from a legacy system, adding methods or changing some default behavior might help. In that case, register a @@ -339,10 +331,7 @@ code, and an array of HTTP headers:: array('content-type' => 'text/html') ); -.. versionadded:: 2.4 - Support for HTTP status code constants was introduced in Symfony 2.4. - -These information can also be manipulated after the Response object creation:: +This information can also be manipulated after the Response object creation:: $response->setContent('Hello World'); @@ -445,6 +434,8 @@ To redirect the client to another URL, you can use the $response = new RedirectResponse('http://example.com/'); +.. _streaming-response: + Streaming a Response ~~~~~~~~~~~~~~~~~~~~ @@ -494,10 +485,6 @@ abstracts the hard work behind a simple API:: $response->headers->set('Content-Disposition', $d); -.. versionadded:: 2.2 - The :class:`Symfony\\Component\\HttpFoundation\\BinaryFileResponse` - class was introduced in Symfony 2.2. - Alternatively, if you are serving a static file, you can use a :class:`Symfony\\Component\\HttpFoundation\\BinaryFileResponse`:: @@ -523,6 +510,13 @@ You can still set the ``Content-Type`` of the sent file, or change its ``Content 'filename.txt' ); +.. versionadded:: 2.6 + The ``deleteFileAfterSend()`` method was introduced in Symfony 2.6. + +It is possible to delete the file after the request is sent with the +:method:`Symfony\\Component\\HttpFoundation\\BinaryFileResponse::deleteFileAfterSend` method. +Please note that this will not work when the ``X-Sendfile`` header is set. + .. _component-http-foundation-json-response: Creating a JSON Response diff --git a/components/http_foundation/session_configuration.rst b/components/http_foundation/session_configuration.rst index 07413e7f162..c2608110621 100644 --- a/components/http_foundation/session_configuration.rst +++ b/components/http_foundation/session_configuration.rst @@ -181,7 +181,7 @@ which runs reasonably frequently. The ``cookie_lifetime`` would be set to a relatively high value, and the garbage collection ``gc_maxlifetime`` would be set to destroy sessions at whatever the desired idle period is. -The other option is to specifically checking if a session has expired after the +The other option is specifically check if a session has expired after the session is started. The session can be destroyed as required. This method of processing can allow the expiry of sessions to be integrated into the user experience, for example, by displaying a message. @@ -239,7 +239,7 @@ Save Handler Proxy ~~~~~~~~~~~~~~~~~~ A Save Handler Proxy is basically a wrapper around a Save Handler that was -introduced to support seamlessly the migration from PHP 5.3 to PHP 5.4+. It +introduced to seamlessly support the migration from PHP 5.3 to PHP 5.4+. It further creates an extension point from where custom logic can be added that works independently of which handler is being wrapped inside. diff --git a/components/http_foundation/sessions.rst b/components/http_foundation/sessions.rst index 27296fc7529..0034963f331 100644 --- a/components/http_foundation/sessions.rst +++ b/components/http_foundation/sessions.rst @@ -46,7 +46,7 @@ Quick example:: .. note:: - While it is recommended to explicitly start a session, a sessions will actually + While it is recommended to explicitly start a session, a session will actually start on demand, that is, if any session request is made to read/write session data. @@ -65,73 +65,76 @@ The :class:`Symfony\\Component\\HttpFoundation\\Session\\Session` class implemen The :class:`Symfony\\Component\\HttpFoundation\\Session\\Session` has a simple API as follows divided into a couple of groups. -Session workflow +Session Workflow +................ -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::start`: - Starts the session - do not use ``session_start()``; +:method:`Symfony\\Component\\HttpFoundation\\Session\\Session::start` + Starts the session - do not use ``session_start()``. -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::migrate`: - Regenerates the session ID - do not use ``session_regenerate_id()``. - This method can optionally change the lifetime of the new cookie that will - be emitted by calling this method; +:method:`Symfony\\Component\\HttpFoundation\\Session\\Session::migrate` + Regenerates the session ID - do not use ``session_regenerate_id()``. + This method can optionally change the lifetime of the new cookie that will + be emitted by calling this method. -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::invalidate`: - Clears all session data and regenerates session ID. Do not use ``session_destroy()``; +:method:`Symfony\\Component\\HttpFoundation\\Session\\Session::invalidate` + Clears all session data and regenerates session ID. Do not use ``session_destroy()``. -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::getId`: Gets the - session ID. Do not use ``session_id()``; +:method:`Symfony\\Component\\HttpFoundation\\Session\\Session::getId` + Gets the session ID. Do not use ``session_id()``. -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::setId`: Sets the - session ID. Do not use ``session_id()``; +:method:`Symfony\\Component\\HttpFoundation\\Session\\Session::setId` + Sets the session ID. Do not use ``session_id()``. -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::getName`: Gets the - session name. Do not use ``session_name()``; +:method:`Symfony\\Component\\HttpFoundation\\Session\\Session::getName` + Gets the session name. Do not use ``session_name()``. -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::setName`: Sets the - session name. Do not use ``session_name()``. +:method:`Symfony\\Component\\HttpFoundation\\Session\\Session::setName` + Sets the session name. Do not use ``session_name()``. -Session attributes +Session Attributes +.................. -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::set`: - Sets an attribute by key; +:method:`Symfony\\Component\\HttpFoundation\\Session\\Session::set` + Sets an attribute by key. -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::get`: - Gets an attribute by key; +:method:`Symfony\\Component\\HttpFoundation\\Session\\Session::get` + Gets an attribute by key. -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::all`: - Gets all attributes as an array of key => value; +:method:`Symfony\\Component\\HttpFoundation\\Session\\Session::all` + Gets all attributes as an array of key => value. -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::has`: - Returns true if the attribute exists; +:method:`Symfony\\Component\\HttpFoundation\\Session\\Session::has` + Returns true if the attribute exists. -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::replace`: - Sets multiple attributes at once: takes a keyed array and sets each key => value pair; +:method:`Symfony\\Component\\HttpFoundation\\Session\\Session::replace` + Sets multiple attributes at once: takes a keyed array and sets each key => value pair. -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::remove`: - Deletes an attribute by key; +:method:`Symfony\\Component\\HttpFoundation\\Session\\Session::remove` + Deletes an attribute by key. -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::clear`: - Clear all attributes. +:method:`Symfony\\Component\\HttpFoundation\\Session\\Session::clear` + Clear all attributes. The attributes are stored internally in a "Bag", a PHP object that acts like an array. A few methods exist for "Bag" management: -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::registerBag`: - Registers a :class:`Symfony\\Component\\HttpFoundation\\Session\\SessionBagInterface`; +:method:`Symfony\\Component\\HttpFoundation\\Session\\Session::registerBag` + Registers a :class:`Symfony\\Component\\HttpFoundation\\Session\\SessionBagInterface`. -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::getBag`: - Gets a :class:`Symfony\\Component\\HttpFoundation\\Session\\SessionBagInterface` by - bag name; +:method:`Symfony\\Component\\HttpFoundation\\Session\\Session::getBag` + Gets a :class:`Symfony\\Component\\HttpFoundation\\Session\\SessionBagInterface` by + bag name. -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::getFlashBag`: - Gets the :class:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface`. - This is just a shortcut for convenience. +:method:`Symfony\\Component\\HttpFoundation\\Session\\Session::getFlashBag` + Gets the :class:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface`. + This is just a shortcut for convenience. -Session metadata +Session Metadata +................ -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::getMetadataBag`: - Gets the :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\MetadataBag` - which contains information about the session. +:method:`Symfony\\Component\\HttpFoundation\\Session\\Session::getMetadataBag` + Gets the :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\MetadataBag` + which contains information about the session. Session Data Management ~~~~~~~~~~~~~~~~~~~~~~~ @@ -154,16 +157,16 @@ bag types if necessary. :class:`Symfony\\Component\\HttpFoundation\\Session\\SessionBagInterface` has the following API which is intended mainly for internal purposes: -* :method:`Symfony\\Component\\HttpFoundation\\Session\\SessionBagInterface::getStorageKey`: - Returns the key which the bag will ultimately store its array under in ``$_SESSION``. - Generally this value can be left at its default and is for internal use. +:method:`Symfony\\Component\\HttpFoundation\\Session\\SessionBagInterface::getStorageKey` + Returns the key which the bag will ultimately store its array under in ``$_SESSION``. + Generally this value can be left at its default and is for internal use. -* :method:`Symfony\\Component\\HttpFoundation\\Session\\SessionBagInterface::initialize`: - This is called internally by Symfony session storage classes to link bag data - to the session. +:method:`Symfony\\Component\\HttpFoundation\\Session\\SessionBagInterface::initialize` + This is called internally by Symfony session storage classes to link bag data + to the session. -* :method:`Symfony\\Component\\HttpFoundation\\Session\\SessionBagInterface::getName`: - Returns the name of the session bag. +:method:`Symfony\\Component\\HttpFoundation\\Session\\SessionBagInterface::getName` + Returns the name of the session bag. Attributes ~~~~~~~~~~ @@ -172,11 +175,11 @@ The purpose of the bags implementing the :class:`Symfony\\Component\\HttpFoundat is to handle session attribute storage. This might include things like user ID, and remember me login settings or other user based state information. -* :class:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBag` - This is the standard default implementation. +:class:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBag` + This is the standard default implementation. -* :class:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\NamespacedAttributeBag` - This implementation allows for attributes to be stored in a structured namespace. +:class:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\NamespacedAttributeBag` + This implementation allows for attributes to be stored in a structured namespace. Any plain key-value storage system is limited in the extent to which complex data can be stored since each key must be unique. You can achieve @@ -187,8 +190,12 @@ data is an array, for example a set of tokens. In this case, managing the array becomes a burden because you have to retrieve the array then process it and store it again:: - $tokens = array('tokens' => array('a' => 'a6c1e0b6', - 'b' => 'f4a7b1f3')); + $tokens = array( + 'tokens' => array( + 'a' => 'a6c1e0b6', + 'b' => 'f4a7b1f3', + ), + ); So any processing of this might quickly get ugly, even simply adding a token to the array:: @@ -198,7 +205,7 @@ the array:: $session->set('tokens', $tokens); With structured namespacing, the key can be translated to the array -structure like this using a namespace character (defaults to `/`):: +structure like this using a namespace character (defaults to ``/``):: $session->set('tokens/c', $value); @@ -207,29 +214,29 @@ This way you can easily access a key within the stored array directly and easily :class:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface` has a simple API -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::set`: - Sets an attribute by key; +:method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::set` + Sets an attribute by key. -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::get`: - Gets an attribute by key; +:method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::get` + Gets an attribute by key. -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::all`: - Gets all attributes as an array of key => value; +:method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::all` + Gets all attributes as an array of key => value. -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::has`: - Returns true if the attribute exists; +:method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::has` + Returns true if the attribute exists. -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::keys`: - Returns an array of stored attribute keys; +:method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::keys` + Returns an array of stored attribute keys. -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::replace`: - Sets multiple attributes at once: takes a keyed array and sets each key => value pair. +:method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::replace` + Sets multiple attributes at once: takes a keyed array and sets each key => value pair. -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::remove`: - Deletes an attribute by key; +:method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::remove` + Deletes an attribute by key. -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::clear`: - Clear the bag; +:method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::clear` + Clear the bag. Flash Messages ~~~~~~~~~~~~~~ @@ -243,49 +250,49 @@ updated page or an error page. Flash messages set in the previous page request would be displayed immediately on the subsequent page load for that session. This is however just one application for flash messages. -* :class:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\AutoExpireFlashBag` - In this implementation, messages set in one page-load will - be available for display only on the next page load. These messages will auto - expire regardless of if they are retrieved or not. +:class:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\AutoExpireFlashBag` + In this implementation, messages set in one page-load will + be available for display only on the next page load. These messages will auto + expire regardless of if they are retrieved or not. -* :class:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBag` - In this implementation, messages will remain in the session until - they are explicitly retrieved or cleared. This makes it possible to use ESI - caching. +:class:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBag` + In this implementation, messages will remain in the session until + they are explicitly retrieved or cleared. This makes it possible to use ESI + caching. :class:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface` has a simple API -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::add`: - Adds a flash message to the stack of specified type; +:method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::add` + Adds a flash message to the stack of specified type. -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::set`: - Sets flashes by type; This method conveniently takes both single messages as - a ``string`` or multiple messages in an ``array``. +:method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::set` + Sets flashes by type; This method conveniently takes both single messages as + a ``string`` or multiple messages in an ``array``. -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::get`: - Gets flashes by type and clears those flashes from the bag; +:method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::get` + Gets flashes by type and clears those flashes from the bag. -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::setAll`: - Sets all flashes, accepts a keyed array of arrays ``type => array(messages)``; +:method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::setAll` + Sets all flashes, accepts a keyed array of arrays ``type => array(messages)``. -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::all`: - Gets all flashes (as a keyed array of arrays) and clears the flashes from the bag; +:method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::all` + Gets all flashes (as a keyed array of arrays) and clears the flashes from the bag. -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::peek`: - Gets flashes by type (read only); +:method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::peek` + Gets flashes by type (read only). -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::peekAll`: - Gets all flashes (read only) as keyed array of arrays; +:method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::peekAll` + Gets all flashes (read only) as keyed array of arrays. -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::has`: - Returns true if the type exists, false if not; +:method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::has` + Returns true if the type exists, false if not. -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::keys`: - Returns an array of the stored flash types; +:method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::keys` + Returns an array of the stored flash types. -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::clear`: - Clears the bag; +:method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::clear` + Clears the bag. For simple applications it is usually sufficient to have one flash message per type, for example a confirmation notice after a form is submitted. However, diff --git a/components/http_kernel/introduction.rst b/components/http_kernel/introduction.rst index f011ef68633..10bec26f314 100644 --- a/components/http_kernel/introduction.rst +++ b/components/http_kernel/introduction.rst @@ -167,6 +167,11 @@ return a ``Response`` directly, or to add information to the ``Request`` (e.g. setting the locale or setting some other information on the ``Request`` attributes). +.. note:: + + When setting a response for the ``kernel.request`` event, the propagation + is stopped. This means listeners with lower priority won't be executed. + .. sidebar:: ``kernel.request`` in the Symfony Framework The most important listener to ``kernel.request`` in the Symfony Framework @@ -288,16 +293,15 @@ on the event object that's passed to listeners on this event. the Symfony Framework, and many deal with collecting profiler data when the profiler is enabled. - One interesting listener comes from the :doc:`SensioFrameworkExtraBundle `, + One interesting listener comes from the `SensioFrameworkExtraBundle`_, which is packaged with the Symfony Standard Edition. This listener's - :doc:`@ParamConverter ` - functionality allows you to pass a full object (e.g. a ``Post`` object) - to your controller instead of a scalar value (e.g. an ``id`` parameter - that was on your route). The listener - ``ParamConverterListener`` - uses - reflection to look at each of the arguments of the controller and tries - to use different methods to convert those to objects, which are then - stored in the ``attributes`` property of the ``Request`` object. Read the - next section to see why this is important. + `@ParamConverter`_ functionality allows you to pass a full object (e.g. a + ``Post`` object) to your controller instead of a scalar value (e.g. an + ``id`` parameter that was on your route). The listener - + ``ParamConverterListener`` - uses reflection to look at each of the + arguments of the controller and tries to use different methods to convert + those to objects, which are then stored in the ``attributes`` property of + the ``Request`` object. Read the next section to see why this is important. 4) Getting the Controller Arguments ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -392,17 +396,20 @@ At this stage, if no listener sets a response on the event, then an exception is thrown: either the controller *or* one of the view listeners must always return a ``Response``. +.. note:: + + When setting a response for the ``kernel.view`` event, the propagation + is stopped. This means listeners with lower priority won't be executed. + .. sidebar:: ``kernel.view`` in the Symfony Framework There is no default listener inside the Symfony Framework for the ``kernel.view`` - event. However, one core bundle - - :doc:`SensioFrameworkExtraBundle ` - - *does* add a listener to this event. If your controller returns an array, - and you place the :doc:`@Template ` - annotation above the controller, then this listener renders a template, - passes the array you returned from your controller to that template, - and creates a ``Response`` containing the returned content from that - template. + event. However, one core bundle - `SensioFrameworkExtraBundle`_ - *does* + add a listener to this event. If your controller returns an array, + and you place the `@Template`_ annotation above the controller, then this + listener renders a template, passes the array you returned from your + controller to that template, and creates a ``Response`` containing the + returned content from that template. Additionally, a popular community bundle `FOSRestBundle`_ implements a listener on this event which aims to give you a robust view layer @@ -472,6 +479,15 @@ you will trigger the ``kernel.terminate`` event where you can perform certain actions that you may have delayed in order to return the response as quickly as possible to the client (e.g. sending emails). +.. caution:: + + Internally, the HttpKernel makes use of the :phpfunction:`fastcgi_finish_request` + PHP function. This means that at the moment, only the `PHP FPM`_ server + API is able to send a response to the client while the server's PHP process + still performs some tasks. With all other server APIs, listeners to ``kernel.terminate`` + are still executed, but the response is not sent to the client until they + are all completed. + .. note:: Using the ``kernel.terminate`` event is optional, and should only be @@ -479,10 +495,9 @@ as possible to the client (e.g. sending emails). .. sidebar:: ``kernel.terminate`` in the Symfony Framework - If you use the SwiftmailerBundle with Symfony and use ``memory`` - spooling, then the :class:`Symfony\\Bundle\\SwiftmailerBundle\\EventListener\\EmailSenderListener` - is activated, which actually delivers any emails that you scheduled to - send during the request. + If you use the SwiftmailerBundle with Symfony and use ``memory`` spooling, + then the `EmailSenderListener`_ is activated, which actually delivers + any emails that you scheduled to send during the request. .. _component-http-kernel-kernel-exception: @@ -516,6 +531,11 @@ comes with an :class:`Symfony\\Component\\HttpKernel\\EventListener\\ExceptionLi which if you choose to use, will do this and more by default (see the sidebar below for more details). +.. note:: + + When setting a response for the ``kernel.exception`` event, the propagation + is stopped. This means listeners with lower priority won't be executed. + .. sidebar:: ``kernel.exception`` in the Symfony Framework There are two main listeners to ``kernel.exception`` when using the @@ -569,23 +589,17 @@ each event has their own event object: .. _component-http-kernel-event-table: -+-----------------------+----------------------------------+-------------------------------------------------------------------------------------+ -| Name | ``KernelEvents`` Constant | Argument passed to the listener | -+=======================+==================================+=====================================================================================+ -| kernel.request | ``KernelEvents::REQUEST`` | :class:`Symfony\\Component\\HttpKernel\\Event\\GetResponseEvent` | -+-----------------------+----------------------------------+-------------------------------------------------------------------------------------+ -| kernel.controller | ``KernelEvents::CONTROLLER`` | :class:`Symfony\\Component\\HttpKernel\\Event\\FilterControllerEvent` | -+-----------------------+----------------------------------+-------------------------------------------------------------------------------------+ -| kernel.view | ``KernelEvents::VIEW`` | :class:`Symfony\\Component\\HttpKernel\\Event\\GetResponseForControllerResultEvent` | -+-----------------------+----------------------------------+-------------------------------------------------------------------------------------+ -| kernel.response | ``KernelEvents::RESPONSE`` | :class:`Symfony\\Component\\HttpKernel\\Event\\FilterResponseEvent` | -+-----------------------+----------------------------------+-------------------------------------------------------------------------------------+ -| kernel.finish_request | ``KernelEvents::FINISH_REQUEST`` | :class:`Symfony\\Component\\HttpKernel\\Event\\FinishRequestEvent` | -+-----------------------+----------------------------------+-------------------------------------------------------------------------------------+ -| kernel.terminate | ``KernelEvents::TERMINATE`` | :class:`Symfony\\Component\\HttpKernel\\Event\\PostResponseEvent` | -+-----------------------+----------------------------------+-------------------------------------------------------------------------------------+ -| kernel.exception | ``KernelEvents::EXCEPTION`` | :class:`Symfony\\Component\\HttpKernel\\Event\\GetResponseForExceptionEvent` | -+-----------------------+----------------------------------+-------------------------------------------------------------------------------------+ +===================== ================================ =================================================================================== +Name ``KernelEvents`` Constant Argument passed to the listener +===================== ================================ =================================================================================== +kernel.request ``KernelEvents::REQUEST`` :class:`Symfony\\Component\\HttpKernel\\Event\\GetResponseEvent` +kernel.controller ``KernelEvents::CONTROLLER`` :class:`Symfony\\Component\\HttpKernel\\Event\\FilterControllerEvent` +kernel.view ``KernelEvents::VIEW`` :class:`Symfony\\Component\\HttpKernel\\Event\\GetResponseForControllerResultEvent` +kernel.response ``KernelEvents::RESPONSE`` :class:`Symfony\\Component\\HttpKernel\\Event\\FilterResponseEvent` +kernel.finish_request ``KernelEvents::FINISH_REQUEST`` :class:`Symfony\\Component\\HttpKernel\\Event\\FinishRequestEvent` +kernel.terminate ``KernelEvents::TERMINATE`` :class:`Symfony\\Component\\HttpKernel\\Event\\PostResponseEvent` +kernel.exception ``KernelEvents::EXCEPTION`` :class:`Symfony\\Component\\HttpKernel\\Event\\GetResponseForExceptionEvent` +===================== ================================ =================================================================================== .. _http-kernel-working-example: @@ -665,10 +679,6 @@ argument as follows:: $response = $kernel->handle($request, HttpKernelInterface::SUB_REQUEST); // do something with this response -.. versionadded:: 2.4 - The ``isMasterRequest()`` method was introduced in Symfony 2.4. - Prior, the ``getRequestType()`` method must be used. - This creates another full request-response cycle where this new ``Request`` is transformed into a ``Response``. The only difference internally is that some listeners (e.g. security) may only act upon the master request. Each listener @@ -695,3 +705,8 @@ look like this:: .. _reflection: http://php.net/manual/en/book.reflection.php .. _FOSRestBundle: https://github.com/friendsofsymfony/FOSRestBundle .. _`Create your own framework... on top of the Symfony2 Components`: http://fabien.potencier.org/article/50/create-your-own-framework-on-top-of-the-symfony2-components-part-1 +.. _`PHP FPM`: http://php.net/manual/en/install.fpm.php +.. _`SensioFrameworkExtraBundle`: http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/index.html +.. _`@ParamConverter`: http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html +.. _`@Template`: http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/view.html +.. _`EmailSenderListener`: https://github.com/symfony/SwiftmailerBundle/blob/master/EventListener/EmailSenderListener.php diff --git a/components/index.rst b/components/index.rst index 8e167cd2ac9..d16b78de06f 100644 --- a/components/index.rst +++ b/components/index.rst @@ -14,7 +14,7 @@ The Components dom_crawler event_dispatcher/index expression_language/index - filesystem + filesystem/index finder form/index http_foundation/index @@ -29,6 +29,7 @@ The Components stopwatch templating/index translation/index + var_dumper/index yaml/index .. include:: /components/map.rst.inc diff --git a/components/intl.rst b/components/intl.rst index bed02fc2b55..6e85544c774 100644 --- a/components/intl.rst +++ b/components/intl.rst @@ -22,8 +22,8 @@ Installation You can install the component in two different ways: -* Using the official Git repository (https://github.com/symfony/Intl); -* :doc:`Install it via Composer` (``symfony/intl`` on `Packagist`_). +* :doc:`Install it via Composer` (``symfony/intl`` on `Packagist`_); +* Using the official Git repository (https://github.com/symfony/Intl). If you install the component via Composer, the following classes and functions of the intl extension will be automatically provided if the intl extension is @@ -85,13 +85,13 @@ code:: the server. For example, consider that your development machines ship ICU 4.8 and the server - ICU 4.2. When you run ``php composer.phar update`` on the development machine, version + ICU 4.2. When you run ``composer update`` on the development machine, version 1.2.* of the Icu component will be installed. But after deploying the - application, ``php composer.phar install`` will fail with the following error: + application, ``composer install`` will fail with the following error: .. code-block:: bash - $ php composer.phar install + $ composer install Loading composer repositories with package information Installing dependencies from lock file Your requirements could not be resolved to an installable set of packages. @@ -104,8 +104,8 @@ code:: The error tells you that the requested version of the Icu component, version 1.2, is not compatible with PHP's ICU version 4.2. - One solution to this problem is to run ``php composer.phar update`` instead of - ``php composer.phar install``. It is highly recommended **not** to do this. The + One solution to this problem is to run ``composer update`` instead of + ``composer install``. It is highly recommended **not** to do this. The ``update`` command will install the latest versions of each Composer dependency to your production server and potentially break the application. @@ -130,7 +130,7 @@ code:: * "1.0.*" if the server does not have the intl extension installed; * "1.1.*" if the server is compiled with ICU 4.2 or lower. - Finally, run ``php composer.phar update symfony/icu`` on your development machine, test + Finally, run ``composer update symfony/icu`` on your development machine, test extensively and deploy again. The installation of the dependencies will now succeed. diff --git a/components/map.rst.inc b/components/map.rst.inc index 73eb44a964a..7b6ceae5db2 100644 --- a/components/map.rst.inc +++ b/components/map.rst.inc @@ -4,6 +4,7 @@ * :doc:`/components/class_loader/introduction` * :doc:`/components/class_loader/class_loader` + * :doc:`/components/class_loader/psr4_class_loader` * :doc:`/components/class_loader/map_class_loader` * :doc:`/components/class_loader/cache_class_loader` * :doc:`/components/class_loader/debug_class_loader` @@ -21,7 +22,10 @@ * :doc:`/components/console/introduction` * :doc:`/components/console/usage` * :doc:`/components/console/single_command_tool` + * :doc:`/components/console/changing_default_command` + * :doc:`/components/console/console_arguments` * :doc:`/components/console/events` + * :doc:`/components/console/logger` * :doc:`/components/console/helpers/index` * **CssSelector** @@ -67,9 +71,10 @@ * :doc:`/components/expression_language/extending` * :doc:`/components/expression_language/caching` -* **Filesystem** +* :doc:`/components/filesystem/index` - * :doc:`/components/filesystem` + * :doc:`/components/filesystem/introduction` + * :doc:`/components/filesystem/lock_handler` * **Finder** @@ -121,6 +126,7 @@ * :doc:`/components/security/firewall` * :doc:`/components/security/authentication` * :doc:`/components/security/authorization` + * :doc:`/components/security/secure_tools` * **Serializer** @@ -140,6 +146,11 @@ * :doc:`/components/translation/usage` * :doc:`/components/translation/custom_formats` +* :doc:`/components/var_dumper/index` + + * :doc:`/components/var_dumper/introduction` + * :doc:`/components/var_dumper/advanced` + * :doc:`/components/yaml/index` * :doc:`/components/yaml/introduction` diff --git a/components/options_resolver.rst b/components/options_resolver.rst index 6e239e2f421..7320962fdba 100644 --- a/components/options_resolver.rst +++ b/components/options_resolver.rst @@ -5,8 +5,9 @@ The OptionsResolver Component ============================= - The OptionsResolver component helps you configure objects with option - arrays. It supports default values, option constraints and lazy options. + The OptionsResolver component is :phpfunction:`array_replace` on steroids. + It allows you to create an options system with required options, defaults, + validation (type, value), normalization and more. Installation ------------ @@ -16,14 +17,19 @@ You can install the component in 2 different ways: * :doc:`Install it via Composer ` (``symfony/options-resolver`` on `Packagist`_); * Use the official Git repository (https://github.com/symfony/OptionsResolver). +Notes on Previous Versions +-------------------------- + +.. versionadded:: 2.6 + This documentation was written for Symfony 2.6 and later. If you use an older + version, please `read the Symfony 2.5 documentation`_. For a list of changes, + see the `CHANGELOG`_. + Usage ----- -Imagine you have a ``Mailer`` class which has 2 options: ``host`` and -``password``. These options are going to be handled by the OptionsResolver -Component. - -First, create the ``Mailer`` class:: +Imagine you have a ``Mailer`` class which has four options: ``host``, +``username``, ``password`` and ``port``:: class Mailer { @@ -31,71 +37,131 @@ First, create the ``Mailer`` class:: public function __construct(array $options = array()) { + $this->options = $options; } } -You could of course set the ``$options`` value directly on the property. Instead, -use the :class:`Symfony\\Component\\OptionsResolver\\OptionsResolver` class -and let it resolve the options by calling -:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::resolve`. -The advantages of doing this will become more obvious as you continue:: - - use Symfony\Component\OptionsResolver\OptionsResolver; +When accessing the ``$options``, you need to add a lot of boilerplate code to +check which options are set:: - // ... - public function __construct(array $options = array()) + class Mailer { - $resolver = new OptionsResolver(); + // ... + public function sendMail($from, $to) + { + $mail = ...; - $this->options = $resolver->resolve($options); + $mail->setHost(isset($this->options['host']) + ? $this->options['host'] + : 'smtp.example.org'); + + $mail->setUsername(isset($this->options['username']) + ? $this->options['username'] + : 'user'); + + $mail->setPassword(isset($this->options['password']) + ? $this->options['password'] + : 'pa$$word'); + + $mail->setPort(isset($this->options['port']) + ? $this->options['port'] + : 25); + + // ... + } } -The options property now is a well defined array with all resolved options -readily available:: +This boilerplate is hard to read and repetitive. Also, the default values of the +options are buried in the business logic of your code. Use the +:phpfunction:`array_replace` to fix that:: - // ... - public function sendMail($from, $to) + class Mailer { - $mail = ...; - $mail->setHost($this->options['host']); - $mail->setUsername($this->options['username']); - $mail->setPassword($this->options['password']); // ... + + public function __construct(array $options = array()) + { + $this->options = array_replace(array( + 'host' => 'smtp.example.org', + 'username' => 'user', + 'password' => 'pa$$word', + 'port' => 25, + ), $options); + } } -Configuring the OptionsResolver -------------------------------- +Now all four options are guaranteed to be set. But what happens if the user of +the ``Mailer`` class makes a mistake? -Now, try to actually use the class:: +.. code-block:: php $mailer = new Mailer(array( - 'host' => 'smtp.example.org', - 'username' => 'user', - 'password' => 'pa$$word', + 'usernme' => 'johndoe', )); -Right now, you'll receive a -:class:`Symfony\\Component\\OptionsResolver\\Exception\\InvalidOptionsException`, -which tells you that the options ``host`` and ``password`` do not exist. -This is because you need to configure the ``OptionsResolver`` first, so it -knows which options should be resolved. +No error will be shown. In the best case, the bug will appear during testing, +but the developer will spend time looking for the problem. In the worst case, +the bug might not appear until it's deployed to the live system. -.. tip:: +Fortunately, the :class:`Symfony\\Component\\OptionsResolver\\OptionsResolver` +class helps you to fix this problem:: - To check if an option exists, you can use the - :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::isKnown` - function. + use Symfony\Component\OptionsResolver\OptionsResolver; -A best practice is to put the configuration in a method (e.g. -``configureOptions``). You call this method in the constructor to configure -the ``OptionsResolver`` class:: + class Mailer + { + // ... - use Symfony\Component\OptionsResolver\OptionsResolver; - use Symfony\Component\OptionsResolver\OptionsResolverInterface; + public function __construct(array $options = array()) + { + $resolver = new OptionsResolver(); + $resolver->setDefaults(array( + 'host' => 'smtp.example.org', + 'username' => 'user', + 'password' => 'pa$$word', + 'port' => 25, + )); + + $this->options = $resolver->resolve($options); + } + } + +Like before, all options will be guaranteed to be set. Additionally, an +:class:`Symfony\\Component\\OptionsResolver\\Exception\\UndefinedOptionsException` +is thrown if an unknown option is passed:: + + $mailer = new Mailer(array( + 'usernme' => 'johndoe', + )); + // UndefinedOptionsException: The option "usernme" does not exist. + // Known options are: "host", "password", "port", "username" + +The rest of your code can access the values of the options without boilerplate +code:: + + // ... class Mailer { - protected $options; + // ... + + public function sendMail($from, $to) + { + $mail = ...; + $mail->setHost($this->options['host']); + $mail->setUsername($this->options['username']); + $mail->setPassword($this->options['password']); + $mail->setPort($this->options['port']); + // ... + } + } + +It's a good practice to split the option configuration into a separate method:: + + // ... + class Mailer + { + // ... public function __construct(array $options = array()) { @@ -105,300 +171,563 @@ the ``OptionsResolver`` class:: $this->options = $resolver->resolve($options); } - protected function configureOptions(OptionsResolverInterface $resolver) + protected function configureOptions(OptionsResolver $resolver) { - // ... configure the resolver, you will learn this - // in the sections below + $resolver->setDefaults(array( + 'host' => 'smtp.example.org', + 'username' => 'user', + 'password' => 'pa$$word', + 'port' => 25, + 'encryption' => null, + )); } } -Set default Values -~~~~~~~~~~~~~~~~~~ +First, your code becomes easier to read, especially if the constructor does more +than processing options. Second, sub-classes may now override the +``configureOptions()`` method to adjust the configuration of the options:: -Most of the options have a default value. You can configure these options by -calling :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setDefaults`:: + // ... + class GoogleMailer extends Mailer + { + protected function configureOptions(OptionsResolver $resolver) + { + parent::configureOptions($resolver); + + $resolver->setDefaults(array( + 'host' => 'smtp.google.com', + 'encryption' => 'ssl', + )); + } + } + +Required Options +~~~~~~~~~~~~~~~~ + +If an option must be set by the caller, pass that option to +:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setRequired`. +For example, to make the ``host`` option required, you can do:: // ... - protected function setDefaultOptions(OptionsResolverInterface $resolver) + class Mailer { // ... - $resolver->setDefaults(array( - 'username' => 'root', - )); + protected function configureOptions(OptionsResolver $resolver) + { + // ... + $resolver->setRequired('host'); + } } -This would add an option - ``username`` - and give it a default value of -``root``. If the user passes in a ``username`` option, that value will -override this default. You don't need to configure ``username`` as an optional -option. +.. versionadded:: 2.6 + As of Symfony 2.6, ``setRequired()`` accepts both an array of options or a + single option. Prior to 2.6, you could only pass arrays. -Required Options +If you omit a required option, a +:class:`Symfony\\Component\\OptionsResolver\\Exception\\MissingOptionsException` +will be thrown:: + + $mailer = new Mailer(); + + // MissingOptionsException: The required option "host" is missing. + +The :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setRequired` +method accepts a single name or an array of option names if you have more than +one required option:: + + // ... + class Mailer + { + // ... + + protected function configureOptions(OptionsResolver $resolver) + { + // ... + $resolver->setRequired(array('host', 'username', 'password')); + } + } + +.. versionadded:: 2.6 + The methods :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::isRequired` + and :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::getRequiredOptions` + were introduced in Symfony 2.6. + +Use :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::isRequired` to find +out if an option is required. You can use +:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::getRequiredOptions` to +retrieve the names of all required options:: + + // ... + class GoogleMailer extends Mailer + { + protected function configureOptions(OptionsResolver $resolver) + { + parent::configureOptions($resolver); + + if ($resolver->isRequired('host')) { + // ... + } + + $requiredOptions = $resolver->getRequiredOptions(); + } + } + +.. versionadded:: 2.6 + The methods :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::isMissing` + and :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::getMissingOptions` + were introduced in Symfony 2.6. + +If you want to check whether a required option is still missing from the default +options, you can use :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::isMissing`. +The difference between this and :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::isRequired` +is that this method will return false if a required option has already +been set:: + + // ... + class Mailer + { + // ... + + protected function configureOptions(OptionsResolver $resolver) + { + // ... + $resolver->setRequired('host'); + } + } + + // ... + class GoogleMailer extends Mailer + { + protected function configureOptions(OptionsResolver $resolver) + { + parent::configureOptions($resolver); + + $resolver->isRequired('host'); + // => true + + $resolver->isMissing('host'); + // => true + + $resolver->setDefault('host', 'smtp.google.com'); + + $resolver->isRequired('host'); + // => true + + $resolver->isMissing('host'); + // => false + } + } + +The method :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::getMissingOptions` +lets you access the names of all missing options. + +Type Validation +~~~~~~~~~~~~~~~ + +You can run additional checks on the options to make sure they were passed +correctly. To validate the types of the options, call +:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setAllowedTypes`:: + + // ... + class Mailer + { + // ... + + protected function configureOptions(OptionsResolver $resolver) + { + // ... + $resolver->setAllowedTypes('host', 'string'); + $resolver->setAllowedTypes('port', array('null', 'int')); + } + } + +For each option, you can define either just one type or an array of acceptable +types. You can pass any type for which an ``is_()`` function is defined +in PHP. Additionally, you may pass fully qualified class or interface names. + +If you pass an invalid option now, an +:class:`Symfony\\Component\\OptionsResolver\\Exception\\InvalidOptionsException` +is thrown:: + + $mailer = new Mailer(array( + 'host' => 25, + )); + + // InvalidOptionsException: The option "host" with value "25" is + // expected to be of type "string" + +In sub-classes, you can use :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::addAllowedTypes` +to add additional allowed types without erasing the ones already set. + +.. versionadded:: 2.6 + Before Symfony 2.6, ``setAllowedTypes()`` and ``addAllowedTypes()`` expected + the values to be given as an array mapping option names to allowed types: + ``$resolver->setAllowedTypes(array('port' => array('null', 'int')));`` + +Value Validation ~~~~~~~~~~~~~~~~ -The ``host`` option is required: the class can't work without it. You can set -the required options by calling -:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setRequired`:: +Some options can only take one of a fixed list of predefined values. For +example, suppose the ``Mailer`` class has a ``transport`` option which can be +one of ``sendmail``, ``mail`` and ``smtp``. Use the method +:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setAllowedValues` +to verify that the passed option contains one of these values:: // ... - protected function setDefaultOptions(OptionsResolverInterface $resolver) + class Mailer { - $resolver->setRequired(array('host')); + // ... + + protected function configureOptions(OptionsResolver $resolver) + { + // ... + $resolver->setDefault('transport', 'sendmail'); + $resolver->setAllowedValues('transport', array('sendmail', 'mail', 'smtp')); + } } -You are now able to use the class without errors:: +If you pass an invalid transport, an +:class:`Symfony\\Component\\OptionsResolver\\Exception\\InvalidOptionsException` +is thrown:: $mailer = new Mailer(array( - 'host' => 'smtp.example.org', + 'transport' => 'send-mail', )); - echo $mailer->getHost(); // 'smtp.example.org' + // InvalidOptionsException: The option "transport" has the value + // "send-mail", but is expected to be one of "sendmail", "mail", "smtp" -If you don't pass a required option, a -:class:`Symfony\\Component\\OptionsResolver\\Exception\\MissingOptionsException` -will be thrown. +For options with more complicated validation schemes, pass a closure which +returns ``true`` for acceptable values and ``false`` for invalid values:: -.. tip:: + $resolver->setAllowedValues(array( + // ... + $resolver->setAllowedValues('transport', function ($value) { + // return true or false + }); + )); - To determine if an option is required, you can use the - :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::isRequired` - method. +In sub-classes, you can use :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::addAllowedValues` +to add additional allowed values without erasing the ones already set. -Optional Options -~~~~~~~~~~~~~~~~ +.. versionadded:: 2.6 + Before Symfony 2.6, ``setAllowedValues()`` and ``addAllowedValues()`` expected + the values to be given as an array mapping option names to allowed values: + ``$resolver->setAllowedValues(array('transport' => array('sendmail', 'mail', 'smtp')));`` -Sometimes, an option can be optional (e.g. the ``password`` option in the -``Mailer`` class), but it doesn't have a default value. You can configure -these options by calling -:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setOptional`:: +Option Normalization +~~~~~~~~~~~~~~~~~~~~ + +Sometimes, option values need to be normalized before you can use them. For +instance, assume that the ``host`` should always start with ``http://``. To do +that, you can write normalizers. Normalizers are executed after validating an +option. You can configure a normalizer by calling +:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setNormalizer`:: // ... - protected function setDefaultOptions(OptionsResolverInterface $resolver) + class Mailer { // ... - $resolver->setOptional(array('password')); + protected function configureOptions(OptionsResolver $resolver) + { + // ... + + $resolver->setNormalizer('host', function ($options, $value) { + if ('http://' !== substr($value, 0, 7)) { + $value = 'http://'.$value; + } + + return $value; + }); + } } -Options with defaults are already marked as optional. +.. versionadded:: 2.6 + The method :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setNormalizer` + was introduced in Symfony 2.6. Before, you had to use + :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setNormalizers`. -.. tip:: +The normalizer receives the actual ``$value`` and returns the normalized form. +You see that the closure also takes an ``$options`` parameter. This is useful +if you need to use other options during normalization:: - When setting an option as optional, you can't be sure if it's in the array - or not. You have to check if the option exists before using it. + // ... + class Mailer + { + // ... + protected function configureOptions(OptionsResolver $resolver) + { + // ... + $resolver->setNormalizer('host', function ($options, $value) { + if (!in_array(substr($value, 0, 7), array('http://', 'https://'))) { + if ('ssl' === $options['encryption']) { + $value = 'https://'.$value; + } else { + $value = 'http://'.$value; + } + } - To avoid checking if it exists everytime, you can also set a default of - ``null`` to an option using the ``setDefaults()`` method (see `Set Default Values`_), - this means the element always exists in the array, but with a default of - ``null``. + return $value; + }); + } + } Default Values that Depend on another Option ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Suppose you add a ``port`` option to the ``Mailer`` class, whose default -value you guess based on the encryption. You can do that easily by using a -closure as the default value:: +Suppose you want to set the default value of the ``port`` option based on the +encryption chosen by the user of the ``Mailer`` class. More precisely, you want +to set the port to ``465`` if SSL is used and to ``25`` otherwise. + +You can implement this feature by passing a closure as the default value of +the ``port`` option. The closure receives the options as argument. Based on +these options, you can return the desired default value:: use Symfony\Component\OptionsResolver\Options; - use Symfony\Component\OptionsResolver\OptionsResolverInterface; // ... - protected function setDefaultOptions(OptionsResolverInterface $resolver) + class Mailer { // ... + protected function configureOptions(OptionsResolver $resolver) + { + // ... + $resolver->setDefault('encryption', null); - $resolver->setDefaults(array( - 'encryption' => null, - 'port' => function (Options $options) { + $resolver->setDefault('port', function (Options $options) { if ('ssl' === $options['encryption']) { return 465; } return 25; - }, - )); + }); + } } -The :class:`Symfony\\Component\\OptionsResolver\\Options` class implements -:phpclass:`ArrayAccess`, :phpclass:`Iterator` and :phpclass:`Countable`. That -means you can handle it just like a normal array containing the options. - .. caution:: - The first argument of the closure must be typehinted as ``Options``, - otherwise it is considered as the value. - -Overwriting default Values -~~~~~~~~~~~~~~~~~~~~~~~~~~ + The argument of the callable must be type hinted as ``Options``. Otherwise, + the callable itself is considered as the default value of the option. -A previously set default value can be overwritten by invoking -:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setDefaults` -again. When using a closure as the new value it is passed 2 arguments: +.. note:: -* ``$options``: an :class:`Symfony\\Component\\OptionsResolver\\Options` - instance with all the other default options -* ``$previousValue``: the previous set default value + The closure is only executed if the ``port`` option isn't set by the user + or overwritten in a sub-class. -.. code-block:: php - - use Symfony\Component\OptionsResolver\Options; - use Symfony\Component\OptionsResolver\OptionsResolverInterface; +A previously set default value can be accessed by adding a second argument to +the closure:: // ... - protected function setDefaultOptions(OptionsResolverInterface $resolver) + class Mailer { // ... - $resolver->setDefaults(array( - 'encryption' => 'ssl', - 'host' => 'localhost', - )); + protected function configureOptions(OptionsResolver $resolver) + { + // ... + $resolver->setDefaults(array( + 'encryption' => null, + 'host' => 'example.org', + )); + } + } - // ... - $resolver->setDefaults(array( - 'encryption' => 'tls', // simple overwrite - 'host' => function (Options $options, $previousValue) { - return 'localhost' == $previousValue - ? '127.0.0.1' - : $previousValue; - }, - )); + class GoogleMailer extends Mailer + { + protected function configureOptions(OptionsResolver $resolver) + { + parent::configureOptions($resolver); + + $options->setDefault('host', function (Options $options, $previousValue) { + if ('ssl' === $options['encryption']) { + return 'secure.example.org' + } + + // Take default value configured in the base class + return $previousValue; + }); + } } -.. tip:: +As seen in the example, this feature is mostly useful if you want to reuse the +default values set in parent classes in sub-classes. - If the previous default value is calculated by an expensive closure and - you don't need access to it, you can use the - :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::replaceDefaults` - method instead. It acts like ``setDefaults`` but simply erases the - previous value to improve performance. This means that the previous - default value is not available when overwriting with another closure:: +Options without Default Values +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - use Symfony\Component\OptionsResolver\Options; - use Symfony\Component\OptionsResolver\OptionsResolverInterface; +In some cases, it is useful to define an option without setting a default value. +This is useful if you need to know whether or not the user *actually* set +an option or not. For example, if you set the default value for an option, +it's not possible to know whether the user passed this value or if it simply +comes from the default:: + // ... + class Mailer + { // ... - protected function setDefaultOptions(OptionsResolverInterface $resolver) + protected function configureOptions(OptionsResolver $resolver) { // ... - $resolver->setDefaults(array( - 'encryption' => 'ssl', - 'heavy' => function (Options $options) { - // Some heavy calculations to create the $result + $resolver->setDefault('port', 25); + } - return $result; - }, - )); + // ... + public function sendMail($from, $to) + { + // Is this the default value or did the caller of the class really + // set the port to 25? + if (25 === $this->options['port']) { + // ... + } + } + } - $resolver->replaceDefaults(array( - 'encryption' => 'tls', // simple overwrite - 'heavy' => function (Options $options) { - // $previousValue not available - // ... +.. versionadded:: 2.6 + The method :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setDefined` + was introduced in Symfony 2.6. Before, you had to use + :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setOptional`. - return $someOtherResult; - }, - )); +You can use :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setDefined` +to define an option without setting a default value. Then the option will only +be included in the resolved options if it was actually passed to +:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::resolve`:: + + // ... + class Mailer + { + // ... + + protected function configureOptions(OptionsResolver $resolver) + { + // ... + $resolver->setDefined('port'); } -.. note:: + // ... + public function sendMail($from, $to) + { + if (array_key_exists('port', $this->options)) { + echo 'Set!'; + } else { + echo 'Not Set!'; + } + } + } - Existing option keys that you do not mention when overwriting are preserved. + $mailer = new Mailer(); + $mailer->sendMail($from, $to); + // => Not Set! -Configure Allowed Values -~~~~~~~~~~~~~~~~~~~~~~~~ + $mailer = new Mailer(array( + 'port' => 25, + )); + $mailer->sendMail($from, $to); + // => Set! -Not all values are valid values for options. Suppose the ``Mailer`` class has -a ``transport`` option, it can only be one of ``sendmail``, ``mail`` or -``smtp``. You can configure these allowed values by calling -:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setAllowedValues`:: +You can also pass an array of option names if you want to define multiple +options in one go:: // ... - protected function setDefaultOptions(OptionsResolverInterface $resolver) + class Mailer { // ... - - $resolver->setAllowedValues(array( - 'encryption' => array(null, 'ssl', 'tls'), - )); + protected function configureOptions(OptionsResolver $resolver) + { + // ... + $resolver->setDefined(array('port', 'encryption')); + } } -There is also an -:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::addAllowedValues` -method, which you can use if you want to add an allowed value to the previously -configured allowed values. - -Configure Allowed Types -~~~~~~~~~~~~~~~~~~~~~~~ +.. versionadded:: 2.6 + The method :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::isDefined` + and :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::getDefinedOptions` + were introduced in Symfony 2.6. -You can also specify allowed types. For instance, the ``port`` option can -be anything, but it must be an integer. You can configure these types by calling -:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setAllowedTypes`:: +The methods :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::isDefined` +and :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::getDefinedOptions` +let you find out which options are defined:: // ... - protected function setDefaultOptions(OptionsResolverInterface $resolver) + class GoogleMailer extends Mailer { // ... - $resolver->setAllowedTypes(array( - 'port' => 'integer', - )); - } + protected function configureOptions(OptionsResolver $resolver) + { + parent::configureOptions($resolver); -Possible types are the ones associated with the ``is_*`` PHP functions or a -class name. You can also pass an array of types as the value. For instance, -``array('null', 'string')`` allows ``port`` to be ``null`` or a ``string``. + if ($resolver->isDefined('host')) { + // One of the following was called: -There is also an -:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::addAllowedTypes` -method, which you can use to add an allowed type to the previous allowed types. + // $resolver->setDefault('host', ...); + // $resolver->setRequired('host'); + // $resolver->setDefined('host'); + } -Normalize the Options -~~~~~~~~~~~~~~~~~~~~~ + $definedOptions = $resolver->getDefinedOptions(); + } + } + +Performance Tweaks +~~~~~~~~~~~~~~~~~~ -Some values need to be normalized before you can use them. For instance, -pretend that the ``host`` should always start with ``http://``. To do that, -you can write normalizers. These closures will be executed after all options -are passed and should return the normalized value. You can configure these -normalizers by calling -:method:`Symfony\\Components\\OptionsResolver\\OptionsResolver::setNormalizers`:: +With the current implementation, the ``configureOptions()`` method will be +called for every single instance of the ``Mailer`` class. Depending on the +amount of option configuration and the number of created instances, this may add +noticeable overhead to your application. If that overhead becomes a problem, you +can change your code to do the configuration only once per class:: // ... - protected function setDefaultOptions(OptionsResolverInterface $resolver) + class Mailer { - // ... + private static $resolversByClass = array(); - $resolver->setNormalizers(array( - 'host' => function (Options $options, $value) { - if ('http://' !== substr($value, 0, 7)) { - $value = 'http://'.$value; - } + protected $options; - return $value; - }, - )); + public function __construct(array $options = array()) + { + // What type of Mailer is this, a Mailer, a GoogleMailer, ... ? + $class = get_class($this); + + // Was configureOptions() executed before for this class? + if (!isset(self::$resolversByClass[$class])) { + self::$resolversByClass[$class] = new OptionsResolver(); + $this->configureOptions(self::$resolversByClass[$class]); + } + + $this->options = self::$resolversByClass[$class]->resolve($options); + } + + protected function configureOptions(OptionsResolver $resolver) + { + // ... + } } -You see that the closure also gets an ``$options`` parameter. Sometimes, you -need to use the other options for normalizing:: +Now the :class:`Symfony\\Component\\OptionsResolver\\OptionsResolver` instance +will be created once per class and reused from that on. Be aware that this may +lead to memory leaks in long-running applications, if the default options contain +references to objects or object graphs. If that's the case for you, implement a +method ``clearOptionsConfig()`` and call it periodically:: // ... - protected function setDefaultOptions(OptionsResolverInterface $resolver) + class Mailer { - // ... + private static $resolversByClass = array(); - $resolver->setNormalizers(array( - 'host' => function (Options $options, $value) { - if (!in_array(substr($value, 0, 7), array('http://', 'https://'))) { - if ($options['ssl']) { - $value = 'https://'.$value; - } else { - $value = 'http://'.$value; - } - } + public static function clearOptionsConfig() + { + self::$resolversByClass = array(); + } - return $value; - }, - )); + // ... } +That's it! You now have all the tools and knowledge needed to easily process +options in your code. + .. _Packagist: https://packagist.org/packages/symfony/options-resolver +.. _Form component: http://symfony.com/doc/current/components/form/introduction.html +.. _CHANGELOG: https://github.com/symfony/symfony/blob/master/src/Symfony/Component/OptionsResolver/CHANGELOG.md#260 +.. _`read the Symfony 2.5 documentation`: http://symfony.com/doc/2.5/components/options_resolver.html diff --git a/components/process.rst b/components/process.rst index 27a6221846e..c73c380173f 100644 --- a/components/process.rst +++ b/components/process.rst @@ -31,29 +31,43 @@ a command in a sub-process:: throw new \RuntimeException($process->getErrorOutput()); } - print $process->getOutput(); + echo $process->getOutput(); The component takes care of the subtle differences between the different platforms when executing the command. -.. versionadded:: 2.2 - The ``getIncrementalOutput()`` and ``getIncrementalErrorOutput()`` methods - were introduced in Symfony 2.2. - The ``getOutput()`` method always return the whole content of the standard output of the command and ``getErrorOutput()`` the content of the error output. Alternatively, the :method:`Symfony\\Component\\Process\\Process::getIncrementalOutput` and :method:`Symfony\\Component\\Process\\Process::getIncrementalErrorOutput` methods returns the new outputs since the last call. -.. versionadded:: 2.4 - The ``clearOutput()`` and ``clearErrorOutput()`` methods were introduced in Symfony 2.4. - The :method:`Symfony\\Component\\Process\\Process::clearOutput` method clears the contents of the output and :method:`Symfony\\Component\\Process\\Process::clearErrorOutput` clears the contents of the error output. +.. versionadded:: 2.5 + The ``mustRun()`` method was introduced in Symfony 2.5. + +The ``mustRun()`` method is identical to ``run()``, except that it will throw +a :class:`Symfony\\Component\\Process\\Exception\\ProcessFailedException` +if the process couldn't be executed successfully (i.e. the process exited +with a non-zero code):: + + use Symfony\Component\Process\Exception\ProcessFailedException; + use Symfony\Component\Process\Process; + + $process = new Process('ls -lsa'); + + try { + $process->mustRun(); + + echo $process->getOutput(); + } catch (ProcessFailedException $e) { + echo $e->getMessage(); + } + Getting real-time Process Output -------------------------------- @@ -122,7 +136,7 @@ Stopping a Process Any asynchronous process can be stopped at any time with the :method:`Symfony\\Component\\Process\\Process::stop` method. This method takes -two arguments : a timeout and a signal. Once the timeout is reached, the signal +two arguments: a timeout and a signal. Once the timeout is reached, the signal is sent to the running process. The default signal sent to a process is ``SIGKILL``. Please read the :ref:`signal documentation below` to find out more about signal handling in the Process component:: @@ -219,20 +233,16 @@ check regularly:: Process Idle Timeout -------------------- -.. versionadded:: 2.4 - The :method:`Symfony\\Component\\Process\\Process::setIdleTimeout` method - was introduced in Symfony 2.4. - In contrast to the timeout of the previous paragraph, the idle timeout only considers the time since the last output was produced by the process:: use Symfony\Component\Process\Process; - + $process = new Process('something-with-variable-runtime'); $process->setTimeout(3600); $process->setIdleTimeout(60); $process->run(); - + In the case above, a process is considered timed out, when either the total runtime exceeds 3600 seconds, or the process does not produce any output for 60 seconds. @@ -242,7 +252,7 @@ Process Signals .. versionadded:: 2.3 The ``signal`` method was introduced in Symfony 2.3. -When running a program asynchronously, you can send it posix signals with the +When running a program asynchronously, you can send it POSIX signals with the :method:`Symfony\\Component\\Process\\Process::signal` method:: use Symfony\Component\Process\Process; @@ -286,6 +296,34 @@ You can access the `pid`_ of a running process with the you may have to prefix your commands with `exec`_. Please read `Symfony Issue#5759`_ to understand why this is happening. +Disabling Output +---------------- + +.. versionadded:: 2.5 + The :method:`Symfony\\Component\\Process\\Process::disableOutput` and + :method:`Symfony\\Component\\Process\\Process::enableOutput` methods were + introduced in Symfony 2.5. + +As standard output and error output are always fetched from the underlying process, +it might be convenient to disable output in some cases to save memory. +Use :method:`Symfony\\Component\\Process\\Process::disableOutput` and +:method:`Symfony\\Component\\Process\\Process::enableOutput` to toggle this feature:: + + use Symfony\Component\Process\Process; + + $process = new Process('/usr/bin/php worker.php'); + $process->disableOutput(); + $process->run(); + +.. caution:: + + You can not enable or disable the output while the process is running. + + If you disable the output, you cannot access ``getOutput``, + ``getIncrementalOutput``, ``getErrorOutput`` or ``getIncrementalErrorOutput``. + Moreover, you could not pass a callback to the ``start``, ``run`` or ``mustRun`` + methods or use ``setIdleTimeout``. + .. _`Symfony Issue#5759`: https://github.com/symfony/symfony/issues/5759 .. _`PHP Bug#39992`: https://bugs.php.net/bug.php?id=39992 .. _`exec`: http://en.wikipedia.org/wiki/Exec_(operating_system) diff --git a/components/property_access/introduction.rst b/components/property_access/introduction.rst index a9eb4083fd9..d8d4c774610 100644 --- a/components/property_access/introduction.rst +++ b/components/property_access/introduction.rst @@ -8,10 +8,6 @@ The PropertyAccess Component The PropertyAccess component provides function to read and write from/to an object or array using a simple string notation. -.. versionadded:: 2.2 - The PropertyAccess component was introduced in Symfony 2.2. Previously, - the ``PropertyPath`` class was located in the Form component. - Installation ------------ @@ -179,6 +175,8 @@ The ``getValue`` method can also use the magic ``__get`` method:: echo $accessor->getValue($person, 'Wouter'); // array(...) +.. _components-property-access-magic-call: + Magic ``__call()`` Method ~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -209,7 +207,7 @@ enable this feature by using :class:`Symfony\\Component\\PropertyAccess\\Propert $person = new Person(); // Enable magic __call - $accessor = PropertyAccess::getPropertyAccessorBuilder() + $accessor = PropertyAccess::createPropertyAccessorBuilder() ->enableMagicCall() ->getPropertyAccessor(); @@ -305,7 +303,7 @@ see `Enable other Features`_. $person = new Person(); // Enable magic __call - $accessor = PropertyAccess::getPropertyAccessorBuilder() + $accessor = PropertyAccess::createPropertyAccessorBuilder() ->enableMagicCall() ->getPropertyAccessor(); @@ -313,6 +311,39 @@ see `Enable other Features`_. echo $person->getWouter(); // array(...) +Checking Property Paths +----------------------- + +.. versionadded:: 2.5 + The + :method:`PropertyAccessor::isReadable ` + and + :method:`PropertyAccessor::isWritable ` + methods were introduced in Symfony 2.5. + +When you want to check whether +:method:`PropertyAccessor::getValue` +can safely be called without actually calling that method, you can use +:method:`PropertyAccessor::isReadable` +instead:: + + $person = new Person(); + + if ($accessor->isReadable($person, 'firstName')) { + // ... + } + +The same is possible for :method:`PropertyAccessor::setValue`: +Call the +:method:`PropertyAccessor::isWritable` +method to find out whether a property path can be updated:: + + $person = new Person(); + + if ($accessor->isWritable($person, 'firstName')) { + // ... + } + Mixing Objects and Arrays ------------------------- @@ -363,7 +394,7 @@ configured to enable extra features. To do that you could use the $accessorBuilder->disableMagicCall(); // Check if magic __call handling is enabled - $accessorBuilder->isMagicCallEnabled() // true or false + $accessorBuilder->isMagicCallEnabled(); // true or false // At the end get the configured property accessor $accessor = $accessorBuilder->getPropertyAccessor(); @@ -376,7 +407,7 @@ configured to enable extra features. To do that you could use the Or you can pass parameters directly to the constructor (not the recommended way):: // ... - $accessor = new PropertyAccessor(true) // this enables handling of magic __call + $accessor = new PropertyAccessor(true); // this enables handling of magic __call .. _Packagist: https://packagist.org/packages/symfony/property-access diff --git a/components/routing/hostname_pattern.rst b/components/routing/hostname_pattern.rst index fd2e8671504..804ece250ba 100644 --- a/components/routing/hostname_pattern.rst +++ b/components/routing/hostname_pattern.rst @@ -4,9 +4,6 @@ How to Match a Route Based on the Host ====================================== -.. versionadded:: 2.2 - Host matching support was introduced in Symfony 2.2 - You can also match on the HTTP *host* of the incoming request. .. configuration-block:: diff --git a/components/routing/introduction.rst b/components/routing/introduction.rst index e751b0a36d2..3eb64ce7b29 100644 --- a/components/routing/introduction.rst +++ b/components/routing/introduction.rst @@ -72,30 +72,27 @@ Defining Routes A full route definition can contain up to seven parts: -1. The URL path route. This is matched against the URL passed to the `RequestContext`, +#. The URL path route. This is matched against the URL passed to the `RequestContext`, and can contain named wildcard placeholders (e.g. ``{placeholders}``) to match dynamic parts in the URL. -2. An array of default values. This contains an array of arbitrary values +#. An array of default values. This contains an array of arbitrary values that will be returned when the request matches the route. -3. An array of requirements. These define constraints for the values of the +#. An array of requirements. These define constraints for the values of the placeholders as regular expressions. -4. An array of options. These contain internal settings for the route and +#. An array of options. These contain internal settings for the route and are the least commonly needed. -5. A host. This is matched against the host of the request. See +#. A host. This is matched against the host of the request. See :doc:`/components/routing/hostname_pattern` for more details. -6. An array of schemes. These enforce a certain HTTP scheme (``http``, ``https``). +#. An array of schemes. These enforce a certain HTTP scheme (``http``, ``https``). -7. An array of methods. These enforce a certain HTTP request method (``HEAD``, +#. An array of methods. These enforce a certain HTTP request method (``HEAD``, ``GET``, ``POST``, ...). -.. versionadded:: 2.2 - Host matching support was introduced in Symfony 2.2 - Take the following route, which combines several of these ideas:: $route = new Route( @@ -161,7 +158,6 @@ host to all routes of a subtree using methods provided by the $rootCollection->addCollection($subCollection); - Set the Request Parameters ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -288,7 +284,7 @@ calls a closure and uses the result as a :class:`Symfony\\Component\\Routing\\Ro use Symfony\Component\Routing\Loader\ClosureLoader; - $closure = function() { + $closure = function () { return new RouteCollection(); }; @@ -307,7 +303,7 @@ out here. The all-in-one Router ~~~~~~~~~~~~~~~~~~~~~ -The :class:`Symfony\\Component\\Routing\\Router` class is a all-in-one package +The :class:`Symfony\\Component\\Routing\\Router` class is an all-in-one package to quickly use the Routing component. The constructor expects a loader instance, a path to the main route definition and some other settings:: diff --git a/components/security/authentication.rst b/components/security/authentication.rst index 01841b5bb4a..4708d569c69 100644 --- a/components/security/authentication.rst +++ b/components/security/authentication.rst @@ -4,16 +4,22 @@ Authentication ============== +.. versionadded:: 2.6 + The ``TokenStorageInterface`` was introduced in Symfony 2.6. Prior, you + had to use the ``getToken()`` method of the + :class:`Symfony\\Component\\Security\\Core\\SecurityContextInterface`. + When a request points to a secured area, and one of the listeners from the firewall map is able to extract the user's credentials from the current :class:`Symfony\\Component\\HttpFoundation\\Request` object, it should create a token, containing these credentials. The next thing the listener should do is ask the authentication manager to validate the given token, and return an *authenticated* token if the supplied credentials were found to be valid. -The listener should then store the authenticated token in the security context:: +The listener should then store the authenticated token using +:class:`the token storage `:: use Symfony\Component\Security\Http\Firewall\ListenerInterface; - use Symfony\Component\Security\Core\SecurityContextInterface; + use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; @@ -21,9 +27,9 @@ The listener should then store the authenticated token in the security context:: class SomeAuthenticationListener implements ListenerInterface { /** - * @var SecurityContextInterface + * @var TokenStorageInterface */ - private $securityContext; + private $tokenStorage; /** * @var AuthenticationManagerInterface @@ -54,7 +60,7 @@ The listener should then store the authenticated token in the security context:: ->authenticationManager ->authenticate($unauthenticatedToken); - $this->securityContext->setToken($authenticatedToken); + $this->tokenStorage->setToken($authenticatedToken); } } @@ -257,7 +263,7 @@ in) is correct, you can use:: // fetch the Acme\Entity\LegacyUser $user = ...; - + // the submitted password, e.g. from the login form $plainPassword = ...; diff --git a/components/security/authorization.rst b/components/security/authorization.rst index c5b357e5118..17d51b6e998 100644 --- a/components/security/authorization.rst +++ b/components/security/authorization.rst @@ -7,8 +7,8 @@ Authorization When any of the authentication providers (see :ref:`authentication_providers`) has verified the still-unauthenticated token, an authenticated token will be returned. The authentication listener should set this token directly -in the :class:`Symfony\\Component\\Security\\Core\\SecurityContextInterface` -using its :method:`Symfony\\Component\\Security\\Core\\SecurityContextInterface::setToken` +in the :class:`Symfony\\Component\\Security\\Core\\Authentication\\Token\\Storage\\TokenStorageInterface` +using its :method:`Symfony\\Component\\Security\\Core\\Authentication\\Token\\Storage\\TokenStorageInterface::setToken` method. From then on, the user is authenticated, i.e. identified. Now, other parts @@ -29,6 +29,11 @@ An authorization decision will always be based on a few things: Any object for which access control needs to be checked, like an article or a comment object. +.. versionadded:: 2.6 + The ``TokenStorageInterface`` was introduced in Symfony 2.6. Prior, you + had to use the ``setToken()`` method of the + :class:`Symfony\\Component\\Security\\Core\\SecurityContextInterface`. + .. _components-security-access-decision-manager: Access Decision Manager @@ -40,13 +45,13 @@ itself depends on multiple voters, and makes a final verdict based on all the votes (either positive, negative or neutral) it has received. It recognizes several strategies: -* ``affirmative`` (default) +``affirmative`` (default) grant access as soon as any voter returns an affirmative response; -* ``consensus`` +``consensus`` grant access if there are more voters granting access than there are denying; -* ``unanimous`` +``unanimous`` only grant access if none of the voters has denied access; .. code-block:: php @@ -85,14 +90,14 @@ of :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterf which means they have to implement a few methods which allows the decision manager to use them: -* ``supportsAttribute($attribute)`` +``supportsAttribute($attribute)`` will be used to check if the voter knows how to handle the given attribute; -* ``supportsClass($class)`` +``supportsClass($class)`` will be used to check if the voter is able to grant or deny access for an object of the given class; -* ``vote(TokenInterface $token, $object, array $attributes)`` +``vote(TokenInterface $token, $object, array $attributes)`` this method will do the actual voting and return a value equal to one of the class constants of :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface`, i.e. ``VoterInterface::ACCESS_GRANTED``, ``VoterInterface::ACCESS_DENIED`` @@ -227,23 +232,24 @@ are required for the current user to get access to the application:: $authenticationManager ); -Security Context -~~~~~~~~~~~~~~~~ +Authorization Checker +~~~~~~~~~~~~~~~~~~~~~ The access decision manager is also available to other parts of the application -via the :method:`Symfony\\Component\\Security\\Core\\SecurityContext::isGranted` -method of the :class:`Symfony\\Component\\Security\\Core\\SecurityContext`. +via the :method:`Symfony\\Component\\Security\\Core\\Authorization\\AuthorizationChecker::isGranted` +method of the :class:`Symfony\\Component\\Security\\Core\\Authorization\\AuthorizationChecker`. A call to this method will directly delegate the question to the access decision manager:: - use Symfony\Component\Security\SecurityContext; + use Symfony\Component\Security\Core\Authorization\AuthorizationChecker; use Symfony\Component\Security\Core\Exception\AccessDeniedException; - $securityContext = new SecurityContext( + $authorizationChecker = new AuthorizationChecker( + $tokenStorage, $authenticationManager, $accessDecisionManager ); - if (!$securityContext->isGranted('ROLE_ADMIN')) { + if (!$authorizationChecker->isGranted('ROLE_ADMIN')) { throw new AccessDeniedException(); } diff --git a/components/security/firewall.rst b/components/security/firewall.rst index 8d30debff6e..64603efb319 100644 --- a/components/security/firewall.rst +++ b/components/security/firewall.rst @@ -1,35 +1,44 @@ .. index:: single: Security, Firewall -The Firewall and Security Context -================================= +The Firewall and Authorization +============================== -Central to the Security component is the security context, which is an instance -of :class:`Symfony\\Component\\Security\\Core\\SecurityContextInterface`. When all -steps in the process of authenticating the user have been taken successfully, -you can ask the security context if the authenticated user has access to a +Central to the Security component is authorization. This is handled by an instance +of :class:`Symfony\\Component\\Security\\Core\\Authorization\\AuthorizationCheckerInterface`. +When all steps in the process of authenticating the user have been taken successfully, +you can ask the authorization checker if the authenticated user has access to a certain action or resource of the application:: - use Symfony\Component\Security\Core\SecurityContext; + use Symfony\Component\Security\Core\Authorization\AuthorizationChecker; use Symfony\Component\Security\Core\Exception\AccessDeniedException; + // instance of Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface + $tokenStorage = ...; + // instance of Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface $authenticationManager = ...; // instance of Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface $accessDecisionManager = ...; - $securityContext = new SecurityContext( + $authorizationChecker = new AuthorizationChecker( + $tokenStorage, $authenticationManager, $accessDecisionManager ); // ... authenticate the user - if (!$securityContext->isGranted('ROLE_ADMIN')) { + if (!$authorizationChecker->isGranted('ROLE_ADMIN')) { throw new AccessDeniedException(); } +.. versionadded:: 2.6 + As of Symfony 2.6, the :class:`Symfony\\Component\\Security\\Core\\SecurityContext` class was split + in the :class:`Symfony\\Component\\Security\\Core\\Authorization\\AuthorizationChecker` and + :class:`Symfony\\Component\\Security\\Core\\Authentication\\Token\\Storage\\TokenStorage` classes. + .. note:: Read the dedicated sections to learn more about :doc:`/components/security/authentication` @@ -115,7 +124,7 @@ which will eventually result in an "HTTP/1.1 403: Access Denied" response. Entry Points ~~~~~~~~~~~~ -When the user is not authenticated at all (i.e. when the security context +When the user is not authenticated at all (i.e. when the token storage has no token yet), the firewall's entry point will be called to "start" the authentication process. An entry point should implement :class:`Symfony\\Component\\Security\\Http\\EntryPoint\\AuthenticationEntryPointInterface`, diff --git a/components/security/index.rst b/components/security/index.rst index 94e3e6c77d6..e9fa2c24b14 100644 --- a/components/security/index.rst +++ b/components/security/index.rst @@ -8,3 +8,4 @@ Security firewall authentication authorization + secure_tools \ No newline at end of file diff --git a/components/security/secure_tools.rst b/components/security/secure_tools.rst new file mode 100644 index 00000000000..2ee5a98b920 --- /dev/null +++ b/components/security/secure_tools.rst @@ -0,0 +1,60 @@ +Securely Comparing Strings and Generating Random Numbers +======================================================== + +The Symfony Security component comes with a collection of nice utilities +related to security. These utilities are used by Symfony, but you should +also use them if you want to solve the problem they address. + +Comparing Strings +~~~~~~~~~~~~~~~~~ + +The time it takes to compare two strings depends on their differences. This +can be used by an attacker when the two strings represent a password for +instance; it is known as a `Timing attack`_. + +Internally, when comparing two passwords, Symfony uses a constant-time +algorithm; you can use the same strategy in your own code thanks to the +:class:`Symfony\\Component\\Security\\Core\\Util\\StringUtils` class:: + + use Symfony\Component\Security\Core\Util\StringUtils; + + // is some known string (e.g. password) equal to some user input? + $bool = StringUtils::equals($knownString, $userInput); + +.. caution:: + + To avoid timing attacks, the known string must be the first argument + and the user-entered string the second. + +Generating a Secure random Number +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Whenever you need to generate a secure random number, you are highly +encouraged to use the Symfony +:class:`Symfony\\Component\\Security\\Core\\Util\\SecureRandom` class:: + + use Symfony\Component\Security\Core\Util\SecureRandom; + + $generator = new SecureRandom(); + $random = $generator->nextBytes(10); + +The +:method:`Symfony\\Component\\Security\\Core\\Util\\SecureRandom::nextBytes` +method returns a random string composed of the number of characters passed as +an argument (10 in the above example). + +The SecureRandom class works better when OpenSSL is installed. But when it's +not available, it falls back to an internal algorithm, which needs a seed file +to work correctly. Just pass a file name to enable it:: + + use Symfony\Component\Security\Core\Util\SecureRandom; + + $generator = new SecureRandom('/some/path/to/store/the/seed.txt'); + $random = $generator->nextBytes(10); + +.. note:: + + If you're using the Symfony Framework, you can access a secure random + instance directly from the container: its name is ``security.secure_random``. + +.. _`Timing attack`: http://en.wikipedia.org/wiki/Timing_attack diff --git a/components/serializer.rst b/components/serializer.rst index aeaabeb6067..6480fcfd048 100644 --- a/components/serializer.rst +++ b/components/serializer.rst @@ -62,6 +62,7 @@ exists in your project:: { private $age; private $name; + private $sportsman; // Getters public function getName() @@ -74,6 +75,12 @@ exists in your project:: return $this->age; } + // Issers + public function isSportsman() + { + return $this->sportsman; + } + // Setters public function setName($name) { @@ -84,6 +91,11 @@ exists in your project:: { $this->age = $age; } + + public function setSportsman($sportsman) + { + $this->sportsman = $sportsman; + } } Now, if you want to serialize this object into JSON, you only need to @@ -92,10 +104,11 @@ use the Serializer service created before:: $person = new Acme\Person(); $person->setName('foo'); $person->setAge(99); + $person->setSportsman(false); $jsonContent = $serializer->serialize($person, 'json'); - // $jsonContent contains {"name":"foo","age":99} + // $jsonContent contains {"name":"foo","age":99,"sportsman":false} echo $jsonContent; // or return it in a Response @@ -124,7 +137,7 @@ method on the normalizer definition:: $encoder = new JsonEncoder(); $serializer = new Serializer(array($normalizer), array($encoder)); - $serializer->serialize($person, 'json'); // Output: {"name":"foo"} + $serializer->serialize($person, 'json'); // Output: {"name":"foo","sportsman":false} Deserializing an Object ----------------------- @@ -136,17 +149,18 @@ of the ``Person`` class would be encoded in XML format:: foo 99 + false EOF; - $person = $serializer->deserialize($data,'Acme\Person','xml'); + $person = $serializer->deserialize($data, 'Acme\Person', 'xml'); In this case, :method:`Symfony\\Component\\Serializer\\Serializer::deserialize` needs three parameters: -1. The information to be decoded -2. The name of the class this information will be decoded to -3. The encoder used to convert that information into an array +#. The information to be decoded +#. The name of the class this information will be decoded to +#. The encoder used to convert that information into an array Using Camelized Method Names for Underscored Attributes ------------------------------------------------------- @@ -181,6 +195,18 @@ method on the normalizer definition:: As a final result, the deserializer uses the ``first_name`` attribute as if it were ``firstName`` and uses the ``getFirstName`` and ``setFirstName`` methods. +Serializing Boolean Attributes +------------------------------ + +.. versionadded:: 2.5 + Support for ``is*`` accessors in + :class:`Symfony\\Component\\Serializer\\Normalizer\\GetSetMethodNormalizer` + was introduced in Symfony 2.5. + +If you are using isser methods (methods prefixed by ``is``, like +``Acme\Person::isSportsman()``), the Serializer component will automatically +detect and use it to serialize related attributes. + Using Callbacks to Serialize Properties with Object Instances ------------------------------------------------------------- @@ -198,7 +224,7 @@ When serializing, you can set a callback to format a specific object property:: return $dateTime instanceof \DateTime ? $dateTime->format(\DateTime::ISO8601) : ''; - } + }; $normalizer->setCallbacks(array('createdAt' => $callback)); @@ -212,6 +238,101 @@ When serializing, you can set a callback to format a specific object property:: $serializer->serialize($person, 'json'); // Output: {"name":"cordoval", "age": 34, "createdAt": "2014-03-22T09:43:12-0500"} +Handling Circular References +---------------------------- + +.. versionadded:: 2.6 + Handling of circular references was introduced in Symfony 2.6. In previous + versions of Symfony, circular references led to infinite loops. + +Circular references are common when dealing with entity relations:: + + class Organization + { + private $name; + private $members; + + public function setName($name) + { + $this->name = $name; + } + + public function getName() + { + return $this->name; + } + + public function setMembers(array $members) + { + $this->members = $members; + } + + public function getMembers() + { + return $this->members; + } + } + + class Member + { + private $name; + private $organization; + + public function setName($name) + { + $this->name = $name; + } + + public function getName() + { + return $this->name; + } + + public function setOrganization(Organization $organization) + { + $this->organization = $organization; + } + + public function getOrganization() + { + return $this->organization; + } + } + +To avoid infinite loops, :class:`Symfony\\Component\\Serializer\\Normalizer\\GetSetMethodNormalizer` +throws a :class:`Symfony\\Component\\Serializer\\Exception\\CircularReferenceException` +when such a case is encountered:: + + $member = new Member(); + $member->setName('Kévin'); + + $org = new Organization(); + $org->setName('Les-Tilleuls.coop'); + $org->setMembers(array($member)); + + $member->setOrganization($org); + + echo $serializer->serialize($org, 'json'); // Throws a CircularReferenceException + +The ``setCircularReferenceLimit()`` method of this normalizer sets the number +of times it will serialize the same object before considering it a circular +reference. Its default value is ``1``. + +Instead of throwing an exception, circular references can also be handled +by custom callables. This is especially useful when serializing entities +having unique identifiers:: + + $encoder = new JsonEncoder(); + $normalizer = new GetSetMethodNormalizer(); + + $normalizer->setCircularReferenceHandler(function ($object) { + return $object->getName(); + }); + + $serializer = new Serializer(array($normalizer), array($encoder)); + echo $serializer->serialize($org, 'json'); + // {"name":"Les-Tilleuls.coop","members":[{"name":"K\u00e9vin", organization: "Les-Tilleuls.coop"}]} + JMSSerializer ------------- @@ -219,7 +340,7 @@ A popular third-party library, `JMS serializer`_, provides a more sophisticated albeit more complex solution. This library includes the ability to configure how your objects should be serialized/deserialized via annotations (as well as YAML, XML and PHP), integration with the Doctrine ORM, -and handling of other complex cases (e.g. circular references). +and handling of other complex cases. .. _`JMS serializer`: https://github.com/schmittjoh/serializer .. _Packagist: https://packagist.org/packages/symfony/serializer diff --git a/components/stopwatch.rst b/components/stopwatch.rst index e9506ce4c8f..58c76b85cff 100644 --- a/components/stopwatch.rst +++ b/components/stopwatch.rst @@ -7,10 +7,6 @@ The Stopwatch Component The Stopwatch component provides a way to profile code. -.. versionadded:: 2.2 - The Stopwatch component was introduced in Symfony 2.2. Previously, the - ``Stopwatch`` class was located in the HttpKernel component. - Installation ------------ @@ -35,10 +31,16 @@ microtime by yourself. Instead, use the simple // ... some code goes here $event = $stopwatch->stop('eventName'); +.. versionadded:: 2.5 + The ``getEvent()`` method was introduced in Symfony 2.5 + The :class:`Symfony\\Component\\Stopwatch\\StopwatchEvent` object can be retrieved -from the :method:`Symfony\\Component\\Stopwatch\\Stopwatch::start`, -:method:`Symfony\\Component\\Stopwatch\\Stopwatch::stop` and -:method:`Symfony\\Component\\Stopwatch\\Stopwatch::lap` methods. +from the :method:`Symfony\\Component\\Stopwatch\\Stopwatch::start`, +:method:`Symfony\\Component\\Stopwatch\\Stopwatch::stop`, +:method:`Symfony\\Component\\Stopwatch\\Stopwatch::lap` and +:method:`Symfony\\Component\\Stopwatch\\Stopwatch::getEvent` methods. +The latter should be used when you need to retrieve the duration of an event +while it is still running. You can also provide a category name to an event:: diff --git a/components/templating/helpers/assetshelper.rst b/components/templating/helpers/assetshelper.rst index ed4e681c19a..583dfc4d74c 100644 --- a/components/templating/helpers/assetshelper.rst +++ b/components/templating/helpers/assetshelper.rst @@ -47,6 +47,23 @@ You can also specify a URL to use in the second parameter of the constructor:: Now URLs are rendered like ``http://cdn.example.com/images/logo.png``. +.. versionadded:: 2.5 + Absolute URLs for assets were introduced in Symfony 2.5. + +You can also use the third argument of the helper to force an absolute URL: + +.. code-block:: html+php + + + + +.. note:: + + If you already set a URL in the constructor, using the third argument of + ``getUrl`` will not affect the generated URL. + Versioning ---------- @@ -63,6 +80,19 @@ is used in :phpfunction:`sprintf`. The first argument is the path and the second is the version. For instance, ``%s?v=%s`` will be rendered as ``/images/logo.png?v=328rad75``. +.. versionadded:: 2.5 + On-demand versioned URLs for assets were introduced in Symfony 2.5. + +You can also generate a versioned URL on an asset-by-asset basis using the +fourth argument of the helper: + +.. code-block:: html+php + + + + Multiple Packages ----------------- @@ -75,7 +105,7 @@ Asset path generation is handled internally by packages. The component provides You can also use multiple packages:: use Symfony\Component\Templating\Asset\PathPackage; - + // ... $templateEngine->set(new AssetsHelper()); @@ -104,4 +134,4 @@ Custom Packages --------------- You can create your own package by extending -:class:`Symfony\\Component\\Templating\\Package\\Package`. +:class:`Symfony\\Component\\Templating\\Asset\\Package`. diff --git a/components/templating/helpers/slotshelper.rst b/components/templating/helpers/slotshelper.rst index c264b2244e2..e368b83f86a 100644 --- a/components/templating/helpers/slotshelper.rst +++ b/components/templating/helpers/slotshelper.rst @@ -55,7 +55,7 @@ Extending Templates The :method:`Symfony\\Component\\Templating\\PhpEngine::extend` method is called in the sub-template to set its parent template. Then -:method:`$view['slots']->set() ` +:method:`$view['slots']->set() ` can be used to set the content of a slot. All content which is not explicitly set in a slot is in the ``_content`` slot. diff --git a/components/templating/introduction.rst b/components/templating/introduction.rst index 0f4ec567a5b..247ea748c35 100644 --- a/components/templating/introduction.rst +++ b/components/templating/introduction.rst @@ -135,7 +135,8 @@ escaper using the Helpers ------- -The Templating component can be easily extended via helpers. The component has +The Templating component can be easily extended via helpers. Helpers are PHP objects that +provide features useful in a template context. The component has 2 built-in helpers: * :doc:`/components/templating/helpers/assetshelper` @@ -195,7 +196,7 @@ method is used. $templating = new DelegatingEngine(array( new PhpEngine(...), - new CustomEngine(...) + new CustomEngine(...), )); .. _Packagist: https://packagist.org/packages/symfony/templating diff --git a/components/translation/introduction.rst b/components/translation/introduction.rst index 8a74f37e120..c8130e6f7ec 100644 --- a/components/translation/introduction.rst +++ b/components/translation/introduction.rst @@ -62,9 +62,6 @@ The Translation component uses Loader classes to load catalogs. You can load multiple resources for the same locale, which will then be combined into one catalog. -.. versionadded:: 2.4 - The ``JsonFileLoader`` was introduced in Symfony 2.4. - The component comes with some default Loaders and you can create your own Loader too. The default loaders are: @@ -160,12 +157,12 @@ If the message is not located in the catalog of the specific locale, the translator will look into the catalog of one or more fallback locales. For example, assume you're trying to translate into the ``fr_FR`` locale: -1. First, the translator looks for the translation in the ``fr_FR`` locale; +#. First, the translator looks for the translation in the ``fr_FR`` locale; -2. If it wasn't found, the translator looks for the translation in the ``fr`` +#. If it wasn't found, the translator looks for the translation in the ``fr`` locale; -3. If the translation still isn't found, the translator uses the one or more +#. If the translation still isn't found, the translator uses the one or more fallback locales set explicitly on the translator. For (3), the fallback locales can be set by calling diff --git a/components/translation/usage.rst b/components/translation/usage.rst index 68a85e44681..507d4f1c3c2 100644 --- a/components/translation/usage.rst +++ b/components/translation/usage.rst @@ -226,7 +226,7 @@ Pluralization ------------- Message pluralization is a tough topic as the rules can be quite complex. For -instance, here is the mathematic representation of the Russian pluralization +instance, here is the mathematical representation of the Russian pluralization rules:: (($number % 10 == 1) && ($number % 100 != 11)) @@ -371,3 +371,24 @@ use for translation:: .. _`L10n`: http://en.wikipedia.org/wiki/Internationalization_and_localization .. _`ISO 31-11`: http://en.wikipedia.org/wiki/Interval_(mathematics)#Notations_for_intervals + +Retrieving the Message Catalogue +-------------------------------- + +In case you want to use the same translation catalogue outside your application +(e.g. use translation on the client side), it's possible to fetch raw translation +messages. Just specify the required locale:: + + $messages = $translator->getMessages('fr_FR'); + +The ``$messages`` variable will have the following structure:: + + array( + 'messages' => array( + 'Hello world' => 'Bonjour tout le monde', + ), + 'validators' => array( + 'Value should not be empty' => 'Valeur ne doit pas être vide', + 'Value is too long' => 'Valeur est trop long', + ), + ); diff --git a/components/using_components.rst b/components/using_components.rst index 6c9c13c003c..4103b956afc 100644 --- a/components/using_components.rst +++ b/components/using_components.rst @@ -20,40 +20,39 @@ Using the Finder Component **1.** If you're creating a new project, create a new empty directory for it. -**2.** Create a new file called ``composer.json`` and paste the following into it: +**2.** Open a terminal and use Composer to grab the library. -.. code-block:: json +.. code-block:: bash - { - "require": { - "symfony/finder": "2.3.*" - } - } + $ composer require symfony/finder -If you already have a ``composer.json`` file, just add this line to it. You -may also need to adjust the version (e.g. ``2.2.2`` or ``2.3.*``). +The name ``symfony/finder`` is written at the top of the documentation for +whatever component you want. -You can research the component names and versions at `packagist.org`_. +.. tip:: -**3.** `Install composer`_ if you don't already have it present on your system: + `Install composer`_ if you don't have it already present on your system. + Depending on how you install, you may end up with a ``composer.phar`` + file in your directory. In that case, no worries! Just run + ``php composer.phar require symfony/finder``. -**4.** Download the vendor libraries and generate the ``vendor/autoload.php`` file: +If you know you need a specific version of the library, add that to the command: .. code-block:: bash - $ php composer.phar install + $ composer require symfony/finder -**5.** Write your code: +**3.** Write your code! Once Composer has downloaded the component(s), all you need to do is include the ``vendor/autoload.php`` file that was generated by Composer. This file takes care of autoloading all of the libraries so that you can use them immediately:: - // File: src/script.php + // File example: src/script.php // update this to the path to the "vendor/" directory, relative to this file - require_once '../vendor/autoload.php'; + require_once __DIR__.'/../vendor/autoload.php'; use Symfony\Component\Finder\Finder; @@ -62,33 +61,18 @@ immediately:: // ... -.. tip:: - - If you want to use all of the Symfony Components, then instead of adding - them one by one: +Using all of the Components +--------------------------- - .. code-block:: json +If you want to use all of the Symfony Components, then instead of adding +them one by one, you can include the ``symfony/symfony`` package: - { - "require": { - "symfony/finder": "2.3.*", - "symfony/dom-crawler": "2.3.*", - "symfony/css-selector": "2.3.*" - } - } - - you can use: - - .. code-block:: json +.. code-block:: bash - { - "require": { - "symfony/symfony": "2.3.*" - } - } + $ composer require symfony/symfony - This will include the Bundle and Bridge libraries, which you may not - actually need. +This will also include the Bundle and Bridge libraries, which you may or +may not actually need. Now what? --------- @@ -100,4 +84,3 @@ And have fun! .. _Composer: http://getcomposer.org .. _Install composer: http://getcomposer.org/download/ -.. _packagist.org: https://packagist.org/ diff --git a/components/var_dumper/advanced.rst b/components/var_dumper/advanced.rst new file mode 100644 index 00000000000..99998826ab2 --- /dev/null +++ b/components/var_dumper/advanced.rst @@ -0,0 +1,238 @@ +.. index:: + single: VarDumper + single: Components; VarDumper + +Advanced Usage of the VarDumper Component +========================================= + +The ``dump()`` function is just a thin wrapper and a more convenient way to call +:method:`VarDumper::dump() `. +You can change the behavior of this function by calling +:method:`VarDumper::setHandler($callable) `. +Calls to ``dump()`` will then be forwarded to ``$callable``. + +By adding a handler, you can customize the `Cloners`_, `Dumpers`_ and `Casters`_ +as explained below. A simple implementation of a handler function might look +like this:: + + use Symfony\Component\VarDumper\VarDumper; + use Symfony\Component\VarDumper\Cloner\VarCloner; + use Symfony\Component\VarDumper\Dumper\CliDumper; + use Symfony\Component\VarDumper\Dumper\HtmlDumper; + + VarDumper::setHandler(function ($var) { + $cloner = new VarCloner(); + $dumper = 'cli' === PHP_SAPI ? new CliDumper() : new HtmlDumper(); + + $dumper->dump($cloner->cloneVar($var)); + }); + +Cloners +------- + +A cloner is used to create an intermediate representation of any PHP variable. +Its output is a :class:`Symfony\\Component\\VarDumper\\Cloner\\Data` +object that wraps this representation. + +You can create a :class:`Symfony\\Component\\VarDumper\\Cloner\\Data` +object this way:: + + use Symfony\Component\VarDumper\Cloner\VarCloner; + + $cloner = new VarCloner(); + $data = $cloner->cloneVar($myVar); + // this is commonly then passed to the dumper + // see the example at the top of this page + // $dumper->dump($data); + +A cloner also applies limits when creating the representation, so that the +corresponding Data object could represent only a subset of the cloned variable. +Before calling :method:`Symfony\\Component\\VarDumper\\Cloner\\VarCloner::cloneVar`, +you can configure these limits: + +* :method:`Symfony\\Component\\VarDumper\\Cloner\\VarCloner::setMaxItems` + configures the maximum number of items that will be cloned + *past the first nesting level*. Items are counted using a breadth-first + algorithm so that lower level items have higher priority than deeply nested + items; +* :method:`Symfony\\Component\\VarDumper\\Cloner\\VarCloner::setMaxString` + configures the maximum number of characters that will be cloned before + cutting overlong strings; +* in both cases, specifying `-1` removes any limit. + +Before dumping it, you can further limit the resulting +:class:`Symfony\\Component\\VarDumper\\Cloner\\Data` object by calling its +:method:`Symfony\\Component\\VarDumper\\Cloner\\Data::getLimitedClone` +method: + +* the first ``$maxDepth`` argument allows limiting dumps in the depth dimension, +* the second ``$maxItemsPerDepth`` limits the number of items per depth level, +* and the last ``$useRefHandles`` defaults to ``true``, but allows removing + internal objects' handles for sparser output, +* but unlike the previous limits on cloners that remove data on purpose, + these can be changed back and forth before dumping since they do not affect + the intermediate representation internally. + +.. note:: + + When no limit is applied, a :class:`Symfony\\Component\\VarDumper\\Cloner\\Data` + object is as accurate as the native :phpfunction:`serialize` function, + and thus could be for purposes beyond dumping for debugging. + +Dumpers +------- + +A dumper is responsible for outputting a string representation of a PHP variable, +using a :class:`Symfony\\Component\\VarDumper\\Cloner\\Data` object as input. +The destination and the formatting of this output vary with dumpers. + +This component comes with an :class:`Symfony\\Component\\VarDumper\\Dumper\\HtmlDumper` +for HTML output and a :class:`Symfony\\Component\\VarDumper\\Dumper\\CliDumper` +for optionally colored command line output. + +For example, if you want to dump some ``$variable``, just do:: + + use Symfony\Component\VarDumper\Cloner\VarCloner; + use Symfony\Component\VarDumper\Dumper\CliDumper; + + $cloner = new VarCloner(); + $dumper = new CliDumper(); + + $dumper->dump($cloner->cloneVar($variable)); + +By using the first argument of the constructor, you can select the output +stream where the dump will be written. By default, the ``CliDumper`` writes +on ``php://stdout`` and the ``HtmlDumper`` on ``php://output``. But any PHP +stream (resource or URL) is acceptable. + +Instead of a stream destination, you can also pass it a ``callable`` that +will be called repeatedly for each line generated by a dumper. This +callable can be configured using the first argument of a dumper's constructor, +but also using the +:method:`Symfony\\Component\\VarDumper\\Dumper\\AbstractDumper::setOutput` +method or the second argument of the +:method:`Symfony\\Component\\VarDumper\\Dumper\\AbstractDumper::dump` method. + +For example, to get a dump as a string in a variable, you can do:: + + use Symfony\Component\VarDumper\Cloner\VarCloner; + use Symfony\Component\VarDumper\Dumper\CliDumper; + + $cloner = new VarCloner(); + $dumper = new CliDumper(); + $output = ''; + + $dumper->dump( + $cloner->cloneVar($variable), + function ($line, $depth) use (&$output) { + // A negative depth means "end of dump" + if ($depth >= 0) { + // Adds a two spaces indentation to the line + $output .= str_repeat(' ', $depth).$line."\n"; + } + } + ); + + // $output is now populated with the dump representation of $variable + +Another option for doing the same could be:: + + use Symfony\Component\VarDumper\Cloner\VarCloner; + use Symfony\Component\VarDumper\Dumper\CliDumper; + + cloner = new VarCloner(); + $dumper = new CliDumper(); + $output = fopen('php://memory', 'r+b'); + + $dumper->dump($cloner->cloneVar($variable), $output); + rewind($output); + $output = stream_get_contents($output); + + // $output is now populated with the dump representation of $variable + +Dumpers implement the :class:`Symfony\\Component\\VarDumper\\Dumper\\DataDumperInterface` +interface that specifies the +:method:`dump(Data $data) ` +method. They also typically implement the +:class:`Symfony\\Component\\VarDumper\\Cloner\\DumperInterface` that frees +them from re-implementing the logic required to walk through a +:class:`Symfony\\Component\\VarDumper\\Cloner\\Data` object's internal structure. + +Casters +------- + +Objects and resources nested in a PHP variable are "cast" to arrays in the +intermediate :class:`Symfony\\Component\\VarDumper\\Cloner\\Data` +representation. You can tweak the array representation for each object/resource +by hooking a Caster into this process. The component already includes many +casters for base PHP classes and other common classes. + +If you want to build your own Caster, you can register one before cloning +a PHP variable. Casters are registered using either a Cloner's constructor +or its ``addCasters()`` method:: + + use Symfony\Component\VarDumper\Cloner\VarCloner; + + $myCasters = array(...); + $cloner = new VarCloner($myCasters); + + // or + + $cloner->addCasters($myCasters); + +The provided ``$myCasters`` argument is an array that maps a class, +an interface or a resource type to a callable:: + + $myCasters = array( + 'FooClass' => $myFooClassCallableCaster, + ':bar resource' => $myBarResourceCallableCaster, + ); + +As you can notice, resource types are prefixed by a ``:`` to prevent +colliding with a class name. + +Because an object has one main class and potentially many parent classes +or interfaces, many casters can be applied to one object. In this case, +casters are called one after the other, starting from casters bound to the +interfaces, the parents classes and then the main class. Several casters +can also be registered for the same resource type/class/interface. +They are called in registration order. + +Casters are responsible for returning the properties of the object or resource +being cloned in an array. They are callables that accept four arguments: + +* the object or resource being casted, +* an array modelled for objects after PHP's native ``(array)`` cast operator, +* a :class:`Symfony\\Component\\VarDumper\\Cloner\\Stub` object + representing the main properties of the object (class, type, etc.), +* true/false when the caster is called nested in a structure or not. + +Here is a simple caster not doing anything:: + + function myCaster($object, $array, $stub, $isNested) + { + // ... populate/alter $array to your needs + + return $array; + } + +For objects, the ``$array`` parameter comes pre-populated using PHP's native +``(array)`` casting operator or with the return value of ``$object->__debugInfo()`` +if the magic method exists. Then, the return value of one Caster is given +as the array argument to the next Caster in the chain. + +When casting with the ``(array)`` operator, PHP prefixes protected properties +with a ``\0*\0`` and private ones with the class owning the property. For example, +``\0Foobar\0`` will be the prefix for all private properties of objects of +type Foobar. Casters follow this convention and add two more prefixes: ``\0~\0`` +is used for virtual properties and ``\0+\0`` for dynamic ones (runtime added +properties not in the class declaration). + +.. note:: + + Although you can, it is advised to not alter the state of an object + while casting it in a Caster. + +.. tip:: + + Before writing your own casters, you should check the existing ones. diff --git a/components/var_dumper/index.rst b/components/var_dumper/index.rst new file mode 100644 index 00000000000..2c67f2aa53f --- /dev/null +++ b/components/var_dumper/index.rst @@ -0,0 +1,8 @@ +VarDumper +========= + +.. toctree:: + :maxdepth: 2 + + introduction + advanced diff --git a/components/var_dumper/introduction.rst b/components/var_dumper/introduction.rst new file mode 100644 index 00000000000..05eff3e8849 --- /dev/null +++ b/components/var_dumper/introduction.rst @@ -0,0 +1,253 @@ +.. index:: + single: VarDumper + single: Components; VarDumper + +The VarDumper Component +======================= + + The VarDumper component provides mechanisms for walking through any + arbitrary PHP variable. Built on top, it provides a better ``dump()`` + function that you can use instead of :phpfunction:`var_dump`. + +.. versionadded:: 2.6 + The VarDumper component was introduced in Symfony 2.6. + +Installation +------------ + +You can install the component in 2 different ways: + +* :doc:`Install it via Composer ` (``symfony/var-dumper`` on `Packagist`_); +* Use the official Git repository (https://github.com/symfony/var-dumper). + +.. _components-var-dumper-dump: + +The dump() Function +------------------- + +The VarDumper component creates a global ``dump()`` function that you can +use instead of e.g. :phpfunction:`var_dump`. By using it, you'll gain: + +* Per object and resource types specialized view to e.g. filter out + Doctrine internals while dumping a single proxy entity, or get more + insight on opened files with :phpfunction:`stream_get_meta_data`; +* Configurable output formats: HTML or colored command line output; +* Ability to dump internal references, either soft ones (objects or + resources) or hard ones (``=&`` on arrays or objects properties). + Repeated occurrences of the same object/array/resource won't appear + again and again anymore. Moreover, you'll be able to inspect the + reference structure of your data; +* Ability to operate in the context of an output buffering handler. + +For example:: + + require __DIR__.'/vendor/autoload.php'; + // create a variable, which could be anything! + $someVar = '...'; + + dump($someVar); + +By default, the output format and destination are selected based on your +current PHP SAPI: + +* On the command line (CLI SAPI), the output is written on ``STDOUT``. This + can be surprising to some because this bypasses PHP's output buffering + mechanism; +* On other SAPIs, dumps are written as HTML in the regular output. + +.. note:: + + If you want to catch the dump output as a string, please read the + `advanced documentation `_ which contains examples of it. + You'll also learn how to change the format or redirect the output to + wherever you want. + +.. tip:: + + In order to have the ``dump()`` function always available when running + any PHP code, you can install it globally on your computer: + + #. Run ``composer global require symfony/var-dumper``; + #. Add ``auto_prepend_file = ${HOME}/.composer/vendor/autoload.php`` + to your ``php.ini`` file; + #. From time to time, run ``composer global update`` to have the latest + bug fixes. + +DebugBundle and Twig Integration +-------------------------------- + +The ``DebugBundle`` allows greater integration of the component into the +Symfony full stack framework. It is enabled by default in the *dev* and *test* +environment of the standard edition since version 2.6. + +Since generating (even debug) output in the controller or in the model +of your application may just break it by e.g. sending HTTP headers or +corrupting your view, the bundle configures the ``dump()`` function so that +variables are dumped in the web debug toolbar. + +But if the toolbar can not be displayed because you e.g. called ``die``/``exit`` +or a fatal error occurred, then dumps are written on the regular output. + +In a Twig template, two constructs are available for dumping a variable. +Choosing between both is mostly a matter of personal taste, still: + +* ``{% dump foo.bar %}`` is the way to go when the original template output + shall not be modified: variables are not dumped inline, but in the web + debug toolbar; +* on the contrary, ``{{ dump(foo.bar) }}`` dumps inline and thus may or not + be suited to your use case (e.g. you shouldn't use it in an HTML + attribute or a `` + {% javascripts '@AppBundle/Resources/public/js/example.coffee' filter='coffee' %} + {% endjavascripts %} .. code-block:: html+php javascripts( - array('@AcmeFooBundle/Resources/public/js/example.coffee'), + array('@AppBundle/Resources/public/js/example.coffee'), array('coffee') ) as $url): ?> - - + + This is all that's needed to compile this CoffeeScript file and serve it as the compiled JavaScript. @@ -84,23 +84,23 @@ You can also combine multiple CoffeeScript files into a single output file: .. code-block:: html+jinja - {% javascripts '@AcmeFooBundle/Resources/public/js/example.coffee' - '@AcmeFooBundle/Resources/public/js/another.coffee' + {% javascripts '@AppBundle/Resources/public/js/example.coffee' + '@AppBundle/Resources/public/js/another.coffee' filter='coffee' %} - + {% endjavascripts %} .. code-block:: html+php javascripts( array( - '@AcmeFooBundle/Resources/public/js/example.coffee', - '@AcmeFooBundle/Resources/public/js/another.coffee', + '@AppBundle/Resources/public/js/example.coffee', + '@AppBundle/Resources/public/js/another.coffee', ), array('coffee') ) as $url): ?> - - + + Both the files will now be served up as a single file compiled into regular JavaScript. @@ -118,7 +118,7 @@ adding the JavaScript files to the files to be combined as above will not work as the regular JavaScript files will not survive the CoffeeScript compilation. This problem can be avoided by using the ``apply_to`` option in the config, -which allows you to specify that a filter should always be applied to particular +which allows you to specify which filter should always be applied to particular file extensions. In this case you can specify that the ``coffee`` filter is applied to all ``.coffee`` files: @@ -170,20 +170,20 @@ being run through the CoffeeScript filter): .. code-block:: html+jinja - {% javascripts '@AcmeFooBundle/Resources/public/js/example.coffee' - '@AcmeFooBundle/Resources/public/js/another.coffee' - '@AcmeFooBundle/Resources/public/js/regular.js' %} - + {% javascripts '@AppBundle/Resources/public/js/example.coffee' + '@AppBundle/Resources/public/js/another.coffee' + '@AppBundle/Resources/public/js/regular.js' %} + {% endjavascripts %} .. code-block:: html+php javascripts( array( - '@AcmeFooBundle/Resources/public/js/example.coffee', - '@AcmeFooBundle/Resources/public/js/another.coffee', - '@AcmeFooBundle/Resources/public/js/regular.js', + '@AppBundle/Resources/public/js/example.coffee', + '@AppBundle/Resources/public/js/another.coffee', + '@AppBundle/Resources/public/js/regular.js', ) ) as $url): ?> - - + + diff --git a/cookbook/assetic/asset_management.rst b/cookbook/assetic/asset_management.rst index 90311adc4e4..d15e9786814 100644 --- a/cookbook/assetic/asset_management.rst +++ b/cookbook/assetic/asset_management.rst @@ -18,11 +18,11 @@ directly: .. code-block:: html+jinja - + .. code-block:: php - + But *with* Assetic, you can manipulate these assets however you want (or load them from anywhere) before serving them. This means you can: @@ -59,17 +59,17 @@ To include JavaScript files, use the ``javascripts`` tag in any template: .. code-block:: html+jinja - {% javascripts '@AcmeFooBundle/Resources/public/js/*' %} - + {% javascripts '@AppBundle/Resources/public/js/*' %} + {% endjavascripts %} .. code-block:: html+php javascripts( - array('@AcmeFooBundle/Resources/public/js/*') + array('@AppBundle/Resources/public/js/*') ) as $url): ?> - - + + .. note:: @@ -81,8 +81,8 @@ To include JavaScript files, use the ``javascripts`` tag in any template: {# ... #} {% block javascripts %} - {% javascripts '@AcmeFooBundle/Resources/public/js/*' %} - + {% javascripts '@AppBundle/Resources/public/js/*' %} + {% endjavascripts %} {% endblock %} {# ... #} @@ -92,7 +92,7 @@ To include JavaScript files, use the ``javascripts`` tag in any template: You can also include CSS Stylesheets: see :ref:`cookbook-assetic-including-css`. In this example, all of the files in the ``Resources/public/js/`` directory -of the ``AcmeFooBundle`` will be loaded and served from a different location. +of the AppBundle will be loaded and served from a different location. The actual rendered tag might simply look like: .. code-block:: html @@ -115,18 +115,18 @@ above, except with the ``stylesheets`` tag: .. code-block:: html+jinja - {% stylesheets 'bundles/acme_foo/css/*' filter='cssrewrite' %} + {% stylesheets 'bundles/app/css/*' filter='cssrewrite' %} {% endstylesheets %} .. code-block:: html+php stylesheets( - array('bundles/acme_foo/css/*'), + array('bundles/app/css/*'), array('cssrewrite') ) as $url): ?> - + .. note:: @@ -138,7 +138,7 @@ above, except with the ``stylesheets`` tag: {# ... #} {% block stylesheets %} - {% stylesheets 'bundles/acme_foo/css/*' filter='cssrewrite' %} + {% stylesheets 'bundles/app/css/*' filter='cssrewrite' %} {% endstylesheets %} {% endblock %} @@ -151,11 +151,11 @@ the :ref:`cssrewrite ` filter. .. note:: Notice that in the original example that included JavaScript files, you - referred to the files using a path like ``@AcmeFooBundle/Resources/public/file.js``, + referred to the files using a path like ``@AppBundle/Resources/public/file.js``, but that in this example, you referred to the CSS files using their actual, - publicly-accessible path: ``bundles/acme_foo/css``. You can use either, except + publicly-accessible path: ``bundles/app/css``. You can use either, except that there is a known issue that causes the ``cssrewrite`` filter to fail - when using the ``@AcmeFooBundle`` syntax for CSS Stylesheets. + when using the ``@AppBundle`` syntax for CSS Stylesheets. .. _cookbook-assetic-including-image: @@ -168,17 +168,17 @@ To include an image you can use the ``image`` tag. .. code-block:: html+jinja - {% image '@AcmeFooBundle/Resources/public/images/example.jpg' %} + {% image '@AppBundle/Resources/public/images/example.jpg' %} Example {% endimage %} .. code-block:: html+php image( - array('@AcmeFooBundle/Resources/public/images/example.jpg') + array('@AppBundle/Resources/public/images/example.jpg') ) as $url): ?> Example - + You can also use Assetic for image optimization. More information in :doc:`/cookbook/assetic/jpeg_optimize`. @@ -198,7 +198,7 @@ You can see an example in the previous section. .. caution:: When using the ``cssrewrite`` filter, don't refer to your CSS files using - the ``@AcmeFooBundle`` syntax. See the note in the above section for details. + the ``@AppBundle`` syntax. See the note in the above section for details. Combining Assets ~~~~~~~~~~~~~~~~ @@ -215,7 +215,7 @@ but still serve them as a single file: .. code-block:: html+jinja {% javascripts - '@AcmeFooBundle/Resources/public/js/*' + '@AppBundle/Resources/public/js/*' '@AcmeBarBundle/Resources/public/js/form.js' '@AcmeBarBundle/Resources/public/js/calendar.js' %} @@ -225,13 +225,13 @@ but still serve them as a single file: javascripts( array( - '@AcmeFooBundle/Resources/public/js/*', + '@AppBundle/Resources/public/js/*', '@AcmeBarBundle/Resources/public/js/form.js', '@AcmeBarBundle/Resources/public/js/calendar.js', ) ) as $url): ?> - + In the ``dev`` environment, each file is still served individually, so that you can debug problems more easily. However, in the ``prod`` environment @@ -254,8 +254,8 @@ combine third party assets, such as jQuery, with your own into a single file: .. code-block:: html+jinja {% javascripts - '@AcmeFooBundle/Resources/public/js/thirdparty/jquery.js' - '@AcmeFooBundle/Resources/public/js/*' %} + '@AppBundle/Resources/public/js/thirdparty/jquery.js' + '@AppBundle/Resources/public/js/*' %} {% endjavascripts %} @@ -263,12 +263,12 @@ combine third party assets, such as jQuery, with your own into a single file: javascripts( array( - '@AcmeFooBundle/Resources/public/js/thirdparty/jquery.js', - '@AcmeFooBundle/Resources/public/js/*', + '@AppBundle/Resources/public/js/thirdparty/jquery.js', + '@AppBundle/Resources/public/js/*', ) ) as $url): ?> - + Using Named Assets ~~~~~~~~~~~~~~~~~~ @@ -287,8 +287,8 @@ configuration under the ``assetic`` section. Read more in the assets: jquery_and_ui: inputs: - - '@AcmeFooBundle/Resources/public/js/thirdparty/jquery.js' - - '@AcmeFooBundle/Resources/public/js/thirdparty/jquery.ui.js' + - '@AppBundle/Resources/public/js/thirdparty/jquery.js' + - '@AppBundle/Resources/public/js/thirdparty/jquery.ui.js' .. code-block:: xml @@ -299,8 +299,8 @@ configuration under the ``assetic`` section. Read more in the - @AcmeFooBundle/Resources/public/js/thirdparty/jquery.js - @AcmeFooBundle/Resources/public/js/thirdparty/jquery.ui.js + @AppBundle/Resources/public/js/thirdparty/jquery.js + @AppBundle/Resources/public/js/thirdparty/jquery.ui.js @@ -312,8 +312,8 @@ configuration under the ``assetic`` section. Read more in the 'assets' => array( 'jquery_and_ui' => array( 'inputs' => array( - '@AcmeFooBundle/Resources/public/js/thirdparty/jquery.js', - '@AcmeFooBundle/Resources/public/js/thirdparty/jquery.ui.js', + '@AppBundle/Resources/public/js/thirdparty/jquery.js', + '@AppBundle/Resources/public/js/thirdparty/jquery.ui.js', ), ), ), @@ -328,7 +328,7 @@ with the ``@named_asset`` notation: {% javascripts '@jquery_and_ui' - '@AcmeFooBundle/Resources/public/js/*' %} + '@AppBundle/Resources/public/js/*' %} {% endjavascripts %} @@ -337,11 +337,11 @@ with the ``@named_asset`` notation: javascripts( array( '@jquery_and_ui', - '@AcmeFooBundle/Resources/public/js/*', + '@AppBundle/Resources/public/js/*', ) ) as $url): ?> - + .. _cookbook-assetic-filters: @@ -406,18 +406,18 @@ into your template: .. code-block:: html+jinja - {% javascripts '@AcmeFooBundle/Resources/public/js/*' filter='uglifyjs2' %} + {% javascripts '@AppBundle/Resources/public/js/*' filter='uglifyjs2' %} {% endjavascripts %} .. code-block:: html+php javascripts( - array('@AcmeFooBundle/Resources/public/js/*'), + array('@AppBundle/Resources/public/js/*'), array('uglifyjs2') ) as $url): ?> - + A more detailed guide about configuring and using Assetic filters as well as details of Assetic's debug mode can be found in :doc:`/cookbook/assetic/uglifyjs`. @@ -432,19 +432,19 @@ done from the template and is relative to the public document root: .. code-block:: html+jinja - {% javascripts '@AcmeFooBundle/Resources/public/js/*' output='js/compiled/main.js' %} + {% javascripts '@AppBundle/Resources/public/js/*' output='js/compiled/main.js' %} {% endjavascripts %} .. code-block:: html+php javascripts( - array('@AcmeFooBundle/Resources/public/js/*'), + array('@AppBundle/Resources/public/js/*'), array(), array('output' => 'js/compiled/main.js') ) as $url): ?> - + .. note:: @@ -548,23 +548,23 @@ command will automatically regenerate assets *as they change*: $ php app/console assetic:dump --watch Since running this command in the ``dev`` environment may generate a bunch -of files, it's usually a good idea to point your generated assets files to +of files, it's usually a good idea to point your generated asset files to some isolated directory (e.g. ``/js/compiled``), to keep things organized: .. configuration-block:: .. code-block:: html+jinja - {% javascripts '@AcmeFooBundle/Resources/public/js/*' output='js/compiled/main.js' %} + {% javascripts '@AppBundle/Resources/public/js/*' output='js/compiled/main.js' %} {% endjavascripts %} .. code-block:: html+php javascripts( - array('@AcmeFooBundle/Resources/public/js/*'), + array('@AppBundle/Resources/public/js/*'), array(), array('output' => 'js/compiled/main.js') ) as $url): ?> - + diff --git a/cookbook/assetic/jpeg_optimize.rst b/cookbook/assetic/jpeg_optimize.rst index 162b859fcf9..7955771ca02 100644 --- a/cookbook/assetic/jpeg_optimize.rst +++ b/cookbook/assetic/jpeg_optimize.rst @@ -57,7 +57,7 @@ It can now be used from a template: .. code-block:: html+jinja - {% image '@AcmeFooBundle/Resources/public/images/example.jpg' + {% image '@AppBundle/Resources/public/images/example.jpg' filter='jpegoptim' output='/images/example.jpg' %} Example {% endimage %} @@ -65,11 +65,11 @@ It can now be used from a template: .. code-block:: html+php image( - array('@AcmeFooBundle/Resources/public/images/example.jpg'), + array('@AppBundle/Resources/public/images/example.jpg'), array('jpegoptim') ) as $url): ?> Example - + Removing all EXIF Data ~~~~~~~~~~~~~~~~~~~~~~ @@ -204,7 +204,7 @@ The Twig template can now be changed to the following: .. code-block:: html+jinja - Example + Example You can specify the output directory in the config in the following way: diff --git a/cookbook/assetic/uglifyjs.rst b/cookbook/assetic/uglifyjs.rst index 64d76c0a0f8..8b0ddd66028 100644 --- a/cookbook/assetic/uglifyjs.rst +++ b/cookbook/assetic/uglifyjs.rst @@ -133,7 +133,7 @@ can configure its location using the ``node`` key: .. code-block:: xml - {% endjavascripts %} .. code-block:: html+php javascripts( - array('@AcmeFooBundle/Resources/public/js/*'), + array('@AppBundle/Resources/public/js/*'), array('uglifyj2s') ) as $url): ?> - + .. note:: - The above example assumes that you have a bundle called ``AcmeFooBundle`` - and your JavaScript files are in the ``Resources/public/js`` directory under - your bundle. This isn't important however - you can include your JavaScript + The above example assumes that you have a bundle called AppBundle and your + JavaScript files are in the ``Resources/public/js`` directory under your + bundle. This isn't important however - you can include your JavaScript files no matter where they are. With the addition of the ``uglifyjs2`` filter to the asset tags above, you @@ -197,18 +197,18 @@ apply this filter when debug mode is off (e.g. ``app.php``): .. code-block:: html+jinja - {% javascripts '@AcmeFooBundle/Resources/public/js/*' filter='?uglifyjs2' %} + {% javascripts '@AppBundle/Resources/public/js/*' filter='?uglifyjs2' %} {% endjavascripts %} .. code-block:: html+php javascripts( - array('@AcmeFooBundle/Resources/public/js/*'), + array('@AppBundle/Resources/public/js/*'), array('?uglifyjs2') ) as $url): ?> - + To try this out, switch to your ``prod`` environment (``app.php``). But before you do, don't forget to :ref:`clear your cache ` @@ -272,19 +272,19 @@ helper: .. code-block:: html+jinja - {% stylesheets 'bundles/AcmeFoo/css/*' filter='uglifycss' filter='cssrewrite' %} + {% stylesheets 'bundles/App/css/*' filter='uglifycss' filter='cssrewrite' %} {% endstylesheets %} .. code-block:: html+php stylesheets( - array('bundles/AcmeFoo/css/*'), + array('bundles/App/css/*'), array('uglifycss'), array('cssrewrite') ) as $url): ?> - + Just like with the ``uglifyjs2`` filter, if you prefix the filter name with ``?`` (i.e. ``?uglifycss``), the minification will only happen when you're diff --git a/cookbook/assetic/yuicompressor.rst b/cookbook/assetic/yuicompressor.rst index ee200b82e46..356b67b6071 100644 --- a/cookbook/assetic/yuicompressor.rst +++ b/cookbook/assetic/yuicompressor.rst @@ -91,24 +91,24 @@ the view layer, this work is done in your templates: .. code-block:: html+jinja - {% javascripts '@AcmeFooBundle/Resources/public/js/*' filter='yui_js' %} + {% javascripts '@AppBundle/Resources/public/js/*' filter='yui_js' %} {% endjavascripts %} .. code-block:: html+php javascripts( - array('@AcmeFooBundle/Resources/public/js/*'), + array('@AppBundle/Resources/public/js/*'), array('yui_js') ) as $url): ?> - + .. note:: - The above example assumes that you have a bundle called ``AcmeFooBundle`` - and your JavaScript files are in the ``Resources/public/js`` directory under - your bundle. This isn't important however - you can include your JavaScript + The above example assumes that you have a bundle called AppBundle and your + JavaScript files are in the ``Resources/public/js`` directory under your + bundle. This isn't important however - you can include your JavaScript files no matter where they are. With the addition of the ``yui_js`` filter to the asset tags above, you should @@ -119,18 +119,18 @@ can be repeated to minify your stylesheets. .. code-block:: html+jinja - {% stylesheets '@AcmeFooBundle/Resources/public/css/*' filter='yui_css' %} + {% stylesheets '@AppBundle/Resources/public/css/*' filter='yui_css' %} {% endstylesheets %} .. code-block:: html+php stylesheets( - array('@AcmeFooBundle/Resources/public/css/*'), + array('@AppBundle/Resources/public/css/*'), array('yui_css') ) as $url): ?> - + Disable Minification in Debug Mode ---------------------------------- @@ -145,18 +145,18 @@ apply this filter when debug mode is off. .. code-block:: html+jinja - {% javascripts '@AcmeFooBundle/Resources/public/js/*' filter='?yui_js' %} + {% javascripts '@AppBundle/Resources/public/js/*' filter='?yui_js' %} {% endjavascripts %} .. code-block:: html+php javascripts( - array('@AcmeFooBundle/Resources/public/js/*'), + array('@AppBundle/Resources/public/js/*'), array('?yui_js') ) as $url): ?> - + .. tip:: diff --git a/cookbook/bundles/best_practices.rst b/cookbook/bundles/best_practices.rst index d9c5fa88801..8d0ecead684 100644 --- a/cookbook/bundles/best_practices.rst +++ b/cookbook/bundles/best_practices.rst @@ -1,12 +1,24 @@ .. index:: single: Bundle; Best practices -How to Use best Practices for Structuring Bundles -================================================= +Best Practices for Reusable Bundles +=================================== -A bundle is a directory that has a well-defined structure and can host anything -from classes to controllers and web resources. Even if bundles are very -flexible, you should follow some best practices if you want to distribute them. +There are 2 types of bundles: + +* Application-specific bundles: only used to build your application; +* Reusable bundles: meant to be shared across many projects. + +This article is all about how to structure your **reusable bundles** so that +they're easy to configure and extend. Many of these recommendations do not +apply to application bundles because you'll want to keep those as simple +as possible. For application bundles, just follow the practices shown throughout +the book and cookbook. + +.. seealso:: + + The best practices for application-specific bundles are discussed in + :doc:`/best_practices/introduction`. .. index:: pair: Bundle; Naming conventions @@ -55,7 +67,7 @@ class name. .. note:: - Symfony2 core Bundles do not prefix the Bundle class with ``Symfony`` + Symfony core Bundles do not prefix the Bundle class with ``Symfony`` and always add a ``Bundle`` sub-namespace; for example: :class:`Symfony\\Bundle\\FrameworkBundle\\FrameworkBundle`. @@ -68,8 +80,7 @@ examples). Directory Structure ------------------- -The basic directory structure of a ``HelloBundle`` bundle must read as -follows: +The basic directory structure of a HelloBundle must read as follows: .. code-block:: text @@ -148,8 +159,7 @@ instance, a ``HelloController`` controller is stored in ``Bundle/HelloBundle/Controller/HelloController.php`` and the fully qualified class name is ``Bundle\HelloBundle\Controller\HelloController``. -All classes and files must follow the Symfony2 coding -:doc:`standards `. +All classes and files must follow the Symfony coding :doc:`standards `. Some classes should be seen as facades and should be as short as possible, like Commands, Helpers, Listeners, and Controllers. @@ -163,7 +173,7 @@ Vendors ------- A bundle must not embed third-party PHP libraries. It should rely on the -standard Symfony2 autoloading instead. +standard Symfony autoloading instead. A bundle should not embed third-party libraries written in JavaScript, CSS, or any other language. @@ -236,13 +246,13 @@ following standardized instructions in your ``README.md`` file. { $bundles = array( // ... - + new \\(), ); - + // ... } - + // ... } ``` @@ -279,10 +289,10 @@ Configuration ------------- To provide more flexibility, a bundle can provide configurable settings by -using the Symfony2 built-in mechanisms. +using the Symfony built-in mechanisms. For simple configuration settings, rely on the default ``parameters`` entry of -the Symfony2 configuration. Symfony2 parameters are simple key/value pairs; a +the Symfony configuration. Symfony parameters are simple key/value pairs; a value being any valid PHP value. Each parameter name should start with the bundle alias, though this is just a best-practice suggestion. The rest of the parameter name will use a period (``.``) to separate different parts (e.g. @@ -328,6 +338,48 @@ semantic configuration described in the cookbook. If you are defining services, they should also be prefixed with the bundle alias. +Custom Validation Constraints +----------------------------- + +Starting with Symfony 2.5, a new Validation API was introduced. In fact, +there are 3 modes, which the user can configure in their project: + +* 2.4: the original 2.4 and earlier validation API; +* 2.5: the new 2.5 and later validation API; +* 2.5-BC: the new 2.5 API with a backwards-compatible layer so that the + 2.4 API still works. This is only available in PHP 5.3.9+. + +As a bundle author, you'll want to support *both* API's, since some users +may still be using the 2.4 API. Specifically, if your bundle adds a violation +directly to the :class:`Symfony\\Component\\Validator\\Context\\ExecutionContext` +(e.g. like in a custom validation constraint), you'll need to check for which +API is being used. The following code, would work for *all* users:: + + use Symfony\Component\Validator\ConstraintValidator; + use Symfony\Component\Validator\Constraint; + use Symfony\Component\Validator\Context\ExecutionContextInterface; + // ... + + class ContainsAlphanumericValidator extends ConstraintValidator + { + public function validate($value, Constraint $constraint) + { + if ($this->context instanceof ExecutionContextInterface) { + // the 2.5 API + $this->context->buildViolation($constraint->message) + ->setParameter('%string%', $value) + ->addViolation() + ; + } else { + // the 2.4 API + $this->context->addViolation( + $constraint->message, + array('%string%' => $value) + ); + } + } + } + Learn more from the Cookbook ---------------------------- diff --git a/cookbook/bundles/configuration.rst b/cookbook/bundles/configuration.rst index 9d374d2dc4d..fd1c9ea14a1 100644 --- a/cookbook/bundles/configuration.rst +++ b/cookbook/bundles/configuration.rst @@ -129,7 +129,7 @@ First things first, you have to create an extension class as explained in Whenever a user includes the ``acme_social`` key (which is the DI alias) in a configuration file, the configuration under it is added to an array of -configurations and passed to the ``load()`` method of your extension (Symfony2 +configurations and passed to the ``load()`` method of your extension (Symfony automatically converts XML and YAML to an array). For the configuration example in the previous section, the array passed to your @@ -384,7 +384,8 @@ Assume the XSD file is called ``hello-1.0.xsd``, the schema location will be + xsi:schemaLocation="http://acme_company.com/schema/dic/hello + http://acme_company.com/schema/dic/hello/hello-1.0.xsd"> diff --git a/cookbook/bundles/extension.rst b/cookbook/bundles/extension.rst index cc4ce322399..aa2c5be8a35 100644 --- a/cookbook/bundles/extension.rst +++ b/cookbook/bundles/extension.rst @@ -23,8 +23,9 @@ following conventions: * It has to live in the ``DependencyInjection`` namespace of the bundle; * The name is equal to the bundle name with the ``Bundle`` suffix replaced by - ``Extension`` (e.g. the Extension class of ``AcmeHelloBundle`` would be - called ``AcmeHelloExtension``). + ``Extension`` (e.g. the Extension class of the AppBundle would be called + ``AppExtension`` and the one for AcmeHelloBundle would be called + ``AcmeHelloExtension``). The Extension class should implement the :class:`Symfony\\Component\\DependencyInjection\\Extension\\ExtensionInterface`, @@ -84,7 +85,7 @@ container, to ensure all services and parameters are also added to the actual container. In the ``load()`` method, you can use PHP code to register service definitions, -but it is more common if you put the these definitions in a configuration file +but it is more common if you put these definitions in a configuration file (using the Yaml, XML or PHP format). Luckily, you can use the file loaders in the extension! diff --git a/cookbook/bundles/inheritance.rst b/cookbook/bundles/inheritance.rst index 13500eb6685..25c29da67fc 100644 --- a/cookbook/bundles/inheritance.rst +++ b/cookbook/bundles/inheritance.rst @@ -91,7 +91,7 @@ The same goes for routing files and some other resources. .. note:: The overriding of resources only works when you refer to resources with - the ``@FosUserBundle/Resources/config/routing/security.xml`` method. + the ``@FOSUserBundle/Resources/config/routing/security.xml`` method. If you refer to resources without using the @BundleName shortcut, they can't be overridden in this way. diff --git a/cookbook/bundles/installation.rst b/cookbook/bundles/installation.rst index 464b485216f..bd29a2c6226 100644 --- a/cookbook/bundles/installation.rst +++ b/cookbook/bundles/installation.rst @@ -5,69 +5,46 @@ How to Install 3rd Party Bundles ================================ Most bundles provide their own installation instructions. However, the -basic steps for installing a bundle are the same. +basic steps for installing a bundle are the same: -Add Composer Dependencies -------------------------- +* `A) Add Composer Dependencies`_ +* `B) Enable the Bundle`_ +* `C) Configure the Bundle`_ -In Symfony, dependencies are managed with Composer. It's a good idea to learn -some basics of Composer in `their documentation`_. +A) Add Composer Dependencies +---------------------------- -Before you can use Composer to install a bundle, you should look for a -`Packagist`_ package of that bundle. For example, if you search for the popular -`FOSUserBundle`_ you will find a package called `friendsofsymfony/user-bundle`_. +Dependencies are managed with Composer, so if Composer is new to you, learn +some basics in `their documentation`_. This has 2 steps: -.. note:: - - Packagist is the main archive for Composer. If you are searching - for a bundle, the best thing you can do is check out - `KnpBundles`_, it is the unofficial archive of Symfony Bundles. If - a bundle contains a ``README`` file, it is displayed there and if it - has a Packagist package it shows a link to the package. It's a - really useful site to begin searching for bundles. - -Now that you have the package name, you should determine the version -you want to use. Usually different versions of a bundle correspond to -a particular version of Symfony. This information should be in the ``README`` -file. If it isn't, you can use the version you want. If you choose an incompatible -version, Composer will throw dependency errors when you try to install. If -this happens, you can try a different version. - -Now you can add the bundle to your ``composer.json`` file and update the -dependencies. You can do this manually: - -1. **Add it to the ``composer.json`` file:** - - .. code-block:: json - - { - ..., - "require": { - ..., - "friendsofsymfony/user-bundle": "2.0.*@dev" - } - } +1) Find out the Name of the Bundle on Packagist +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -2. **Update the dependency:** +The README for a bundle (e.g. `FOSUserBundle`_) usually tells you its name +(e.g. ``friendsofsymfony/user-bundle``). If it doesn't, you can search for +the library on the `Packagist.org`_ site. - .. code-block:: bash - - $ php composer.phar update friendsofsymfony/user-bundle - - or update all dependencies +.. note:: - .. code-block:: bash + Looking for bundles? Try searching at `KnpBundles.com`_: the unofficial + archive of Symfony Bundles. - $ php composer.phar update +2) Install the Bundle via Composer +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Or you can do this in one command: +Now that you know the package name, you can install it via Composer: .. code-block:: bash - $ php composer.phar require friendsofsymfony/user-bundle:2.0.*@dev + $ composer require friendsofsymfony/user-bundle -Enable the Bundle ------------------ +This will choose the best version for your project, add it to ``composer.json`` +and download the library into the ``vendor/`` directory. If you need a specific +version, add a ``:`` and the version right after the library name (see +`composer require`_). + +B) Enable the Bundle +-------------------- At this point, the bundle is installed in your Symfony project (in ``vendor/friendsofsymfony/``) and the autoloader recognizes its classes. @@ -91,13 +68,13 @@ The only thing you need to do now is register the bundle in ``AppKernel``:: } } -Configure the Bundle --------------------- +C) Configure the Bundle +----------------------- -Usually a bundle requires some configuration to be added to app's -``app/config/config.yml`` file. The bundle's documentation will likely -describe that configuration. But you can also get a reference of the -bundle's config via the ``config:dump-reference`` command. +It's pretty common for a bundle to need some additional setup or configuration +in ``app/config/config.yml``. The bundle's documentation will tell you about +the configuration, but you can also get a reference of the bundle's config +via the ``config:dump-reference`` command. For instance, in order to look the reference of the ``assetic`` config you can use this: @@ -132,10 +109,10 @@ Other Setup ----------- At this point, check the ``README`` file of your brand new bundle to see -what to do next. +what to do next. Have fun! .. _their documentation: http://getcomposer.org/doc/00-intro.md -.. _Packagist: https://packagist.org +.. _Packagist.org: https://packagist.org .. _FOSUserBundle: https://github.com/FriendsOfSymfony/FOSUserBundle -.. _`friendsofsymfony/user-bundle`: https://packagist.org/packages/friendsofsymfony/user-bundle -.. _KnpBundles: http://knpbundles.com/ +.. _KnpBundles.com: http://knpbundles.com/ +.. _`composer require`: https://getcomposer.org/doc/03-cli.md#require diff --git a/cookbook/bundles/override.rst b/cookbook/bundles/override.rst index bdca242f217..70ba6786dde 100644 --- a/cookbook/bundles/override.rst +++ b/cookbook/bundles/override.rst @@ -18,12 +18,12 @@ For information on overriding templates, see Routing ------- -Routing is never automatically imported in Symfony2. If you want to include +Routing is never automatically imported in Symfony. If you want to include the routes from any bundle, then they must be manually imported from somewhere in your application (e.g. ``app/config/routing.yml``). The easiest way to "override" a bundle's routing is to never import it at -all. Instead of importing a third-party bundle's routing, simply copying +all. Instead of importing a third-party bundle's routing, simply copy that routing file into your application, modify it, and import it instead. Controllers @@ -68,7 +68,7 @@ in the core FrameworkBundle: $container->setParameter('translator.class', 'Acme\HelloBundle\Translation\Translator'); Secondly, if the class is not available as a parameter, you want to make sure the -class is always overridden when your bundle is used, or you need to modify +class is always overridden when your bundle is used or if you need to modify something beyond just the class name, you should use a compiler pass:: // src/Acme/DemoBundle/DependencyInjection/Compiler/OverrideServiceCompilerPass.php @@ -106,7 +106,7 @@ Forms ----- In order to override a form type, it has to be registered as a service (meaning -it is tagged as "form.type"). You can then override it as you would override any +it is tagged as ``form.type``). You can then override it as you would override any service as explained in `Services & Configuration`_. This, of course, will only work if the type is referred to by its alias rather than being instantiated, e.g.:: @@ -136,7 +136,7 @@ the constraints to a new validation group: .. code-block:: yaml # src/Acme/UserBundle/Resources/config/validation.yml - Fos\UserBundle\Model\User: + FOS\UserBundle\Model\User: properties: plainPassword: - NotBlank: @@ -152,10 +152,17 @@ the constraints to a new validation group: + xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping + http://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd"> + + + + + + - - diff --git a/cookbook/bundles/prepend_extension.rst b/cookbook/bundles/prepend_extension.rst index 4c5cb7364fc..35deb3731b0 100644 --- a/cookbook/bundles/prepend_extension.rst +++ b/cookbook/bundles/prepend_extension.rst @@ -68,9 +68,10 @@ in case a specific other bundle is not registered:: switch ($name) { case 'acme_something': case 'acme_other': - // set use_acme_goodbye to false in the config of acme_something and acme_other - // note that if the user manually configured use_acme_goodbye to true in the - // app/config/config.yml then the setting would in the end be true and not false + // set use_acme_goodbye to false in the config of + // acme_something and acme_other note that if the user manually + // configured use_acme_goodbye to true in the app/config/config.yml + // then the setting would in the end be true and not false $container->prependExtensionConfig($name, $config); break; } @@ -79,7 +80,8 @@ in case a specific other bundle is not registered:: // process the configuration of AcmeHelloExtension $configs = $container->getExtensionConfig($this->getAlias()); - // use the Configuration class to generate a config array with the settings "acme_hello" + // use the Configuration class to generate a config array with + // the settings "acme_hello" $config = $this->processConfiguration(new Configuration(), $configs); // check if entity_manager_name is set in the "acme_hello" configuration @@ -90,9 +92,9 @@ in case a specific other bundle is not registered:: } } -The above would be the equivalent of writing the following into the ``app/config/config.yml`` -in case ``AcmeGoodbyeBundle`` is not registered and the ``entity_manager_name`` setting -for ``acme_hello`` is set to ``non_default``: +The above would be the equivalent of writing the following into the +``app/config/config.yml`` in case AcmeGoodbyeBundle is not registered and the +``entity_manager_name`` setting for ``acme_hello`` is set to ``non_default``: .. configuration-block:: @@ -121,11 +123,11 @@ for ``acme_hello`` is set to ``non_default``: // app/config/config.php $container->loadFromExtension('acme_something', array( - ..., + // ... 'use_acme_goodbye' => false, 'entity_manager_name' => 'non_default', )); $container->loadFromExtension('acme_other', array( - ..., + // ... 'use_acme_goodbye' => false, )); diff --git a/cookbook/bundles/remove.rst b/cookbook/bundles/remove.rst index f289ecd144c..407ee421aa4 100644 --- a/cookbook/bundles/remove.rst +++ b/cookbook/bundles/remove.rst @@ -4,7 +4,7 @@ How to Remove the AcmeDemoBundle ================================ -The Symfony2 Standard Edition comes with a complete demo that lives inside a +The Symfony Standard Edition comes with a complete demo that lives inside a bundle called AcmeDemoBundle. It is a great boilerplate to refer to while starting a project, but you'll probably want to eventually remove it. diff --git a/cookbook/cache/form_csrf_caching.rst b/cookbook/cache/form_csrf_caching.rst new file mode 100644 index 00000000000..74d4c5854e7 --- /dev/null +++ b/cookbook/cache/form_csrf_caching.rst @@ -0,0 +1,42 @@ +.. index:: + single: Cache; CSRF; Forms + +Caching Pages that Contain CSRF Protected Forms +=============================================== + +CSRF tokens are meant to be different for every user. This is why you +need to be cautious if you try to cache pages with forms including them. + +For more information about how CSRF protection works in Symfony, please +check :ref:`CSRF Protection `. + +Why Caching Pages with a CSRF token is Problematic +-------------------------------------------------- + +Typically, each user is assigned a unique CSRF token, which is stored in +the session for validation. This means that if you *do* cache a page with +a form containing a CSRF token, you'll cache the CSRF token of the *first* +user only. When a user submits the form, the token won't match the token +stored in the session and all users (except for the first) will fail CSRF +validation when submitting the form. + +In fact, many reverse proxies (like Varnish) will refuse to cache a page +with a CSRF token. This is because a cookie is sent in order to preserve +the PHP session open and Varnish's default behaviour is to not cache HTTP +requests with cookies. + +How to Cache Most of the Page and still be able to Use CSRF Protection +---------------------------------------------------------------------- + +To cache a page that contains a CSRF token, you can use more advanced caching +techniques like :ref:`ESI fragments `, where you cache +the full page and embedding the form inside an ESI tag with no cache at all. + +Another option would be to load the form via an uncached AJAX request, but +cache the rest of the HTML response. + +Or you can even load just the CSRF token with an AJAX request and replace the +form field value with it. + +.. _`Cross-site request forgery`: http://en.wikipedia.org/wiki/Cross-site_request_forgery +.. _`Security CSRF Component`: https://github.com/symfony/security-csrf \ No newline at end of file diff --git a/cookbook/cache/index.rst b/cookbook/cache/index.rst index 567d418b750..00dcfda66b6 100644 --- a/cookbook/cache/index.rst +++ b/cookbook/cache/index.rst @@ -5,3 +5,4 @@ Cache :maxdepth: 2 varnish + form_csrf_caching diff --git a/cookbook/cache/varnish.rst b/cookbook/cache/varnish.rst index cb6ba5b8174..6e1ac0c32b7 100644 --- a/cookbook/cache/varnish.rst +++ b/cookbook/cache/varnish.rst @@ -7,27 +7,149 @@ How to Use Varnish to Speed up my Website Because Symfony's cache uses the standard HTTP cache headers, the :ref:`symfony-gateway-cache` can easily be replaced with any other reverse proxy. `Varnish`_ is a powerful, open-source, HTTP accelerator capable of serving -cached content quickly and including support for :ref:`Edge Side Includes `. +cached content fast and including support for :ref:`Edge Side Includes `. -Trusting Reverse Proxies ------------------------- +.. index:: + single: Varnish; configuration + +Make Symfony Trust the Reverse Proxy +------------------------------------ For ESI to work correctly and for the :ref:`X-FORWARDED ` headers to be used, you need to configure Varnish as a :doc:`trusted proxy `. -.. index:: - single: Varnish; configuration +.. _varnish-x-forwarded-headers: + +Routing and X-FORWARDED Headers +------------------------------- + +To ensure that the Symfony Router generates URLs correctly with Varnish, +a ``X-Forwarded-Port`` header must be present for Symfony to use the +correct port number. -Configuration -------------- +This port depends on your setup. Lets say that external connections come in +on the default HTTP port 80. For HTTPS connections, there is another proxy +(as Varnish does not do HTTPS itself) on the default HTTPS port 443 that +handles the SSL termination and forwards the requests as HTTP requests to +Varnish with a ``X-Forwarded-Proto`` header. In this case, you need to add +the following configuration snippet: -As seen previously, Symfony is smart enough to detect whether it talks to a -reverse proxy that understands ESI or not. It works out of the box when you -use the Symfony reverse proxy, but you need a special configuration to make -it work with Varnish. Thankfully, Symfony relies on yet another standard -written by Akamai (`Edge Architecture`_), so the configuration tips in this -chapter can be useful even if you don't use Symfony. +.. code-block:: varnish4 + + sub vcl_recv { + if (req.http.X-Forwarded-Proto == "https" ) { + set req.http.X-Forwarded-Port = "443"; + } else { + set req.http.X-Forwarded-Port = "80"; + } + } + +.. note:: + + Remember to configure :ref:`framework.trusted_proxies ` + in the Symfony configuration so that Varnish is seen as a trusted proxy + and the ``X-Forwarded-*`` headers are used. + + Varnish automatically forwards the IP as ``X-Forwarded-For`` and leaves + the ``X-Forwarded-Proto`` header in the request. If you do not configure + Varnish as trusted proxy, Symfony will see all requests as coming through + insecure HTTP connections from the Varnish host instead of the real client. + +If the ``X-Forwarded-Port`` header is not set correctly, Symfony will append +the port where the PHP application is running when generating absolute URLs, +e.g. ``http://example.com:8080/my/path``. + +Cookies and Caching +------------------- + +By default, a sane caching proxy does not cache anything when a request is sent +with :ref:`cookies or a basic authentication header`. +This is because the content of the page is supposed to depend on the cookie +value or authentication header. + +If you know for sure that the backend never uses sessions or basic +authentication, have varnish remove the corresponding header from requests to +prevent clients from bypassing the cache. In practice, you will need sessions +at least for some parts of the site, e.g. when using forms with +:ref:`CSRF Protection `. In this situation, make sure to only +start a session when actually needed, and clear the session when it is no +longer needed. Alternatively, you can look into :doc:`../cache/form_csrf_caching`. + +.. todo link "only start a session when actually needed" to cookbook/session/avoid_session_start once https://github.com/symfony/symfony-docs/pull/4661 is merged + +Cookies created in Javascript and used only in the frontend, e.g. when using +Google analytics are nonetheless sent to the server. These cookies are not +relevant for the backend and should not affect the caching decision. Configure +your Varnish cache to `clean the cookies header`_. You want to keep the +session cookie, if there is one, and get rid of all other cookies so that pages +are cached if there is no active session. Unless you changed the default +configuration of PHP, your session cookie has the name PHPSESSID: + +.. code-block:: varnish4 + + sub vcl_recv { + // Remove all cookies except the session ID. + if (req.http.Cookie) { + set req.http.Cookie = ";" + req.http.Cookie; + set req.http.Cookie = regsuball(req.http.Cookie, "; +", ";"); + set req.http.Cookie = regsuball(req.http.Cookie, ";(PHPSESSID)=", "; \1="); + set req.http.Cookie = regsuball(req.http.Cookie, ";[^ ][^;]*", ""); + set req.http.Cookie = regsuball(req.http.Cookie, "^[; ]+|[; ]+$", ""); + + if (req.http.Cookie == "") { + // If there are no more cookies, remove the header to get page cached. + remove req.http.Cookie; + } + } + } + +.. tip:: + + If content is not different for every user, but depends on the roles of a + user, a solution is to separate the cache per group. This pattern is + implemented and explained by the FOSHttpCacheBundle_ under the name + `User Context`_. + +Ensure Consistent Caching Behaviour +----------------------------------- + +Varnish uses the cache headers sent by your application to determine how +to cache content. However, versions prior to Varnish 4 did not respect +``Cache-Control: no-cache``, ``no-store`` and ``private``. To ensure +consistent behavior, use the following configuration if you are still +using Varnish 3: + +.. configuration-block:: + + .. code-block:: varnish3 + + sub vcl_fetch { + /* By default, Varnish3 ignores Cache-Control: no-cache and private + https://www.varnish-cache.org/docs/3.0/tutorial/increasing_your_hitrate.html#cache-control + */ + if (beresp.http.Cache-Control ~ "private" || + beresp.http.Cache-Control ~ "no-cache" || + beresp.http.Cache-Control ~ "no-store" + ) { + return (hit_for_pass); + } + } + +.. tip:: + + You can see the default behavior of Varnish in the form of a VCL file: + `default.vcl`_ for Varnish 3, `builtin.vcl`_ for Varnish 4. + +Enable Edge Side Includes (ESI) +------------------------------- + +As explained in the :ref:`Edge Side Includes section`, +Symfony detects whether it talks to a reverse proxy that understands ESI or +not. When you use the Symfony reverse proxy, you don't need to do anything. +But to make Varnish instead of Symfony resolve the ESI tags, you need some +configuration in Varnish. Symfony uses the ``Surrogate-Capability`` header +from the `Edge Architecture`_ described by Akamai. .. note:: @@ -38,7 +160,7 @@ First, configure Varnish so that it advertises its ESI support by adding a ``Surrogate-Capability`` header to requests forwarded to the backend application: -.. code-block:: text +.. code-block:: varnish4 sub vcl_recv { // Add a Surrogate-Capability header to announce ESI support. @@ -54,36 +176,33 @@ Then, optimize Varnish so that it only parses the Response contents when there is at least one ESI tag by checking the ``Surrogate-Control`` header that Symfony adds automatically: -.. code-block:: text +.. configuration-block:: - sub vcl_fetch { - /* - Check for ESI acknowledgement - and remove Surrogate-Control header - */ - if (beresp.http.Surrogate-Control ~ "ESI/1.0") { - unset beresp.http.Surrogate-Control; + .. code-block:: varnish4 - // For Varnish >= 3.0 - set beresp.do_esi = true; - // For Varnish < 3.0 - // esi; + sub vcl_backend_response { + // Check for ESI acknowledgement and remove Surrogate-Control header + if (beresp.http.Surrogate-Control ~ "ESI/1.0") { + unset beresp.http.Surrogate-Control; + set beresp.do_esi = true; + } } - /* By default Varnish ignores Cache-Control: nocache - (https://www.varnish-cache.org/docs/3.0/tutorial/increasing_your_hitrate.html#cache-control), - so in order avoid caching it has to be done explicitly */ - if (beresp.http.Pragma ~ "no-cache" || - beresp.http.Cache-Control ~ "no-cache" || - beresp.http.Cache-Control ~ "private") { - return (hit_for_pass); + + .. code-block:: varnish3 + + sub vcl_fetch { + // Check for ESI acknowledgement and remove Surrogate-Control header + if (beresp.http.Surrogate-Control ~ "ESI/1.0") { + unset beresp.http.Surrogate-Control; + set beresp.do_esi = true; + } } - } -.. caution:: +.. tip:: - Compression with ESI was not supported in Varnish until version 3.0 - (read `GZIP and Varnish`_). If you're not using Varnish 3.0, put a web - server in front of Varnish to perform the compression. + If you followed the advice about ensuring a consistent caching + behavior, those vcl functions already exist. Just append the code + to the end of the function, they won't interfere with each other. .. index:: single: Varnish; Invalidation @@ -102,143 +221,16 @@ proxy before it has expired, it adds complexity to your caching setup. invalidation by helping you to organize your caching and invalidation setup. -Varnish can be configured to accept a special HTTP ``PURGE`` method -that will invalidate the cache for a given resource: - -.. code-block:: text - - /* - Connect to the backend server - on the local machine on port 8080 - */ - backend default { - .host = "127.0.0.1"; - .port = "8080"; - } - - sub vcl_recv { - /* - Varnish default behavior doesn't support PURGE. - Match the PURGE request and immediately do a cache lookup, - otherwise Varnish will directly pipe the request to the backend - and bypass the cache - */ - if (req.request == "PURGE") { - return(lookup); - } - } - - sub vcl_hit { - // Match PURGE request - if (req.request == "PURGE") { - // Force object expiration for Varnish < 3.0 - set obj.ttl = 0s; - // Do an actual purge for Varnish >= 3.0 - // purge; - error 200 "Purged"; - } - } - - sub vcl_miss { - /* - Match the PURGE request and - indicate the request wasn't stored in cache. - */ - if (req.request == "PURGE") { - error 404 "Not purged"; - } - } - -.. caution:: - - You must protect the ``PURGE`` HTTP method somehow to avoid random people - purging your cached data. You can do this by setting up an access list: - - .. code-block:: text - - /* - Connect to the backend server - on the local machine on port 8080 - */ - backend default { - .host = "127.0.0.1"; - .port = "8080"; - } - - // ACL's can contain IP's, subnets and hostnames - acl purge { - "localhost"; - "192.168.55.0"/24; - } - - sub vcl_recv { - // Match PURGE request to avoid cache bypassing - if (req.request == "PURGE") { - // Match client IP to the ACL - if (!client.ip ~ purge) { - // Deny access - error 405 "Not allowed."; - } - // Perform a cache lookup - return(lookup); - } - } - - sub vcl_hit { - // Match PURGE request - if (req.request == "PURGE") { - // Force object expiration for Varnish < 3.0 - set obj.ttl = 0s; - // Do an actual purge for Varnish >= 3.0 - // purge; - error 200 "Purged"; - } - } - - sub vcl_miss { - // Match PURGE request - if (req.request == "PURGE") { - // Indicate that the object isn't stored in cache - error 404 "Not purged"; - } - } - -.. _varnish-x-forwarded-headers: - -Routing and X-FORWARDED Headers -------------------------------- - -To ensure that the Symfony Router generates URLs correctly with Varnish, -proper ```X-Forwarded``` headers must be added so that Symfony is aware of -the original port number of the request. Exactly how this is done depends -on your setup. As a simple example, Varnish and your web server are on the -same machine and that Varnish is listening on one port (e.g. 80) and Apache -on another (e.g. 8080). In this situation, Varnish should add the ``X-Forwarded-Port`` -header so that the Symfony application knows that the original port number -is 80 and not 8080. - -If this header weren't set properly, Symfony may append ``8080`` when generating -absolute URLs: - -.. code-block:: text - - sub vcl_recv { - if (req.http.X-Forwarded-Proto == "https" ) { - set req.http.X-Forwarded-Port = "443"; - } else { - set req.http.X-Forwarded-Port = "80"; - } - } - -.. note:: - - Remember to configure :ref:`framework.trusted_proxies ` - in the Symfony configuration so that Varnish is seen as a trusted proxy - and the ``X-Forwarded-`` headers are used. + The documentation of the `FOSHttpCacheBundle`_ explains how to configure + Varnish and other reverse proxies for cache invalidation. .. _`Varnish`: https://www.varnish-cache.org .. _`Edge Architecture`: http://www.w3.org/TR/edge-arch .. _`GZIP and Varnish`: https://www.varnish-cache.org/docs/3.0/phk/gzip.html +.. _`Clean the cookies header`: https://www.varnish-cache.org/trac/wiki/VCLExampleRemovingSomeCookies .. _`Surrogate-Capability Header`: http://www.w3.org/TR/edge-arch .. _`cache invalidation`: http://tools.ietf.org/html/rfc2616#section-13.10 .. _`FOSHttpCacheBundle`: http://foshttpcachebundle.readthedocs.org/ +.. _`default.vcl`: https://www.varnish-cache.org/trac/browser/bin/varnishd/default.vcl?rev=3.0 +.. _`builtin.vcl`: https://www.varnish-cache.org/trac/browser/bin/varnishd/builtin.vcl?rev=4.0 +.. _`User Context`: http://foshttpcachebundle.readthedocs.org/en/latest/features/user-context.html diff --git a/cookbook/composer.rst b/cookbook/composer.rst new file mode 100644 index 00000000000..d57624c4a29 --- /dev/null +++ b/cookbook/composer.rst @@ -0,0 +1,44 @@ +.. index:: + double: Composer; Installation + +Installing Composer +=================== + +`Composer`_ is the package manager used by modern PHP applications and the +recommended way to install Symfony2. + +Install Composer on Linux and Mac OS X +-------------------------------------- + +To install Composer on Linux or Mac OS X, execute the following two commands: + +.. code-block:: bash + + $ curl -sS https://getcomposer.org/installer | php + $ sudo mv composer.phar /usr/local/bin/composer + +.. note:: + + If you don't have ``curl`` installed, you can also just download the + ``installer`` file manually at http://getcomposer.org/installer and + then run: + + .. code-block:: bash + + $ php installer + $ sudo mv composer.phar /usr/local/bin/composer + +Install Composer on Windows +--------------------------- + +Download the installer from `getcomposer.org/download`_, execute it and follow +the instructions. + +Learn more +---------- + +You can read more about Composer in `its documentation`_. + +.. _`Composer`: https://getcomposer.org/ +.. _`getcomposer.org/download`: https://getcomposer.org/download +.. _`its documentation`: https://getcomposer.org/doc/00-intro.md diff --git a/cookbook/configuration/apache_router.rst b/cookbook/configuration/apache_router.rst index 90f652992f4..f77b3d6e7e9 100644 --- a/cookbook/configuration/apache_router.rst +++ b/cookbook/configuration/apache_router.rst @@ -7,6 +7,13 @@ How to Use the Apache Router Symfony, while fast out of the box, also provides various ways to increase that speed with a little bit of tweaking. One of these ways is by letting Apache handle routes directly, rather than using Symfony for this task. +.. caution:: + + Apache router was deprecated in Symfony 2.5 and will be removed in Symfony + 3.0. Since the PHP implementation of the Router was improved, performance + gains were no longer significant (while it's very hard to replicate the + same behavior). + Change Router Configuration Parameters -------------------------------------- @@ -52,7 +59,7 @@ Symfony to use the ``ApacheUrlMatcher`` instead of the default one: Generating mod_rewrite Rules ---------------------------- -To test that it's working, create a very basic route for the AcmeDemoBundle: +To test that it's working, create a very basic route for the AppBundle: .. configuration-block:: @@ -61,20 +68,20 @@ To test that it's working, create a very basic route for the AcmeDemoBundle: # app/config/routing.yml hello: path: /hello/{name} - defaults: { _controller: AcmeDemoBundle:Demo:hello } + defaults: { _controller: AppBundle:Demo:hello } .. code-block:: xml - AcmeDemoBundle:Demo:hello + AppBundle:Demo:hello .. code-block:: php // app/config/routing.php $collection->add('hello', new Route('/hello/{name}', array( - '_controller' => 'AcmeDemoBundle:Demo:hello', + '_controller' => 'AppBundle:Demo:hello', ))); Now generate the mod_rewrite rules: @@ -93,7 +100,7 @@ Which should roughly output the following: # hello RewriteCond %{REQUEST_URI} ^/hello/([^/]+?)$ - RewriteRule .* app.php [QSA,L,E=_ROUTING__route:hello,E=_ROUTING_name:%1,E=_ROUTING__controller:AcmeDemoBundle\:Demo\:hello] + RewriteRule .* app.php [QSA,L,E=_ROUTING__route:hello,E=_ROUTING_name:%1,E=_ROUTING__controller:AppBundle\:Demo\:hello] You can now rewrite ``web/.htaccess`` to use the new rules, so with this example it should look like this: @@ -109,7 +116,7 @@ it should look like this: # hello RewriteCond %{REQUEST_URI} ^/hello/([^/]+?)$ - RewriteRule .* app.php [QSA,L,E=_ROUTING__route:hello,E=_ROUTING_name:%1,E=_ROUTING__controller:AcmeDemoBundle\:Demo\:hello] + RewriteRule .* app.php [QSA,L,E=_ROUTING__route:hello,E=_ROUTING_name:%1,E=_ROUTING__controller:AppBundle\:Demo\:hello] .. note:: diff --git a/cookbook/configuration/configuration_organization.rst b/cookbook/configuration/configuration_organization.rst index 63bcd9704f0..62311e7c515 100644 --- a/cookbook/configuration/configuration_organization.rst +++ b/cookbook/configuration/configuration_organization.rst @@ -127,8 +127,10 @@ needed for the ``app/config/dev/config.yml`` file: + xsi:schemaLocation="http://symfony.com/schema/dic/services + http://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/symfony + http://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> @@ -237,8 +239,10 @@ format (``.yml``, ``.xml``, ``.php``, ``.ini``): + xsi:schemaLocation="http://symfony.com/schema/dic/services + http://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/symfony + http://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> @@ -299,8 +303,10 @@ any other configuration file: + xsi:schemaLocation="http://symfony.com/schema/dic/services + http://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/symfony + http://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> @@ -340,8 +346,10 @@ doesn't exist: + xsi:schemaLocation="http://symfony.com/schema/dic/services + http://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/symfony + http://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> diff --git a/cookbook/configuration/environments.rst b/cookbook/configuration/environments.rst index c3fd907859f..751f5971ca1 100644 --- a/cookbook/configuration/environments.rst +++ b/cookbook/configuration/environments.rst @@ -107,9 +107,7 @@ activated by modifying the default value in the ``dev`` configuration file: - + .. code-block:: php @@ -212,6 +210,39 @@ environment by using this code and changing the environment string. mode. You'll need to enable that in your front controller by calling :method:`Symfony\\Component\\Debug\\Debug::enable`. +Selecting the Environment for Console Commands +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +By default, Symfony commands are executed in the ``dev`` environment and with the +debug mode enabled. Use the ``--env`` and ``--no-debug`` options to modify this +behavior: + +.. code-block:: bash + + # 'dev' environment and debug enabled + $ php app/console command_name + + # 'prod' environment (debug is always disabled for 'prod') + $ php app/console command_name --env=prod + + # 'test' environment and debug disabled + $ php app/console command_name --env=test --no-debug + +In addition to the ``--env`` and ``--debug`` options, the behavior of Symfony +commands can also be controlled with environment variables. The Symfony console +application checks the existence and value of these environment variables before +executing any command: + +``SYMFONY_ENV`` + Sets the execution environment of the command to the value of this variable + (``dev``, ``prod``, ``test``, etc.); +``SYMFONY_DEBUG`` + If ``0``, debug mode is disabled. Otherwise, debug mode is enabled. + +These environment variables are very useful for production servers because they +allow you to ensure that commands always run in the ``prod`` environment without +having to add any command option. + .. index:: single: Environments; Creating a new environment @@ -276,7 +307,7 @@ should also create a front controller for it. Copy the ``web/app.php`` file to ``web/app_benchmark.php`` and edit the environment to be ``benchmark``:: // web/app_benchmark.php - + // ... // change just this line $kernel = new AppKernel('benchmark', false); diff --git a/cookbook/configuration/external_parameters.rst b/cookbook/configuration/external_parameters.rst index 0f9c56501c5..603164b8c93 100644 --- a/cookbook/configuration/external_parameters.rst +++ b/cookbook/configuration/external_parameters.rst @@ -14,9 +14,13 @@ Environment Variables --------------------- Symfony will grab any environment variable prefixed with ``SYMFONY__`` and -set it as a parameter in the service container. Double underscores are replaced -with a period, as a period is not a valid character in an environment variable -name. +set it as a parameter in the service container. Some transformations are +applied to the resulting parameter name: + +* ``SYMFONY__`` prefix is removed; +* Parameter name is lowercased; +* Double underscores are replaced with a period, as a period is not + a valid character in an environment variable name. For example, if you're using Apache, environment variables can be set using the following ``VirtualHost`` configuration: diff --git a/cookbook/configuration/override_dir_structure.rst b/cookbook/configuration/override_dir_structure.rst index 957438871a4..c2e341ccc03 100644 --- a/cookbook/configuration/override_dir_structure.rst +++ b/cookbook/configuration/override_dir_structure.rst @@ -151,6 +151,41 @@ file: work: .. code-block:: bash - + $ php app/console cache:clear --env=prod $ php app/console assetic:dump --env=prod --no-debug + +Override the ``vendor`` Directory +--------------------------------- + +To override the ``vendor`` directory, you need to introduce changes in the +following files: + +* ``app/autoload.php`` +* ``composer.json`` + +The change in the ``composer.json`` will look like this: + +.. code-block:: json + + { + ... + "config": { + "bin-dir": "bin", + "vendor-dir": "/some/dir/vendor" + }, + ... + } + +In ``app/autoload.php``, you need to modify the path leading to the ``vendor/autoload.php`` +file:: + + // app/autoload.php + // ... + $loader = require '/some/dir/vendor/autoload.php'; + +.. tip:: + + This modification can be of interest if you are working in a virtual environment + and cannot use NFS - for example, if you're running a Symfony app using + Vagrant/VirtualBox in a guest operating system. diff --git a/cookbook/configuration/pdo_session_storage.rst b/cookbook/configuration/pdo_session_storage.rst index be5e606d2ef..b94f5f34e3c 100644 --- a/cookbook/configuration/pdo_session_storage.rst +++ b/cookbook/configuration/pdo_session_storage.rst @@ -4,6 +4,12 @@ How to Use PdoSessionHandler to Store Sessions in the Database ============================================================== +.. caution:: + + There was a backwards-compatibility break in Symfony 2.6: the database + schema changed slightly. See :ref:`Symfony 2.6 Changes ` + for details. + The default Symfony session storage writes the session information to file(s). Most medium to large websites use a database to store the session values instead of files, because databases are easier to use and scale in a @@ -24,17 +30,11 @@ configuration format of your choice): # ... handler_id: session.handler.pdo - parameters: - pdo.db_options: - db_table: session - db_id_col: session_id - db_data_col: session_value - db_time_col: session_time - services: pdo: class: PDO arguments: + # see below for how to use your existing DB config dsn: "mysql:dbname=mydatabase" user: myuser password: mypassword @@ -43,7 +43,7 @@ configuration format of your choice): session.handler.pdo: class: Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler - arguments: ["@pdo", "%pdo.db_options%"] + arguments: ["@pdo"] .. code-block:: xml @@ -52,15 +52,6 @@ configuration format of your choice): - - - session - session_id - session_value - session_time - - - mysql:dbname=mydatabase @@ -74,7 +65,6 @@ configuration format of your choice): - %pdo.db_options% @@ -92,13 +82,6 @@ configuration format of your choice): ), )); - $container->setParameter('pdo.db_options', array( - 'db_table' => 'session', - 'db_id_col' => 'session_id', - 'db_data_col' => 'session_value', - 'db_time_col' => 'session_time', - )); - $pdoDefinition = new Definition('PDO', array( 'mysql:dbname=mydatabase', 'myuser', @@ -109,14 +92,74 @@ configuration format of your choice): $storageDefinition = new Definition('Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler', array( new Reference('pdo'), - '%pdo.db_options%', )); $container->setDefinition('session.handler.pdo', $storageDefinition); -* ``db_table``: The name of the session table in your database -* ``db_id_col``: The name of the id column in your session table (VARCHAR(255) or larger) -* ``db_data_col``: The name of the value column in your session table (TEXT or CLOB) -* ``db_time_col``: The name of the time column in your session table (INTEGER) +Configuring the Table and Column Names +-------------------------------------- + +This will expect a ``sessions`` table with a number of different columns. +The table name, and all of the column names, can be configured by passing +a second array argument to ``PdoSessionHandler``: + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/config.yml + services: + # ... + session.handler.pdo: + class: Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler + arguments: + - "@pdo" + - { 'db_table': 'sessions'} + + .. code-block:: xml + + + + + + + sessions + + + + + .. code-block:: php + + // app/config/config.php + + use Symfony\Component\DependencyInjection\Definition; + // ... + + $storageDefinition = new Definition( + 'Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler', + array( + new Reference('pdo'), + array('db_table' => 'session') + ) + ); + $container->setDefinition('session.handler.pdo', $storageDefinition); + +.. versionadded:: 2.6 + The ``db_lifetime_col`` was introduced in Symfony 2.6. Prior to 2.6, + this column did not exist. + +The following things can be configured: + +* ``db_table``: (default ``sessions``) The name of the session table in your + database; +* ``db_id_col``: (default ``sess_id``) The name of the id column in your + session table (VARCHAR(128)); +* ``db_data_col``: (default ``sess_data``) The name of the value column in + your session table (BLOB); +* ``db_time_col``: (default ``sess_time``) The name of the time column in + your session table (INTEGER); +* ``db_lifetime_col``: (default ``sess_lifetime``) The name of the lifetime + column in your session table (INTEGER). Sharing your Database Connection Information -------------------------------------------- @@ -133,12 +176,13 @@ of your project's data, you can use the connection settings from the .. code-block:: yaml - pdo: - class: PDO - arguments: - - "mysql:host=%database_host%;port=%database_port%;dbname=%database_name%" - - "%database_user%" - - "%database_password%" + services: + pdo: + class: PDO + arguments: + - "mysql:host=%database_host%;port=%database_port%;dbname=%database_name%" + - "%database_user%" + - "%database_password%" .. code-block:: xml @@ -159,6 +203,24 @@ of your project's data, you can use the connection settings from the Example SQL Statements ---------------------- +.. _pdo-session-handle-26-changes: + +.. sidebar:: Schema Changes needed when Upgrading to Symfony 2.6 + + If you use the ``PdoSessionHandler`` prior to Symfony 2.6 and upgrade, you'll + need to make a few changes to your session table: + + * A new session lifetime (``sess_lifetime`` by default) integer column + needs to be added; + * The data column (``sess_data`` by default) needs to be changed to a + BLOB type. + + Check the SQL statements below for more details. + + To keep the old (2.5 and earlier) functionality, change your class name + to use ``LegacyPdoSessionHandler`` instead of ``PdoSessionHandler`` (the + legacy class was added in Symfony 2.6.2). + MySQL ~~~~~ @@ -167,12 +229,12 @@ following (MySQL): .. code-block:: sql - CREATE TABLE `session` ( - `session_id` varchar(255) NOT NULL, - `session_value` text NOT NULL, - `session_time` int(11) NOT NULL, - PRIMARY KEY (`session_id`) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8; + CREATE TABLE `sessions` ( + `sess_id` VARBINARY(128) NOT NULL PRIMARY KEY, + `sess_data` BLOB NOT NULL, + `sess_time` INTEGER UNSIGNED NOT NULL, + `sess_lifetime` MEDIUMINT NOT NULL + ) COLLATE utf8_bin, ENGINE = InnoDB; PostgreSQL ~~~~~~~~~~ @@ -181,11 +243,11 @@ For PostgreSQL, the statement should look like this: .. code-block:: sql - CREATE TABLE session ( - session_id character varying(255) NOT NULL, - session_value text NOT NULL, - session_time integer NOT NULL, - CONSTRAINT session_pkey PRIMARY KEY (session_id) + CREATE TABLE sessions ( + sess_id VARCHAR(128) NOT NULL PRIMARY KEY, + sess_data BYTEA NOT NULL, + sess_time INTEGER NOT NULL, + sess_lifetime INTEGER NOT NULL ); Microsoft SQL Server @@ -195,12 +257,13 @@ For MSSQL, the statement might look like the following: .. code-block:: sql - CREATE TABLE [dbo].[session]( - [session_id] [nvarchar](255) NOT NULL, - [session_value] [ntext] NOT NULL, - [session_time] [int] NOT NULL, + CREATE TABLE [dbo].[sessions]( + [sess_id] [nvarchar](255) NOT NULL, + [sess_data] [ntext] NOT NULL, + [sess_time] [int] NOT NULL, + [sess_lifetime] [int] NOT NULL, PRIMARY KEY CLUSTERED( - [session_id] ASC + [sess_id] ASC ) WITH ( PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, diff --git a/cookbook/configuration/web_server_configuration.rst b/cookbook/configuration/web_server_configuration.rst index e93e873307c..f246e24ee26 100644 --- a/cookbook/configuration/web_server_configuration.rst +++ b/cookbook/configuration/web_server_configuration.rst @@ -52,6 +52,11 @@ are: CustomLog /var/log/apache2/project_access.log combined +.. note:: + + If your system supports the ``APACHE_LOG_DIR`` variable, you may want + to use ``${APACHE_LOG_DIR}/`` instead of ``/var/log/apache2/``. + .. note:: For performance reasons, you will probably want to set @@ -89,7 +94,7 @@ the FastCGI process manager ``php-fpm`` binary and Apache's FastCGI module installed (for example, on a Debian based system you have to install the ``libapache2-mod-fastcgi`` and ``php5-fpm`` packages). -PHP-FPM uses so called *pools* to handle incoming FastCGI requests. You can +PHP-FPM uses so-called *pools* to handle incoming FastCGI requests. You can configure an arbitrary number of pools in the FPM configuration. In a pool you configure either a TCP socket (IP and port) or a unix domain socket to listen on. Each pool can also be run under a different UID and GID: @@ -113,7 +118,7 @@ Using mod_proxy_fcgi with Apache 2.4 If you are running Apache 2.4, you can easily use ``mod_proxy_fcgi`` to pass incoming requests to PHP-FPM. Configure PHP-FPM to listen on a TCP socket (``mod_proxy`` currently `does not support unix sockets`_), enable ``mod_proxy`` -and ``mod_proxy_fcgi`` in your Apache configuration and use the ``ProxyPassMatch`` +and ``mod_proxy_fcgi`` in your Apache configuration and use the ``SetHandler`` directive to pass requests for PHP files to PHP FPM: .. code-block:: apache @@ -122,7 +127,22 @@ directive to pass requests for PHP files to PHP FPM: ServerName domain.tld ServerAlias www.domain.tld - ProxyPassMatch ^/(.*\.php(/.*)?)$ fcgi://127.0.0.1:9000/var/www/project/web/$1 + # Uncomment the following line to force Apache to pass the Authorization + # header to PHP: required for "basic_auth" under PHP-FPM and FastCGI + # + # SetEnvIfNoCase ^Authorization$ "(.+)" HTTP_AUTHORIZATION=$1 + + # For Apache 2.4.9 or higher + # Using SetHandler avoids issues with using ProxyPassMatch in combination + # with mod_rewrite or mod_autoindex + + SetHandler proxy:fcgi://127.0.0.1:9000 + + # If you use Apache version below 2.4.9 you must consider update or use this instead + # ProxyPassMatch ^/(.*\.php(/.*)?)$ fcgi://127.0.0.1:9000/var/www/project/web/$1 + # If you run your Symfony application on a subpath of your document root, the + # regular expression must be changed accordingly: + # ProxyPassMatch ^/path-to-app/(.*\.php(/.*)?)$ fcgi://127.0.0.1:9000/var/www/project/web/$1 DocumentRoot /var/www/project/web @@ -135,16 +155,6 @@ directive to pass requests for PHP files to PHP FPM: CustomLog /var/log/apache2/project_access.log combined -.. caution:: - - When you run your Symfony application on a subpath of your document root, - the regular expression used in ``ProxyPassMatch`` directive must be changed - accordingly: - - .. code-block:: apache - - ProxyPassMatch ^/path-to-app/(.*\.php(/.*)?)$ fcgi://127.0.0.1:9000/var/www/project/web/$1 - PHP-FPM with Apache 2.2 ~~~~~~~~~~~~~~~~~~~~~~~ @@ -201,13 +211,27 @@ are: # try to serve file directly, fallback to app.php try_files $uri /app.php$is_args$args; } - - location ~ ^/(app|app_dev|config)\.php(/|$) { + # DEV + # This rule should only be placed on your development environment + # In production, don't include this and don't deploy app_dev.php or config.php + location ~ ^/(app_dev|config)\.php(/|$) { + fastcgi_pass unix:/var/run/php5-fpm.sock; + fastcgi_split_path_info ^(.+\.php)(/.*)$; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param HTTPS off; + } + # PROD + location ~ ^/app\.php(/|$) { fastcgi_pass unix:/var/run/php5-fpm.sock; fastcgi_split_path_info ^(.+\.php)(/.*)$; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param HTTPS off; + # Prevents URIs that include the front controller. This will 404: + # http://domain.tld/app.php/some-path + # Remove the internal directive to allow URIs like this + internal; } error_log /var/log/nginx/project_error.log; diff --git a/cookbook/console/commands_as_services.rst b/cookbook/console/commands_as_services.rst index a60ebaa11e7..f31219d2fde 100644 --- a/cookbook/console/commands_as_services.rst +++ b/cookbook/console/commands_as_services.rst @@ -4,10 +4,6 @@ How to Define Commands as Services ================================== -.. versionadded:: 2.4 - Support for registering commands in the service container was introduced in - Symfony 2.4. - By default, Symfony will take a look in the ``Command`` directory of each bundle and automatically register your commands. If a command extends the :class:`Symfony\\Bundle\\FrameworkBundle\\Command\\ContainerAwareCommand`, diff --git a/cookbook/console/console_command.rst b/cookbook/console/console_command.rst index 23f2ad2b494..d0b651fc6d8 100644 --- a/cookbook/console/console_command.rst +++ b/cookbook/console/console_command.rst @@ -14,11 +14,11 @@ Automatically Registering Commands To make the console commands available automatically with Symfony, create a ``Command`` directory inside your bundle and create a PHP file suffixed with ``Command.php`` for each command that you want to provide. For example, if you -want to extend the AcmeDemoBundle to greet you from the command line, create +want to extend the AppBundle to greet you from the command line, create ``GreetCommand.php`` and add the following to it:: - // src/Acme/DemoBundle/Command/GreetCommand.php - namespace Acme\DemoBundle\Command; + // src/AppBundle/Command/GreetCommand.php + namespace AppBundle\Command; use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; use Symfony\Component\Console\Input\InputArgument; @@ -33,8 +33,17 @@ want to extend the AcmeDemoBundle to greet you from the command line, create $this ->setName('demo:greet') ->setDescription('Greet someone') - ->addArgument('name', InputArgument::OPTIONAL, 'Who do you want to greet?') - ->addOption('yell', null, InputOption::VALUE_NONE, 'If set, the task will yell in uppercase letters') + ->addArgument( + 'name', + InputArgument::OPTIONAL, + 'Who do you want to greet?' + ) + ->addOption( + 'yell', + null, + InputOption::VALUE_NONE, + 'If set, the task will yell in uppercase letters' + ) ; } @@ -59,7 +68,7 @@ This command will now automatically be available to run: .. code-block:: bash - $ app/console demo:greet Fabien + $ php app/console demo:greet Fabien .. _cookbook-console-dic: @@ -87,7 +96,7 @@ service container. In other words, you have access to any configured service:: // ... } -However, due to the `container scopes `_ this +However, due to the :doc:`container scopes ` this code doesn't work for some services. For instance, if you try to get the ``request`` service or any other service related to it, you'll get the following error: @@ -103,7 +112,9 @@ translate some contents using a console command:: $name = $input->getArgument('name'); $translator = $this->getContainer()->get('translator'); if ($name) { - $output->writeln($translator->trans('Hello %name%!', array('%name%' => $name))); + $output->writeln( + $translator->trans('Hello %name%!', array('%name%' => $name)) + ); } else { $output->writeln($translator->trans('Hello!')); } @@ -137,7 +148,9 @@ before translating contents:: $translator->setLocale($locale); if ($name) { - $output->writeln($translator->trans('Hello %name%!', array('%name%' => $name))); + $output->writeln( + $translator->trans('Hello %name%!', array('%name%' => $name)) + ); } else { $output->writeln($translator->trans('Hello!')); } @@ -156,7 +169,7 @@ instead of use Symfony\Component\Console\Tester\CommandTester; use Symfony\Bundle\FrameworkBundle\Console\Application; - use Acme\DemoBundle\Command\GreetCommand; + use AppBundle\Command\GreetCommand; class ListCommandTest extends \PHPUnit_Framework_TestCase { @@ -181,11 +194,6 @@ instead of } } -.. versionadded:: 2.4 - Since Symfony 2.4, the ``CommandTester`` automatically detects the name of - the command to execute. Prior to Symfony 2.4, you need to pass it via the - ``command`` key. - .. note:: In the specific case above, the ``name`` parameter and the ``--yell`` option @@ -194,14 +202,14 @@ instead of To be able to use the fully set up service container for your console tests you can extend your test from -:class:`Symfony\\Bundle\\FrameworkBundle\\Test\\WebTestCase`:: +:class:`Symfony\\Bundle\\FrameworkBundle\\Test\\KernelTestCase`:: use Symfony\Component\Console\Tester\CommandTester; use Symfony\Bundle\FrameworkBundle\Console\Application; - use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; - use Acme\DemoBundle\Command\GreetCommand; + use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; + use AppBundle\Command\GreetCommand; - class ListCommandTest extends WebTestCase + class ListCommandTest extends KernelTestCase { public function testExecute() { @@ -225,3 +233,13 @@ you can extend your test from // ... } } + +.. versionadded:: 2.5 + :class:`Symfony\\Bundle\\FrameworkBundle\\Test\\KernelTestCase` was + extracted from :class:`Symfony\\Bundle\\FrameworkBundle\\Test\\WebTestCase` + in Symfony 2.5. ``WebTestCase`` inherits from ``KernelTestCase``. The + ``WebTestCase`` creates an instance of + :class:`Symfony\\Bundle\\FrameworkBundle\\Client` via ``createClient()``, + while ``KernelTestCase`` creates an instance of + :class:`Symfony\\Component\\HttpKernel\\KernelInterface` via + ``createKernel()``. diff --git a/cookbook/console/logging.rst b/cookbook/console/logging.rst index 133021ccf7a..3bed28d5e7a 100644 --- a/cookbook/console/logging.rst +++ b/cookbook/console/logging.rst @@ -26,8 +26,8 @@ extends :class:`Symfony\\Bundle\\FrameworkBundle\\Command\\ContainerAwareCommand This means that you can simply access the standard logger service through the container and use it to do the logging:: - // src/Acme/DemoBundle/Command/GreetCommand.php - namespace Acme\DemoBundle\Command; + // src/AppBundle/Command/GreetCommand.php + namespace AppBundle\Command; use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; use Symfony\Component\Console\Input\InputArgument; @@ -84,7 +84,7 @@ First configure a listener for console exception events in the service container # app/config/services.yml services: kernel.listener.command_dispatch: - class: Acme\DemoBundle\EventListener\ConsoleExceptionListener + class: AppBundle\EventListener\ConsoleExceptionListener arguments: logger: "@logger" tags: @@ -98,12 +98,8 @@ First configure a listener for console exception events in the service container xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> - - Acme\DemoBundle\EventListener\ConsoleExceptionListener - - - + @@ -116,12 +112,8 @@ First configure a listener for console exception events in the service container use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; - $container->setParameter( - 'console_exception_listener.class', - 'Acme\DemoBundle\EventListener\ConsoleExceptionListener' - ); $definitionConsoleExceptionListener = new Definition( - '%console_exception_listener.class%', + 'AppBundle\EventListener\ConsoleExceptionListener', array(new Reference('logger')) ); $definitionConsoleExceptionListener->addTag( @@ -135,8 +127,8 @@ First configure a listener for console exception events in the service container Then implement the actual listener:: - // src/Acme/DemoBundle/EventListener/ConsoleExceptionListener.php - namespace Acme\DemoBundle\EventListener; + // src/AppBundle/EventListener/ConsoleExceptionListener.php + namespace AppBundle\EventListener; use Symfony\Component\Console\Event\ConsoleExceptionEvent; use Psr\Log\LoggerInterface; @@ -164,7 +156,7 @@ Then implement the actual listener:: $command->getName() ); - $this->logger->error($message); + $this->logger->error($message, array('exception' => $exception)); } } @@ -190,7 +182,7 @@ First configure a listener for console terminate events in the service container # app/config/services.yml services: kernel.listener.command_dispatch: - class: Acme\DemoBundle\EventListener\ConsoleTerminateListener + class: AppBundle\EventListener\ErrorLoggerListener arguments: logger: "@logger" tags: @@ -204,12 +196,8 @@ First configure a listener for console terminate events in the service container xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> - - Acme\DemoBundle\EventListener\ConsoleExceptionListener - - - + @@ -222,32 +210,28 @@ First configure a listener for console terminate events in the service container use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; - $container->setParameter( - 'console_exception_listener.class', - 'Acme\DemoBundle\EventListener\ConsoleExceptionListener' - ); - $definitionConsoleExceptionListener = new Definition( - '%console_exception_listener.class%', + $definitionErrorLoggerListener = new Definition( + 'AppBundle\EventListener\ErrorLoggerListener', array(new Reference('logger')) ); - $definitionConsoleExceptionListener->addTag( + $definitionErrorLoggerListener->addTag( 'kernel.event_listener', array('event' => 'console.terminate') ); $container->setDefinition( 'kernel.listener.command_dispatch', - $definitionConsoleExceptionListener + $definitionErrorLoggerListener ); Then implement the actual listener:: - // src/Acme/DemoBundle/EventListener/ConsoleExceptionListener.php - namespace Acme\DemoBundle\EventListener; + // src/AppBundle/EventListener/ErrorLoggerListener.php + namespace AppBundle\EventListener; use Symfony\Component\Console\Event\ConsoleTerminateEvent; use Psr\Log\LoggerInterface; - class ConsoleExceptionListener + class ErrorLoggerListener { private $logger; diff --git a/cookbook/console/sending_emails.rst b/cookbook/console/sending_emails.rst index 2ecd95e4104..74a04bf8255 100644 --- a/cookbook/console/sending_emails.rst +++ b/cookbook/console/sending_emails.rst @@ -17,16 +17,13 @@ what URL it should use when generating URLs. There are two ways of configuring the request context: at the application level and per Command. -Configuring the Request Context globally +Configuring the Request Context Globally ---------------------------------------- -.. versionadded:: 2.2 - The ``base_url`` parameter was introduced in Symfony 2.2. - To configure the Request Context - which is used by the URL Generator - you can redefine the parameters it uses as default values to change the default host -(localhost) and scheme (http). Starting with Symfony 2.2 you can also configure -the base path if Symfony is not running in the root directory. +(localhost) and scheme (http). You can also configure the base path if Symfony +is not running in the root directory. Note that this does not impact URLs generated via normal web requests, since those will override the defaults. @@ -69,7 +66,7 @@ Configuring the Request Context per Command To change it only in one command you can simply fetch the Request Context from the ``router`` service and override its settings:: - // src/Acme/DemoBundle/Command/DemoCommand.php + // src/AppBundle/Command/DemoCommand.php // ... class DemoCommand extends ContainerAwareCommand diff --git a/cookbook/controller/error_pages.rst b/cookbook/controller/error_pages.rst index 00cb1b420f8..9306ed49c43 100644 --- a/cookbook/controller/error_pages.rst +++ b/cookbook/controller/error_pages.rst @@ -5,46 +5,92 @@ How to Customize Error Pages ============================ -When any exception is thrown in Symfony, the exception is caught inside the -``Kernel`` class and eventually forwarded to a special controller, -``TwigBundle:Exception:show`` for handling. This controller, which lives -inside the core TwigBundle, determines which error template to display and -the status code that should be set for the given exception. +When an exception is thrown, the core ``HttpKernel`` class catches it and +dispatches a ``kernel.exception`` event. This gives you the power to convert +the exception into a ``Response`` in a few different ways. -Error pages can be customized in two different ways, depending on how much -control you need: +The core TwigBundle sets up a listener for this event which will run +a configurable (but otherwise arbitrary) controller to generate the +response. The default controller used has a sensible way of +picking one out of the available set of error templates. -1. Customize the error templates of the different error pages; +Thus, error pages can be customized in different ways, depending on how +much control you need: -2. Replace the default exception controller ``twig.controller.exception:showAction``. +#. :ref:`Use the default ExceptionController and create a few + templates that allow you to customize how your different error + pages look (easy); ` -The default ExceptionController -------------------------------- +#. :ref:`Replace the default exception controller with your own + (intermediate). ` -The default ``ExceptionController`` will either display an +#. :ref:`Use the kernel.exception event to come up with your own + handling (advanced). ` + +.. _use-default-exception-controller: + +Using the Default ExceptionController +------------------------------------- + +By default, the ``showAction()`` method of the +:class:`Symfony\\Bundle\\TwigBundle\\Controller\\ExceptionController` +will be called when an exception occurs. + +This controller will either display an *exception* or *error* page, depending on the setting of the ``kernel.debug`` flag. While *exception* pages give you a lot of helpful information during development, *error* pages are meant to be -shown to the end-user. +shown to the user in production. + +.. tip:: -.. sidebar:: Testing Error Pages during Development + You can also :ref:`preview your error pages ` + in ``kernel.debug`` mode. - You should not set ``kernel.debug`` to ``false`` in order to see your - error pages during development. This will also stop - Symfony from recompiling your twig templates, among other things. +.. _cookbook-error-pages-by-status-code: + +How the Template for the Error and Exception Pages Is Selected +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The TwigBundle contains some default templates for error and +exception pages in its ``Resources/views/Exception`` directory. + +.. tip:: - The third-party `WebfactoryExceptionsBundle`_ provides a special - test controller that allows you to display your custom error - pages for arbitrary HTTP status codes even with - ``kernel.debug`` set to ``true``. + In a standard Symfony installation, the TwigBundle can be found at + ``vendor/symfony/symfony/src/Symfony/Bundle/TwigBundle``. In addition + to the standard HTML error page, it also provides a default + error page for many of the most common response formats, including + JSON (``error.json.twig``), XML (``error.xml.twig``) and even + JavaScript (``error.js.twig``), to name a few. -Override Error Templates ------------------------- +Here is how the ``ExceptionController`` will pick one of the +available templates based on the HTTP status code and request format: -All of the error templates live inside the TwigBundle. To override the -templates, simply rely on the standard method for overriding templates that -live inside a bundle. For more information, see -:ref:`overriding-bundle-templates`. +* For *error* pages, it first looks for a template for the given format + and status code (like ``error404.json.twig``); + +* If that does not exist or apply, it looks for a general template for + the given format (like ``error.json.twig`` or + ``exception.json.twig``); + +* Finally, it ignores the format and falls back to the HTML template + (like ``error.html.twig`` or ``exception.html.twig``). + +.. tip:: + + If the exception being handled implements the + :class:`Symfony\\Component\\HttpKernel\\Exception\\HttpExceptionInterface`, + the ``getStatusCode()`` method will be + called to obtain the HTTP status code to use. Otherwise, + the status code will be "500". + +Overriding or Adding Templates +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To override these templates, simply rely on the standard method for +overriding templates that live inside a bundle. For more information, +see :ref:`overriding-bundle-templates`. For example, to override the default error template, create a new template located at @@ -74,47 +120,23 @@ template located at .. tip:: - If you're not familiar with Twig, don't worry. Twig is a simple, powerful - and optional templating engine that integrates with Symfony. For more - information about Twig see :doc:`/book/templating`. - -In addition to the standard HTML error page, Symfony provides a default error -page for many of the most common response formats, including JSON -(``error.json.twig``), XML (``error.xml.twig``) and even JavaScript -(``error.js.twig``), to name a few. To override any of these templates, just -create a new file with the same name in the -``app/Resources/TwigBundle/views/Exception`` directory. This is the standard -way of overriding any template that lives inside a bundle. - -.. _cookbook-error-pages-by-status-code: - -Customizing the 404 Page and other Error Pages ----------------------------------------------- - -You can also customize specific error templates according to the HTTP status -code. For instance, create a -``app/Resources/TwigBundle/views/Exception/error404.html.twig`` template to -display a special page for 404 (page not found) errors. - -Symfony uses the following algorithm to determine which template to use: + If you're not familiar with Twig, don't worry. Twig is a simple, + powerful and optional templating engine that integrates with + Symfony. For more information about Twig see :doc:`/book/templating`. -* First, it looks for a template for the given format and status code (like - ``error404.json.twig``); +This works not only to replace the default templates, but also to add +new ones. -* If it does not exist, it looks for a template for the given format (like - ``error.json.twig``); - -* If it does not exist, it falls back to the HTML template (like - ``error.html.twig``). +For instance, create an ``app/Resources/TwigBundle/views/Exception/error404.html.twig`` +template to display a special page for 404 (page not found) errors. +Refer to the previous section for the order in which the +``ExceptionController`` tries different template names. .. tip:: - To see the full list of default error templates, see the - ``Resources/views/Exception`` directory of the TwigBundle. In a - standard Symfony installation, the TwigBundle can be found at - ``vendor/symfony/symfony/src/Symfony/Bundle/TwigBundle``. Often, the easiest way - to customize an error page is to copy it from the TwigBundle into - ``app/Resources/TwigBundle/views/Exception`` and then modify it. + Often, the easiest way to customize an error page is to copy it from + the TwigBundle into ``app/Resources/TwigBundle/views/Exception`` and + then modify it. .. note:: @@ -123,33 +145,209 @@ Symfony uses the following algorithm to determine which template to use: ``exception.html.twig`` for the standard HTML exception page or ``exception.json.twig`` for the JSON exception page. -.. _`WebfactoryExceptionsBundle`: https://github.com/webfactory/exceptions-bundle +.. _testing-error-pages: + +Testing Error Pages during Development +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The default ``ExceptionController`` also allows you to preview your +*error* pages during development. + +.. versionadded:: 2.6 + This feature was introduced in Symfony 2.6. Before, the third-party + `WebfactoryExceptionsBundle`_ could be used for the same purpose. + +To use this feature, you need to have a definition in your +``routing_dev.yml`` file like so: + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/routing_dev.yml + _errors: + resource: "@TwigBundle/Resources/config/routing/errors.xml" + prefix: /_error + + .. code-block:: xml + + + + + + + + + .. code-block:: php + + // app/config/routing_dev.php + use Symfony\Component\Routing\RouteCollection; + + $collection = new RouteCollection(); + $collection->addCollection( + $loader->import('@TwigBundle/Resources/config/routing/errors.xml') + ); + $collection->addPrefix("/_error"); + + return $collection; + +If you're coming from an older version of Symfony, you might need to +add this to your ``routing_dev.yml`` file. If you're starting from +scratch, the `Symfony Standard Edition`_ already contains it for you. + +With this route added, you can use URLs like -Replace the default Exception Controller ----------------------------------------- +.. code-block:: text -If you need a little more flexibility beyond just overriding the template -(e.g. you need to pass some additional variables into your template), -then you can override the controller that renders the error page. + http://localhost/app_dev.php/_error/{statusCode} + http://localhost/app_dev.php/_error/{statusCode}.{format} -The default exception controller is registered as a service - the actual -class is ``Symfony\Bundle\TwigBundle\Controller\ExceptionController``. +to preview the *error* page for a given status code as HTML or for a +given status code and format. -To do this, create a new controller class and make it extend Symfony's default -``Symfony\Bundle\TwigBundle\Controller\ExceptionController`` class. +.. _custom-exception-controller: -There are several methods you can override to customize different parts of how -the error page is rendered. You could, for example, override the entire -``showAction`` or just the ``findTemplate`` method, which locates which -template should be rendered. +Replacing the Default ExceptionController +------------------------------------------ -To make Symfony use your exception controller instead of the default, set the +If you need a little more flexibility beyond just overriding the +template, then you can change the controller that renders the error +page. For example, you might need to pass some additional variables into +your template. + +.. caution:: + + Make sure you don't lose the exception pages that render the helpful + error messages during development. + +To do this, simply create a new controller and set the :ref:`twig.exception_controller ` option -in app/config/config.yml. +to point to it. + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/config.yml + twig: + exception_controller: AppBundle:Exception:showException + + .. code-block:: xml + + + + + + + AppBundle:Exception:showException + + + + .. code-block:: php + + // app/config/config.php + $container->loadFromExtension('twig', array( + 'exception_controller' => 'AppBundle:Exception:showException', + // ... + )); + +.. tip:: + + You can also set up your controller as a service. + + The default value of ``twig.controller.exception:showAction`` refers + to the ``showAction`` method of the ``ExceptionController`` + described previously, which is registered in the DIC as the + ``twig.controller.exception`` service. + +Your controller will be passed two parameters: ``exception``, +which is a :class:`\\Symfony\\Component\\Debug\\Exception\\FlattenException` +instance created from the exception being handled, and ``logger``, +an instance of :class:`\\Symfony\\Component\\HttpKernel\\Log\\DebugLoggerInterface` +(which may be ``null``). + +.. tip:: + + The Request that will be dispatched to your controller is created + in the :class:`Symfony\\Component\\HttpKernel\\EventListener\\ExceptionListener`. + This event listener is set up by the TwigBundle. + +You can, of course, also extend the previously described +:class:`Symfony\\Bundle\\TwigBundle\\Controller\\ExceptionController`. +In that case, you might want to override one or both of the +``showAction`` and ``findTemplate`` methods. The latter one locates the +template to be used. + +.. caution:: + + As of writing, the ``ExceptionController`` is *not* part of the + Symfony API, so be aware that it might change in following releases. .. tip:: - The customization of exception handling is actually much more powerful - than what's written here. An internal event, ``kernel.exception``, is thrown - which allows complete control over exception handling. For more - information, see :ref:`kernel-kernel.exception`. + The :ref:`error page preview ` also works for + your own controllers set up this way. + +.. _use-kernel-exception-event: + +Working with the kernel.exception Event +----------------------------------------- + +As mentioned in the beginning, the ``kernel.exception`` event is +dispatched whenever the Symfony Kernel needs to +handle an exception. For more information on that, see :ref:`kernel-kernel.exception`. + +Working with this event is actually much more powerful than what has +been explained before but also requires a thorough understanding of +Symfony internals. + +To give one example, assume your application throws +specialized exceptions with a particular meaning to your domain. + +In that case, all the default ``ExceptionListener`` and +``ExceptionController`` could do for you was trying to figure out the +right HTTP status code and display your nice-looking error page. + +:doc:`Writing your own event listener ` +for the ``kernel.exception`` event allows you to have a closer look +at the exception and take different actions depending on it. Those +actions might include logging the exception, redirecting the user to +another page or rendering specialized error pages. + +.. note:: + + If your listener calls ``setResponse()`` on the + :class:`Symfony\\Component\\HttpKernel\\Event\\GetResponseForExceptionEvent`, + event propagation will be stopped and the response will be sent to + the client. + +This approach allows you to create centralized and layered error +handling: Instead of catching (and handling) the same exceptions +in various controllers again and again, you can have just one (or +several) listeners deal with them. + +.. tip:: + + To see an example, have a look at the `ExceptionListener`_ in the + Security Component. + + It handles various security-related exceptions that are thrown in + your application (like :class:`Symfony\\Component\\Security\\Core\\Exception\\AccessDeniedException`) + and takes measures like redirecting the user to the login page, + logging them out and other things. + +Good luck! + +.. _`WebfactoryExceptionsBundle`: https://github.com/webfactory/exceptions-bundle +.. _`Symfony Standard Edition`: https://github.com/symfony/symfony-standard/ +.. _`ExceptionListener`: https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Security/Http/Firewall/ExceptionListener.php diff --git a/cookbook/controller/service.rst b/cookbook/controller/service.rst index 2fa69b5c332..584d0ca2615 100644 --- a/cookbook/controller/service.rst +++ b/cookbook/controller/service.rst @@ -34,8 +34,8 @@ Defining the Controller as a Service A controller can be defined as a service in the same way as any other class. For example, if you have the following simple controller:: - // src/Acme/HelloBundle/Controller/HelloController.php - namespace Acme\HelloBundle\Controller; + // src/AppBundle/Controller/HelloController.php + namespace AppBundle\Controller; use Symfony\Component\HttpFoundation\Response; @@ -53,40 +53,25 @@ Then you can define it as a service as follows: .. code-block:: yaml - # src/Acme/HelloBundle/Resources/config/services.yml - parameters: - # ... - acme.controller.hello.class: Acme\HelloBundle\Controller\HelloController - + # app/config/services.yml services: - acme.hello.controller: - class: "%acme.controller.hello.class%" + app.hello_controller: + class: AppBundle\Controller\HelloController .. code-block:: xml - - - - Acme\HelloBundle\Controller\HelloController - - + - + .. code-block:: php - // src/Acme/HelloBundle/Resources/config/services.php + // app/config/services.php use Symfony\Component\DependencyInjection\Definition; - // ... - $container->setParameter( - 'acme.controller.hello.class', - 'Acme\HelloBundle\Controller\HelloController' - ); - - $container->setDefinition('acme.hello.controller', new Definition( - '%acme.controller.hello.class%' + $container->setDefinition('app.hello_controller', new Definition( + 'AppBundle\Controller\HelloController' )); Referring to the Service @@ -94,9 +79,9 @@ Referring to the Service To refer to a controller that's defined as a service, use the single colon (:) notation. For example, to forward to the ``indexAction()`` method of the service -defined above with the id ``acme.hello.controller``:: +defined above with the id ``app.hello_controller``:: - $this->forward('acme.hello.controller:indexAction', array('name' => $name)); + $this->forward('app.hello_controller:indexAction', array('name' => $name)); .. note:: @@ -113,28 +98,31 @@ the route ``_controller`` value: # app/config/routing.yml hello: path: /hello - defaults: { _controller: acme.hello.controller:indexAction } + defaults: { _controller: app.hello_controller:indexAction } .. code-block:: xml - acme.hello.controller:indexAction + app.hello_controller:indexAction .. code-block:: php // app/config/routing.php $collection->add('hello', new Route('/hello', array( - '_controller' => 'acme.hello.controller:indexAction', + '_controller' => 'app.hello_controller:indexAction', ))); .. tip:: You can also use annotations to configure routing using a controller - defined as a service. See the - :doc:`FrameworkExtraBundle documentation ` - for details. + defined as a service. See the `FrameworkExtraBundle documentation`_ for + details. + +.. versionadded:: 2.6 + If your controller service implements the ``__invoke`` method, you can simply refer to the service id + (``acme.hello.controller``). Alternatives to base Controller Methods --------------------------------------- @@ -149,8 +137,8 @@ For example, if you want to render a template instead of creating the ``Response object directly, then your code would look like this if you were extending Symfony's base controller:: - // src/Acme/HelloBundle/Controller/HelloController.php - namespace Acme\HelloBundle\Controller; + // src/AppBundle/Controller/HelloController.php + namespace AppBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; @@ -159,7 +147,7 @@ Symfony's base controller:: public function indexAction($name) { return $this->render( - 'AcmeHelloBundle:Hello:index.html.twig', + 'AppBundle:Hello:index.html.twig', array('name' => $name) ); } @@ -177,8 +165,8 @@ If you look at the source code for the ``render`` function in Symfony's In a controller that's defined as a service, you can instead inject the ``templating`` service and use it directly:: - // src/Acme/HelloBundle/Controller/HelloController.php - namespace Acme\HelloBundle\Controller; + // src/AppBundle/Controller/HelloController.php + namespace AppBundle\Controller; use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface; use Symfony\Component\HttpFoundation\Response; @@ -195,7 +183,7 @@ service and use it directly:: public function indexAction($name) { return $this->templating->renderResponse( - 'AcmeHelloBundle:Hello:index.html.twig', + 'AppBundle:Hello:index.html.twig', array('name' => $name) ); } @@ -208,46 +196,29 @@ argument: .. code-block:: yaml - # src/Acme/HelloBundle/Resources/config/services.yml - parameters: - # ... - acme.controller.hello.class: Acme\HelloBundle\Controller\HelloController - + # app/config/services.yml services: - acme.hello.controller: - class: "%acme.controller.hello.class%" + app.hello_controller: + class: AppBundle\Controller\HelloController arguments: ["@templating"] .. code-block:: xml - - - - Acme\HelloBundle\Controller\HelloController - - + - + .. code-block:: php - // src/Acme/HelloBundle/Resources/config/services.php + // app/config/services.php use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; - // ... - $container->setParameter( - 'acme.controller.hello.class', - 'Acme\HelloBundle\Controller\HelloController' - ); - - $container->setDefinition('acme.hello.controller', new Definition( - '%acme.controller.hello.class%', + $container->setDefinition('app.hello_controller', new Definition( + 'AppBundle\Controller\HelloController', array(new Reference('templating')) )); @@ -267,3 +238,4 @@ inject *only* the exact service(s) that you need directly into the controller. .. _`Controller class source code`: https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php .. _`base Controller class`: https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php +.. _`FrameworkExtraBundle documentation`: http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/routing.html diff --git a/cookbook/debugging.rst b/cookbook/debugging.rst index 372fb707931..98fae25bad8 100644 --- a/cookbook/debugging.rst +++ b/cookbook/debugging.rst @@ -47,8 +47,6 @@ below:: $loader = require_once __DIR__.'/../app/autoload.php'; require_once __DIR__.'/../app/AppKernel.php'; - use Symfony\Component\HttpFoundation\Request; - $kernel = new AppKernel('dev', true); // $kernel->loadClassCache(); $request = Request::createFromGlobals(); diff --git a/cookbook/deployment/heroku.rst b/cookbook/deployment/heroku.rst index 87baeb6c8d6..44f33b46362 100644 --- a/cookbook/deployment/heroku.rst +++ b/cookbook/deployment/heroku.rst @@ -71,8 +71,9 @@ Deploying your Application on Heroku To deploy your application to Heroku, you must first create a ``Procfile``, which tells Heroku what command to use to launch the web server with the -correct settings. After you've done that, you can simply ``git push`` and -you're done! +correct document root. After that, you will ensure that your Symfony application +runs the ``prod`` environment, and then you'll be ready to ``git push`` to +Heroku for your first deploy! Creating a Procfile ~~~~~~~~~~~~~~~~~~~ @@ -80,9 +81,9 @@ Creating a Procfile By default, Heroku will launch an Apache web server together with PHP to serve applications. However, two special circumstances apply to Symfony applications: -1. The document root is in the ``web/`` directory and not in the root directory +#. The document root is in the ``web/`` directory and not in the root directory of the application; -2. The Composer ``bin-dir``, where vendor binaries (and thus Heroku's own boot +#. The Composer ``bin-dir``, where vendor binaries (and thus Heroku's own boot scripts) are placed, is ``bin/`` , and not the default ``vendor/bin``. .. note:: @@ -110,6 +111,27 @@ create the ``Procfile`` file and to add it to the repository: [master 35075db] Procfile for Apache and PHP 1 file changed, 1 insertion(+) +Setting the ``prod`` Environment +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +During a deploy, Heroku runs ``composer install --no-dev`` to install all of the +dependencies your application requires. However, typical `post-install-commands`_ +in ``composer.json``, e.g. to install assets or clear (or pre-warm) caches, run +using Symfony's ``dev`` environment by default. + +This is clearly not what you want - the app runs in "production" (even if you +use it just for an experiment, or as a staging environment), and so any build +steps should use the same ``prod`` environment as well. + +Thankfully, the solution to this problem is very simple: Symfony will pick up an +environment variable named ``SYMFONY_ENV`` and use that environment if nothing +else is explicitly set. As Heroku exposes all `config vars`_ as environment +variables, you can issue a single command to prepare your app for a deployment: + +.. code-block:: bash + + $ heroku config:set SYMFONY_ENV=prod + Pushing to Heroku ~~~~~~~~~~~~~~~~~ @@ -193,3 +215,5 @@ You should be seeing your Symfony application in your browser. .. _`ephemeral file system`: https://devcenter.heroku.com/articles/dynos#ephemeral-filesystem .. _`Logplex`: https://devcenter.heroku.com/articles/logplex .. _`verified that the RSA key fingerprint is correct`: https://devcenter.heroku.com/articles/git-repository-ssh-fingerprints +.. _`post-install-commands`: https://getcomposer.org/doc/articles/scripts.md +.. _`config vars`: https://devcenter.heroku.com/articles/config-vars \ No newline at end of file diff --git a/cookbook/deployment/index.rst b/cookbook/deployment/index.rst index ef5699e8cea..2b1a962fb54 100644 --- a/cookbook/deployment/index.rst +++ b/cookbook/deployment/index.rst @@ -7,3 +7,4 @@ Deployment tools azure-website heroku + platformsh diff --git a/cookbook/deployment/platformsh.rst b/cookbook/deployment/platformsh.rst new file mode 100644 index 00000000000..0419195f8e0 --- /dev/null +++ b/cookbook/deployment/platformsh.rst @@ -0,0 +1,190 @@ +.. index:: + single: Deployment; Deploying to Platform.sh + +Deploying to Platform.sh +======================== + +This step-by-step cookbook describes how to deploy a Symfony web application to +`Platform.sh`_. You can read more about using Symfony with Platform.sh on the +official `Platform.sh documentation`_. + +Deploy an Existing Site +----------------------- + +In this guide, it is assumed your codebase is already versioned with Git. + +Get a Project on Platform.sh +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You need to subscribe to a `Platform.sh project`_. Choose the development plan +and go through the checkout process. Once your project is ready, give it a name +and choose: **Import an existing site**. + +Prepare Your Application +~~~~~~~~~~~~~~~~~~~~~~~~ + +To deploy your Symfony application on Platform.sh, you simply need to add a +``.platform.app.yaml`` at the root of your Git repository which will tell +Platform.sh how to deploy your application (read more about +`Platform.sh configuration files`_). + +.. code-block:: yaml + + # .platform.app.yaml + + # This file describes an application. You can have multiple applications + # in the same project. + + # The name of this app. Must be unique within a project. + name: myphpproject + + # The toolstack used to build the application. + toolstack: "php:symfony" + + # The relationships of the application with services or other applications. + # The left-hand side is the name of the relationship as it will be exposed + # to the application in the PLATFORM_RELATIONSHIPS variable. The right-hand + # side is in the form `:`. + relationships: + database: "mysql:mysql" + + # The configuration of app when it is exposed to the web. + web: + # The public directory of the app, relative to its root. + document_root: "/web" + # The front-controller script to send non-static requests to. + passthru: "/app.php" + + # The size of the persistent disk of the application (in MB). + disk: 2048 + + # The mounts that will be performed when the package is deployed. + mounts: + "/app/cache": "shared:files/cache" + "/app/logs": "shared:files/logs" + + # The hooks that will be performed when the package is deployed. + hooks: + build: | + rm web/app_dev.php + app/console --env=prod assetic:dump --no-debug + deploy: | + app/console --env=prod cache:clear + +For best practices, you should also add a ``.platform`` folder at the root of +your Git repository which contains the following files: + +.. code-block:: yaml + + # .platform/routes.yaml + "http://{default}/": + type: upstream + upstream: "php:php" + +.. code-block:: yaml + + # .platform/services.yaml + mysql: + type: mysql + disk: 2048 + +An example of these configurations can be found on `GitHub`_. The list of +`available services `_ can be found on the Platform.sh documentation. + +Configure Database Access +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Platform.sh overrides your database specific configuration via importing the +following file:: + + // app/config/parameters_platform.php + setParameter('database_driver', 'pdo_' . $endpoint['scheme']); + $container->setParameter('database_host', $endpoint['host']); + $container->setParameter('database_port', $endpoint['port']); + $container->setParameter('database_name', $endpoint['path']); + $container->setParameter('database_user', $endpoint['username']); + $container->setParameter('database_password', $endpoint['password']); + $container->setParameter('database_path', ''); + } + + # Store session into /tmp. + ini_set('session.save_path', '/tmp/sessions'); + +Make sure this file is listed in your *imports*: + +.. code-block:: yaml + + # app/config/config.yml + imports: + - { resource: parameters_platform.php } + +Deploy your Application +~~~~~~~~~~~~~~~~~~~~~~~ + +Now you need to add a remote to Platform.sh in your Git repository (copy the +command that you see on the Platform.sh web UI): + +.. code-block:: bash + + $ git remote add platform [PROJECT-ID]@git.[CLUSTER].platform.sh:[PROJECT-ID].git + +``PROJECT-ID`` + Unique identifier of your project. Something like ``kjh43kbobssae`` +``CLUSTER`` + Server location where your project is deployed. It can be ``eu`` or ``us`` + +Commit the Platform.sh specific files created in the previous section: + +.. code-block:: bash + + $ git add .platform.app.yaml .platform/* + $ git add app/config/config.yml app/config/parameters_platform.php + $ git commit -m "Adding Platform.sh configuration files." + +Push your code base to the newly added remote: + +.. code-block:: bash + + $ git push platform master + +That's it! Your application is being deployed on Platform.sh and you'll soon be +able to access it in your browser. + +Every code change that you do from now on will be pushed to Git in order to +redeploy your environment on Platform.sh. + +More information about `migrating your database and files `_ can be found on the +Platform.sh documentation. + +Deploy a new Site +----------------- + +You can start a new `Platform.sh project`_. Choose the development plan and go +through the checkout process. + +Once your project is ready, give it a name and choose: **Create a new site**. +Choose the *Symfony* stack and a starting point such as *Standard*. + +That's it! Your Symfony application will be bootstrapped and deployed. You'll +soon be able to see it in your browser. + +.. _`Platform.sh`: https://platform.sh +.. _`Platform.sh documentation`: https://docs.platform.sh/toolstacks/symfony/symfony-getting-started +.. _`Platform.sh project`: https://marketplace.commerceguys.com/platform/buy-now +.. _`Platform.sh configuration files`: https://docs.platform.sh/reference/configuration-files +.. _`GitHub`: https://github.com/platformsh/platformsh-examples +.. _`configure-services`: https://docs.platform.sh/reference/configuration-files/#configure-services +.. _`migrate-existing-site`: https://docs.platform.sh/toolstacks/symfony/migrate-existing-site/ diff --git a/cookbook/deployment/tools.rst b/cookbook/deployment/tools.rst index 76992a17c59..4004c06aeb8 100644 --- a/cookbook/deployment/tools.rst +++ b/cookbook/deployment/tools.rst @@ -101,7 +101,7 @@ as you normally do: .. code-block:: bash - $ php composer.phar install --no-dev --optimize-autoloader + $ composer install --no-dev --optimize-autoloader .. tip:: @@ -142,7 +142,7 @@ setup: * Running any database migrations * Clearing your APC cache -* Running ``assets:install`` (taken care of already in ``composer.phar install``) +* Running ``assets:install`` (already taken care of in ``composer install``) * Add/edit CRON jobs * Pushing assets to a CDN * ... diff --git a/cookbook/doctrine/custom_dql_functions.rst b/cookbook/doctrine/custom_dql_functions.rst index b15877ffa2a..747a353e7a5 100644 --- a/cookbook/doctrine/custom_dql_functions.rst +++ b/cookbook/doctrine/custom_dql_functions.rst @@ -19,12 +19,12 @@ In Symfony, you can register your custom DQL functions as follows: # ... dql: string_functions: - test_string: Acme\HelloBundle\DQL\StringFunction - second_string: Acme\HelloBundle\DQL\SecondStringFunction + test_string: AppBundle\DQL\StringFunction + second_string: AppBundle\DQL\SecondStringFunction numeric_functions: - test_numeric: Acme\HelloBundle\DQL\NumericFunction + test_numeric: AppBundle\DQL\NumericFunction datetime_functions: - test_datetime: Acme\HelloBundle\DQL\DatetimeFunction + test_datetime: AppBundle\DQL\DatetimeFunction .. code-block:: xml @@ -39,10 +39,10 @@ In Symfony, you can register your custom DQL functions as follows: - Acme\HelloBundle\DQL\SecondStringFunction - Acme\HelloBundle\DQL\DatetimeFunction + AppBundle\DQL\StringFunction + AppBundle\DQL\SecondStringFunction + AppBundle\DQL\NumericFunction + AppBundle\DQL\DatetimeFunction @@ -56,14 +56,14 @@ In Symfony, you can register your custom DQL functions as follows: // ... 'dql' => array( 'string_functions' => array( - 'test_string' => 'Acme\HelloBundle\DQL\StringFunction', - 'second_string' => 'Acme\HelloBundle\DQL\SecondStringFunction', + 'test_string' => 'AppBundle\DQL\StringFunction', + 'second_string' => 'AppBundle\DQL\SecondStringFunction', ), 'numeric_functions' => array( - 'test_numeric' => 'Acme\HelloBundle\DQL\NumericFunction', + 'test_numeric' => 'AppBundle\DQL\NumericFunction', ), 'datetime_functions' => array( - 'test_datetime' => 'Acme\HelloBundle\DQL\DatetimeFunction', + 'test_datetime' => 'AppBundle\DQL\DatetimeFunction', ), ), ), diff --git a/cookbook/doctrine/dbal.rst b/cookbook/doctrine/dbal.rst index 5cc468202c2..39cb0eabeaf 100644 --- a/cookbook/doctrine/dbal.rst +++ b/cookbook/doctrine/dbal.rst @@ -1,12 +1,12 @@ .. index:: pair: Doctrine; DBAL -How to Use Doctrine's DBAL Layer -================================ +How to Use Doctrine DBAL +======================== .. note:: - This article is about Doctrine DBAL's layer. Typically, you'll work with + This article is about the Doctrine DBAL. Typically, you'll work with the higher level Doctrine ORM layer, which simply uses the DBAL behind the scenes to actually communicate with the database. To read more about the Doctrine ORM, see ":doc:`/book/doctrine`". @@ -93,8 +93,8 @@ mapping types, read Doctrine's `Custom Mapping Types`_ section of their document doctrine: dbal: types: - custom_first: Acme\HelloBundle\Type\CustomFirst - custom_second: Acme\HelloBundle\Type\CustomSecond + custom_first: AppBundle\Type\CustomFirst + custom_second: AppBundle\Type\CustomSecond .. code-block:: xml @@ -107,8 +107,8 @@ mapping types, read Doctrine's `Custom Mapping Types`_ section of their document - - + + @@ -119,8 +119,8 @@ mapping types, read Doctrine's `Custom Mapping Types`_ section of their document $container->loadFromExtension('doctrine', array( 'dbal' => array( 'types' => array( - 'custom_first' => 'Acme\HelloBundle\Type\CustomFirst', - 'custom_second' => 'Acme\HelloBundle\Type\CustomSecond', + 'custom_first' => 'AppBundle\Type\CustomFirst', + 'custom_second' => 'AppBundle\Type\CustomSecond', ), ), )); @@ -166,7 +166,7 @@ mapping type: // app/config/config.php $container->loadFromExtension('doctrine', array( 'dbal' => array( - mapping_types' => array( + 'mapping_types' => array( 'enum' => 'string', ), ), diff --git a/cookbook/doctrine/file_uploads.rst b/cookbook/doctrine/file_uploads.rst index 95a06bd7672..b68806f59d4 100644 --- a/cookbook/doctrine/file_uploads.rst +++ b/cookbook/doctrine/file_uploads.rst @@ -23,8 +23,8 @@ Basic Setup First, create a simple Doctrine entity class to work with:: - // src/Acme/DemoBundle/Entity/Document.php - namespace Acme\DemoBundle\Entity; + // src/AppBundle/Entity/Document.php + namespace AppBundle\Entity; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; @@ -154,8 +154,8 @@ rules:: .. code-block:: yaml - # src/Acme/DemoBundle/Resources/config/validation.yml - Acme\DemoBundle\Entity\Document: + # src/AppBundle/Resources/config/validation.yml + AppBundle\Entity\Document: properties: file: - File: @@ -163,8 +163,8 @@ rules:: .. code-block:: php-annotations - // src/Acme/DemoBundle/Entity/Document.php - namespace Acme\DemoBundle\Entity; + // src/AppBundle/Entity/Document.php + namespace AppBundle\Entity; // ... use Symfony\Component\Validator\Constraints as Assert; @@ -181,8 +181,8 @@ rules:: .. code-block:: xml - - + + @@ -192,7 +192,7 @@ rules:: .. code-block:: php - // src/Acme/DemoBundle/Entity/Document.php + // src/AppBundle/Entity/Document.php namespace Acme\DemoBundle\Entity; // ... @@ -220,7 +220,7 @@ rules:: The following controller shows you how to handle the entire process:: // ... - use Acme\DemoBundle\Entity\Document; + use AppBundle\Entity\Document; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; use Symfony\Component\HttpFoundation\Request; // ... @@ -404,7 +404,8 @@ Next, refactor the ``Document`` class to take advantage of these callbacks:: */ public function removeUpload() { - if ($file = $this->getAbsolutePath()) { + $file = $this->getAbsolutePath(); + if ($file) { unlink($file); } } diff --git a/cookbook/doctrine/mapping_model_classes.rst b/cookbook/doctrine/mapping_model_classes.rst index 975d95cd0be..9894777a339 100644 --- a/cookbook/doctrine/mapping_model_classes.rst +++ b/cookbook/doctrine/mapping_model_classes.rst @@ -14,23 +14,23 @@ register the mappings for your model classes. For non-reusable bundles, the easiest option is to put your model classes in the default locations: ``Entity`` for the Doctrine ORM or ``Document`` for one of the ODMs. For reusable bundles, rather than duplicate model classes - just to get the auto mapping, use the compiler pass. + just to get the auto-mapping, use the compiler pass. .. versionadded:: 2.3 The base mapping compiler pass was introduced in Symfony 2.3. The Doctrine bundles support it from DoctrineBundle >= 1.2.1, MongoDBBundle >= 3.0.0, - PHPCRBundle >= 1.0.0-alpha2 and the (unversioned) CouchDBBundle supports the + PHPCRBundle >= 1.0.0 and the (unversioned) CouchDBBundle supports the compiler pass since the `CouchDB Mapping Compiler Pass pull request`_ was merged. - If you want your bundle to support older versions of Symfony and - Doctrine, you can provide a copy of the compiler pass in your bundle. - See for example the `FOSUserBundle mapping configuration`_ - ``addRegisterMappingsPass``. - +.. versionadded:: 2.6 + Support for defining namespace aliases was introduced in Symfony 2.6. + It is safe to define the aliases with older versions of Symfony as + the aliases are the last argument to ``createXmlMappingDriver`` and + are ignored by PHP if that argument doesn't exist. In your bundle class, write the following code to register the compiler pass. -This one is written for the FOSUserBundle, so parts of it will need to +This one is written for the CmfRoutingBundle, so parts of it will need to be adapted for your case:: use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\DoctrineOrmMappingsPass; @@ -38,7 +38,7 @@ be adapted for your case:: use Doctrine\Bundle\CouchDBBundle\DependencyInjection\Compiler\DoctrineCouchDBMappingsPass; use Doctrine\Bundle\PHPCRBundle\DependencyInjection\Compiler\DoctrinePhpcrMappingsPass; - class FOSUserBundle extends Bundle + class CmfRoutingBundle extends Bundle { public function build(ContainerBuilder $container) { @@ -47,7 +47,7 @@ be adapted for your case:: $modelDir = realpath(__DIR__.'/Resources/config/doctrine/model'); $mappings = array( - $modelDir => 'FOS\UserBundle\Model', + $modelDir => 'Symfony\Cmf\RoutingBundle\Model', ); $ormCompilerClass = 'Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\DoctrineOrmMappingsPass'; @@ -55,8 +55,9 @@ be adapted for your case:: $container->addCompilerPass( DoctrineOrmMappingsPass::createXmlMappingDriver( $mappings, - array('fos_user.model_manager_name'), - 'fos_user.backend_type_orm' + array('cmf_routing.model_manager_name'), + 'cmf_routing.backend_type_orm', + array('CmfRoutingBundle' => 'Symfony\Cmf\RoutingBundle\Model') )); } @@ -65,8 +66,9 @@ be adapted for your case:: $container->addCompilerPass( DoctrineMongoDBMappingsPass::createXmlMappingDriver( $mappings, - array('fos_user.model_manager_name'), - 'fos_user.backend_type_mongodb' + array('cmf_routing.model_manager_name'), + 'cmf_routing.backend_type_mongodb', + array('CmfRoutingBundle' => 'Symfony\Cmf\RoutingBundle\Model') )); } @@ -75,8 +77,9 @@ be adapted for your case:: $container->addCompilerPass( DoctrineCouchDBMappingsPass::createXmlMappingDriver( $mappings, - array('fos_user.model_manager_name'), - 'fos_user.backend_type_couchdb' + array('cmf_routing.model_manager_name'), + 'cmf_routing.backend_type_couchdb', + array('CmfRoutingBundle' => 'Symfony\Cmf\RoutingBundle\Model') )); } @@ -85,8 +88,9 @@ be adapted for your case:: $container->addCompilerPass( DoctrinePhpcrMappingsPass::createXmlMappingDriver( $mappings, - array('fos_user.model_manager_name'), - 'fos_user.backend_type_phpcr' + array('cmf_routing.model_manager_name'), + 'cmf_routing.backend_type_phpcr', + array('CmfRoutingBundle' => 'Symfony\Cmf\RoutingBundle\Model') )); } } @@ -99,17 +103,20 @@ decide which to use. The compiler pass provides factory methods for all drivers provided by Doctrine: Annotations, XML, Yaml, PHP and StaticPHP. The arguments are: -* a map/hash of absolute directory path to namespace; -* an array of container parameters that your bundle uses to specify the name of - the Doctrine manager that it is using. In the above example, the FOSUserBundle - stores the manager name that's being used under the ``fos_user.model_manager_name`` +* A map/hash of absolute directory path to namespace; +* An array of container parameters that your bundle uses to specify the name of + the Doctrine manager that it is using. In the example above, the CmfRoutingBundle + stores the manager name that's being used under the ``cmf_routing.model_manager_name`` parameter. The compiler pass will append the parameter Doctrine is using to specify the name of the default manager. The first parameter found is used and the mappings are registered with that manager; -* an optional container parameter name that will be used by the compiler +* An optional container parameter name that will be used by the compiler pass to determine if this Doctrine type is used at all. This is relevant if your user has more than one type of Doctrine bundle installed, but your - bundle is only used with one type of Doctrine. + bundle is only used with one type of Doctrine; +* A map/hash of aliases to namespace. This should be the same convention used + by Doctrine auto-mapping. In the example above, this allows the user to call + ``$om->getRepository('CmfRoutingBundle:Route')``. .. note:: @@ -120,7 +127,7 @@ Annotations, XML, Yaml, PHP and StaticPHP. The arguments are: of the class as their filename (e.g. ``BlogPost.orm.xml``) If you also need to map a base class, you can register a compiler pass - with the ``DefaultFileLocator`` like this. This code is simply taken from the + with the ``DefaultFileLocator`` like this. This code is taken from the ``DoctrineOrmMappingsPass`` and adapted to use the ``DefaultFileLocator`` instead of the ``SymfonyFileLocator``:: @@ -138,6 +145,9 @@ Annotations, XML, Yaml, PHP and StaticPHP. The arguments are: ); } + Note that you do not need to provide a namespace alias unless your users are + expected to ask Doctrine for the base classes. + Now place your mapping file into ``/Resources/config/doctrine-base`` with the fully qualified class name, separated by ``.`` instead of ``\``, for example ``Other.Namespace.Model.Name.orm.xml``. You may not mix the two as otherwise @@ -146,4 +156,3 @@ Annotations, XML, Yaml, PHP and StaticPHP. The arguments are: Adjust accordingly for the other Doctrine implementations. .. _`CouchDB Mapping Compiler Pass pull request`: https://github.com/doctrine/DoctrineCouchDBBundle/pull/27 -.. _`FOSUserBundle mapping configuration`: https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/FOSUserBundle.php diff --git a/cookbook/doctrine/multiple_entity_managers.rst b/cookbook/doctrine/multiple_entity_managers.rst index 689d9d1bba2..5434d847365 100644 --- a/cookbook/doctrine/multiple_entity_managers.rst +++ b/cookbook/doctrine/multiple_entity_managers.rst @@ -49,7 +49,7 @@ The following configuration code shows how you can configure two entity managers default: connection: default mappings: - AcmeDemoBundle: ~ + AppBundle: ~ AcmeStoreBundle: ~ customer: connection: customer @@ -90,7 +90,7 @@ The following configuration code shows how you can configure two entity managers - + @@ -134,7 +134,7 @@ The following configuration code shows how you can configure two entity managers 'default' => array( 'connection' => 'default', 'mappings' => array( - 'AcmeDemoBundle' => null, + 'AppBundle' => null, 'AcmeStoreBundle' => null, ), ), @@ -150,9 +150,9 @@ The following configuration code shows how you can configure two entity managers In this case, you've defined two entity managers and called them ``default`` and ``customer``. The ``default`` entity manager manages entities in the -``AcmeDemoBundle`` and ``AcmeStoreBundle``, while the ``customer`` entity -manager manages entities in the ``AcmeCustomerBundle``. You've also defined -two connections, one for each entity manager. +AppBundle and AcmeStoreBundle, while the ``customer`` entity manager manages +entities in the AcmeCustomerBundle. You've also defined two connections, one +for each entity manager. .. note:: diff --git a/cookbook/doctrine/resolve_target_entity.rst b/cookbook/doctrine/resolve_target_entity.rst index b4b12352efc..e5fc217a8e6 100644 --- a/cookbook/doctrine/resolve_target_entity.rst +++ b/cookbook/doctrine/resolve_target_entity.rst @@ -132,7 +132,7 @@ about the replacement: - Acme\AppBundle\Entity\Customer + Acme\AppBundle\Entity\Customer diff --git a/cookbook/doctrine/reverse_engineering.rst b/cookbook/doctrine/reverse_engineering.rst index b5078d4aa44..dd50a6be9c2 100644 --- a/cookbook/doctrine/reverse_engineering.rst +++ b/cookbook/doctrine/reverse_engineering.rst @@ -49,9 +49,8 @@ to a post record thanks to a foreign key constraint. Before diving into the recipe, be sure your database connection parameters are correctly setup in the ``app/config/parameters.yml`` file (or wherever your database configuration is kept) and that you have initialized a bundle that -will host your future entity class. In this tutorial it's assumed that -an ``AcmeBlogBundle`` exists and is located under the ``src/Acme/BlogBundle`` -folder. +will host your future entity class. In this tutorial it's assumed that an +AcmeBlogBundle exists and is located under the ``src/Acme/BlogBundle`` folder. The first step towards building entity classes from an existing database is to ask Doctrine to introspect the database and generate the corresponding diff --git a/cookbook/email/cloud.rst b/cookbook/email/cloud.rst index f225b220a69..9b3677094c3 100644 --- a/cookbook/email/cloud.rst +++ b/cookbook/email/cloud.rst @@ -46,8 +46,10 @@ and complete the configuration with the provided ``username`` and ``password``: + xsi:schemaLocation="http://symfony.com/schema/dic/services + http://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/swiftmailer + http://symfony.com/schema/dic/swiftmailer/swiftmailer-1.0.xsd"> + transport="%mailer_transport%" + host="%mailer_host%" + username="%mailer_user%" + password="%mailer_password%" /> .. code-block:: php // app/config/config.php $container->loadFromExtension('swiftmailer', array( - 'transport' => "smtp", - 'encryption' => "ssl", - 'auth_mode' => "login", - 'host' => "smtp.gmail.com", - 'username' => "your_username", - 'password' => "your_password", + 'transport' => "%mailer_transport%", + 'host' => "%mailer_host%", + 'username' => "%mailer_user%", + 'password' => "%mailer_password%", )); -The majority of the Swift Mailer configuration deals with how the messages -themselves should be delivered. +These values (e.g. ``%mailer_transport%``), are reading from the parameters +that are set in the :ref:`parameters.yml ` file. You +can modify the values in that file, or set the values directly here. The following configuration attributes are available: @@ -103,18 +93,31 @@ an email is pretty straightforward:: public function indexAction($name) { - $message = \Swift_Message::newInstance() - ->setSubject('Hello Email') + $mailer = $this->get('mailer'); + $message = $mailer->createMessage() + ->setSubject('You have Completed Registration!') ->setFrom('send@example.com') ->setTo('recipient@example.com') ->setBody( $this->renderView( - 'HelloBundle:Hello:email.txt.twig', + // app/Resources/views/Emails/registration.html.twig + 'Emails/registration.html.twig', + array('name' => $name) + ), + 'text/html' + ) + /* + * If you also want to include a plaintext version of the message + ->addPart( + $this->renderView( + 'Emails/registration.txt.twig', array('name' => $name) - ) + ), + 'text/plain' ) + */ ; - $this->get('mailer')->send($message); + $mailer->send($message); return $this->render(...); } @@ -137,3 +140,6 @@ of `Creating Messages`_ in great detail in its documentation. .. _`Swift Mailer`: http://swiftmailer.org/ .. _`Creating Messages`: http://swiftmailer.org/docs/messages.html +.. _`Mandrill`: https://mandrill.com/ +.. _`SendGrid`: https://sendgrid.com/ +.. _`Amazon SES`: http://aws.amazon.com/ses/ diff --git a/cookbook/email/gmail.rst b/cookbook/email/gmail.rst index 1565dc00c28..6a64a82e1ea 100644 --- a/cookbook/email/gmail.rst +++ b/cookbook/email/gmail.rst @@ -33,8 +33,10 @@ In the development configuration file, change the ``transport`` setting to + xsi:schemaLocation="http://symfony.com/schema/dic/services + http://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/swiftmailer + http://symfony.com/schema/dic/swiftmailer/swiftmailer-1.0.xsd"> @@ -49,7 +50,7 @@ swiftmailer with the memory option, use the following configuration: // app/config/config.php $container->loadFromExtension('swiftmailer', array( - ..., + // ... 'spool' => array('type' => 'memory') )); @@ -75,7 +76,8 @@ In order to use the spool with a file, use the following configuration: @@ -99,7 +101,7 @@ In order to use the spool with a file, use the following configuration: .. tip:: If you want to store the spool somewhere with your project directory, - remember that you can use the `%kernel.root_dir%` parameter to reference + remember that you can use the ``%kernel.root_dir%`` parameter to reference the project's root: .. code-block:: yaml diff --git a/cookbook/email/testing.rst b/cookbook/email/testing.rst index db6e717ec36..d5fce3b4186 100644 --- a/cookbook/email/testing.rst +++ b/cookbook/email/testing.rst @@ -33,7 +33,7 @@ Start with an easy controller action that sends an e-mail:: In your functional test, use the ``swiftmailer`` collector on the profiler to get information about the messages send on the previous request:: - // src/Acme/DemoBundle/Tests/Controller/MailControllerTest.php + // src/AppBundle/Tests/Controller/MailControllerTest.php use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; class MailControllerTest extends WebTestCase diff --git a/cookbook/event_dispatcher/before_after_filters.rst b/cookbook/event_dispatcher/before_after_filters.rst index 569f0c7cce5..262365037b5 100644 --- a/cookbook/event_dispatcher/before_after_filters.rst +++ b/cookbook/event_dispatcher/before_after_filters.rst @@ -9,7 +9,7 @@ executed just before or just after your controller actions acting as filters or hooks. In symfony1, this was achieved with the preExecute and postExecute methods. -Most major frameworks have similar methods but there is no such thing in Symfony2. +Most major frameworks have similar methods but there is no such thing in Symfony. The good news is that there is a much better way to interfere with the Request -> Response process using the :doc:`EventDispatcher component `. @@ -74,7 +74,7 @@ controller that matches the request needs token validation. A clean and easy way is to create an empty interface and make the controllers implement it:: - namespace Acme\DemoBundle\Controller; + namespace AppBundle\Controller; interface TokenAuthenticatedController { @@ -83,9 +83,9 @@ implement it:: A controller that implements this interface simply looks like this:: - namespace Acme\DemoBundle\Controller; + namespace AppBundle\Controller; - use Acme\DemoBundle\Controller\TokenAuthenticatedController; + use AppBundle\Controller\TokenAuthenticatedController; use Symfony\Bundle\FrameworkBundle\Controller\Controller; class FooController extends Controller implements TokenAuthenticatedController @@ -104,10 +104,10 @@ Next, you'll need to create an event listener, which will hold the logic that you want executed before your controllers. If you're not familiar with event listeners, you can learn more about them at :doc:`/cookbook/service_container/event_listener`:: - // src/Acme/DemoBundle/EventListener/TokenListener.php - namespace Acme\DemoBundle\EventListener; + // src/AppBundle/EventListener/TokenListener.php + namespace AppBundle\EventListener; - use Acme\DemoBundle\Controller\TokenAuthenticatedController; + use AppBundle\Controller\TokenAuthenticatedController; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\HttpKernel\Event\FilterControllerEvent; @@ -125,7 +125,8 @@ event listeners, you can learn more about them at :doc:`/cookbook/service_contai $controller = $event->getController(); /* - * $controller passed can be either a class or a Closure. This is not usual in Symfony but it may happen. + * $controller passed can be either a class or a Closure. + * This is not usual in Symfony but it may happen. * If it is a class, it comes in array format */ if (!is_array($controller)) { @@ -152,33 +153,33 @@ your listener to be called just before any controller is executed. .. code-block:: yaml - # app/config/config.yml (or inside your services.yml) + # app/config/services.yml services: - demo.tokens.action_listener: - class: Acme\DemoBundle\EventListener\TokenListener + app.tokens.action_listener: + class: AappBundle\EventListener\TokenListener arguments: ["%tokens%"] tags: - { name: kernel.event_listener, event: kernel.controller, method: onKernelController } .. code-block:: xml - - + + %tokens% .. code-block:: php - // app/config/config.php (or inside your services.php) + // app/config/services.php use Symfony\Component\DependencyInjection\Definition; - $listener = new Definition('Acme\DemoBundle\EventListener\TokenListener', array('%tokens%')); + $listener = new Definition('AppBundle\EventListener\TokenListener', array('%tokens%')); $listener->addTag('kernel.event_listener', array( 'event' => 'kernel.controller', 'method' => 'onKernelController' )); - $container->setDefinition('demo.tokens.action_listener', $listener); + $container->setDefinition('app.tokens.action_listener', $listener); With this configuration, your ``TokenListener`` ``onKernelController`` method will be executed on each request. If the controller that is about to be executed @@ -189,7 +190,7 @@ want. After Filters with the ``kernel.response`` Event ------------------------------------------------ -In addition to having a "hook" that's executed before your controller, you +In addition to having a "hook" that's executed *before* your controller, you can also add a hook that's executed *after* your controller. For this example, imagine that you want to add a sha1 hash (with a salt using that token) to all responses that have passed this token authentication. @@ -247,10 +248,10 @@ event: .. code-block:: yaml - # app/config/config.yml (or inside your services.yml) + # app/config/services.yml services: - demo.tokens.action_listener: - class: Acme\DemoBundle\EventListener\TokenListener + app.tokens.action_listener: + class: AppBundle\EventListener\TokenListener arguments: ["%tokens%"] tags: - { name: kernel.event_listener, event: kernel.controller, method: onKernelController } @@ -258,8 +259,8 @@ event: .. code-block:: xml - - + + %tokens% @@ -267,10 +268,10 @@ event: .. code-block:: php - // app/config/config.php (or inside your services.php) + // app/config/services.php use Symfony\Component\DependencyInjection\Definition; - $listener = new Definition('Acme\DemoBundle\EventListener\TokenListener', array('%tokens%')); + $listener = new Definition('AppBundle\EventListener\TokenListener', array('%tokens%')); $listener->addTag('kernel.event_listener', array( 'event' => 'kernel.controller', 'method' => 'onKernelController' @@ -279,7 +280,7 @@ event: 'event' => 'kernel.response', 'method' => 'onKernelResponse' )); - $container->setDefinition('demo.tokens.action_listener', $listener); + $container->setDefinition('app.tokens.action_listener', $listener); That's it! The ``TokenListener`` is now notified before every controller is executed (``onKernelController``) and after every controller returns a response diff --git a/cookbook/expression/expressions.rst b/cookbook/expression/expressions.rst index aba6431edfc..63fc728343c 100644 --- a/cookbook/expression/expressions.rst +++ b/cookbook/expression/expressions.rst @@ -4,9 +4,6 @@ How to use Expressions in Security, Routing, Services, and Validation ===================================================================== -.. versionadded:: 2.4 - The expression functionality was introduced in Symfony 2.4. - In Symfony 2.4, a powerful :doc:`ExpressionLanguage ` component was added to Symfony. This allows us to add highly customized logic inside configuration. @@ -16,9 +13,96 @@ ways: * :ref:`Configuring services `; * :ref:`Route matching conditions `; -* :ref:`Checking security ` and +* :ref:`Checking security ` (explained below) and :ref:`access controls with allow_if `; * :doc:`Validation `. For more information about how to create and work with expressions, see :doc:`/components/expression_language/syntax`. + +.. _book-security-expressions: + +Security: Complex Access Controls with Expressions +-------------------------------------------------- + +In addition to a role like ``ROLE_ADMIN``, the ``isGranted`` method also +accepts an :class:`Symfony\\Component\\ExpressionLanguage\\Expression` object:: + + use Symfony\Component\ExpressionLanguage\Expression; + // ... + + public function indexAction() + { + if (!$this->get('security.authorization_checker')->isGranted(new Expression( + '"ROLE_ADMIN" in roles or (user and user.isSuperAdmin())' + ))) { + throw $this->createAccessDeniedException(); + } + + // ... + } + +In this example, if the current user has ``ROLE_ADMIN`` or if the current +user object's ``isSuperAdmin()`` method returns ``true``, then access will +be granted (note: your User object may not have an ``isSuperAdmin`` method, +that method is invented for this example). + +This uses an expression and you can learn more about the expression language +syntax, see :doc:`/components/expression_language/syntax`. + +.. _book-security-expression-variables: + +Inside the expression, you have access to a number of variables: + +``user`` + The user object (or the string ``anon`` if you're not authenticated). +``roles`` + The array of roles the user has, including from the + :ref:`role hierarchy ` but not including the + ``IS_AUTHENTICATED_*`` attributes (see the functions below). +``object`` + The object (if any) that's passed as the second argument to ``isGranted``. +``token`` + The token object. +``trust_resolver`` + The :class:`Symfony\\Component\\Security\\Core\\Authentication\\AuthenticationTrustResolverInterface`, + object: you'll probably use the ``is_*`` functions below instead. + +Additionally, you have access to a number of functions inside the expression: + +``is_authenticated`` + Returns ``true`` if the user is authenticated via "remember-me" or authenticated + "fully" - i.e. returns true if the user is "logged in". +``is_anonymous`` + Equal to using ``IS_AUTHENTICATED_ANONYMOUSLY`` with the ``isGranted`` function. +``is_remember_me`` + Similar, but not equal to ``IS_AUTHENTICATED_REMEMBERED``, see below. +``is_fully_authenticated`` + Similar, but not equal to ``IS_AUTHENTICATED_FULLY``, see below. +``has_role`` + Checks to see if the user has the given role - equivalent to an expression like + ``'ROLE_ADMIN' in roles``. + +.. sidebar:: ``is_remember_me`` is different than checking ``IS_AUTHENTICATED_REMEMBERED`` + + The ``is_remember_me`` and ``is_authenticated_fully`` functions are *similar* + to using ``IS_AUTHENTICATED_REMEMBERED`` and ``IS_AUTHENTICATED_FULLY`` + with the ``isGranted`` function - but they are **not** the same. The + following shows the difference:: + + use Symfony\Component\ExpressionLanguage\Expression; + // ... + + $ac = $this->get('security.authorization_checker'); + $access1 = $ac->isGranted('IS_AUTHENTICATED_REMEMBERED'); + + $access2 = $ac->isGranted(new Expression( + 'is_remember_me() or is_fully_authenticated()' + )); + + Here, ``$access1`` and ``$access2`` will be the same value. Unlike the + behavior of ``IS_AUTHENTICATED_REMEMBERED`` and ``IS_AUTHENTICATED_FULLY``, + the ``is_remember_me`` function *only* returns true if the user is authenticated + via a remember-me cookie and ``is_fully_authenticated`` *only* returns + true if the user has actually logged in during this session (i.e. is + full-fledged). diff --git a/cookbook/form/create_custom_field_type.rst b/cookbook/form/create_custom_field_type.rst index 3cb5f33c265..62d1c454eeb 100644 --- a/cookbook/form/create_custom_field_type.rst +++ b/cookbook/form/create_custom_field_type.rst @@ -20,8 +20,8 @@ will be called ``GenderType`` and the file will be stored in the default locatio for form fields, which is ``\Form\Type``. Make sure the field extends :class:`Symfony\\Component\\Form\\AbstractType`:: - // src/Acme/DemoBundle/Form/Type/GenderType.php - namespace Acme\DemoBundle\Form\Type; + // src/AppBundle/Form/Type/GenderType.php + namespace AppBundle\Form\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\OptionsResolver\OptionsResolverInterface; @@ -60,20 +60,23 @@ all of the logic and rendering of that field type. To see some of the logic, check out the `ChoiceType`_ class. There are three methods that are particularly important: -* ``buildForm()`` - Each field type has a ``buildForm`` method, which is where - you configure and build any field(s). Notice that this is the same method - you use to setup *your* forms, and it works the same here. +``buildForm()`` + Each field type has a ``buildForm`` method, which is where + you configure and build any field(s). Notice that this is the same method + you use to setup *your* forms, and it works the same here. -* ``buildView()`` - This method is used to set any extra variables you'll - need when rendering your field in a template. For example, in `ChoiceType`_, - a ``multiple`` variable is set and used in the template to set (or not - set) the ``multiple`` attribute on the ``select`` field. See `Creating a Template for the Field`_ - for more details. +``buildView()`` + This method is used to set any extra variables you'll + need when rendering your field in a template. For example, in `ChoiceType`_, + a ``multiple`` variable is set and used in the template to set (or not + set) the ``multiple`` attribute on the ``select`` field. See `Creating a Template for the Field`_ + for more details. -* ``setDefaultOptions()`` - This defines options for your form type that - can be used in ``buildForm()`` and ``buildView()``. There are a lot of - options common to all fields (see :doc:`/reference/forms/types/form`), - but you can create any others that you need here. +``setDefaultOptions()`` + This defines options for your form type that + can be used in ``buildForm()`` and ``buildView()``. There are a lot of + options common to all fields (see :doc:`/reference/forms/types/form`), + but you can create any others that you need here. .. tip:: @@ -108,7 +111,7 @@ link for details), create a ``gender_widget`` block to handle this: .. code-block:: html+jinja - {# src/Acme/DemoBundle/Resources/views/Form/fields.html.twig #} + {# src/AppBundle/Resources/views/Form/fields.html.twig #} {% block gender_widget %} {% spaceless %} {% if expanded %} @@ -129,7 +132,7 @@ link for details), create a ``gender_widget`` block to handle this: .. code-block:: html+php - +
      block($form, 'widget_container_attributes') ?>> @@ -159,27 +162,22 @@ link for details), create a ``gender_widget`` block to handle this: # app/config/config.yml twig: - form: - resources: - - 'AcmeDemoBundle:Form:fields.html.twig' + form_themes: + - 'AppBundle:Form:fields.html.twig' .. code-block:: xml - - AcmeDemoBundle:Form:fields.html.twig - + AppBundle:Form:fields.html.twig .. code-block:: php // app/config/config.php $container->loadFromExtension('twig', array( - 'form' => array( - 'resources' => array( - 'AcmeDemoBundle:Form:fields.html.twig', - ), + 'form_themes' => array( + 'AppBundle:Form:fields.html.twig', ), )); @@ -194,7 +192,7 @@ link for details), create a ``gender_widget`` block to handle this: templating: form: resources: - - 'AcmeDemoBundle:Form' + - 'AppBundle:Form' .. code-block:: xml @@ -209,7 +207,7 @@ link for details), create a ``gender_widget`` block to handle this: - AcmeDemoBundle:Form + AppBundle:Form @@ -222,7 +220,7 @@ link for details), create a ``gender_widget`` block to handle this: 'templating' => array( 'form' => array( 'resources' => array( - 'AcmeDemoBundle:Form', + 'AppBundle:Form', ), ), ), @@ -234,8 +232,8 @@ Using the Field Type You can now use your custom field type immediately, simply by creating a new instance of the type in one of your forms:: - // src/Acme/DemoBundle/Form/Type/AuthorType.php - namespace Acme\DemoBundle\Form\Type; + // src/AppBundle/Form/Type/AuthorType.php + namespace AppBundle\Form\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; @@ -245,7 +243,7 @@ new instance of the type in one of your forms:: public function buildForm(FormBuilderInterface $builder, array $options) { $builder->add('gender_code', new GenderType(), array( - 'empty_value' => 'Choose a gender', + 'placeholder' => 'Choose a gender', )); } } @@ -254,6 +252,10 @@ But this only works because the ``GenderType()`` is very simple. What if the gender codes were stored in configuration or in a database? The next section explains how more complex field types solve this problem. +.. versionadded:: 2.6 + The ``placeholder`` option was introduced in Symfony 2.6 in favor of + ``empty_value``, which is available prior to 2.6. + .. _form-cookbook-form-field-service: Creating your Field Type as a Service @@ -298,10 +300,10 @@ the ``genders`` parameter value as the first argument to its to-be-created .. code-block:: yaml - # src/Acme/DemoBundle/Resources/config/services.yml + # src/AppBundle/Resources/config/services.yml services: - acme_demo.form.type.gender: - class: Acme\DemoBundle\Form\Type\GenderType + app.form.type.gender: + class: AppBundle\Form\Type\GenderType arguments: - "%genders%" tags: @@ -309,20 +311,20 @@ the ``genders`` parameter value as the first argument to its to-be-created .. code-block:: xml - - + + %genders% .. code-block:: php - // src/Acme/DemoBundle/Resources/config/services.php + // src/AppBundle/Resources/config/services.php use Symfony\Component\DependencyInjection\Definition; $container - ->setDefinition('acme_demo.form.type.gender', new Definition( - 'Acme\DemoBundle\Form\Type\GenderType', + ->setDefinition('app.form.type.gender', new Definition( + 'AppBundle\Form\Type\GenderType', array('%genders%') )) ->addTag('form.type', array( @@ -340,8 +342,8 @@ returned by the ``getName`` method defined earlier. You'll see the importance of this in a moment when you use the custom field type. But first, add a ``__construct`` method to ``GenderType``, which receives the gender configuration:: - // src/Acme/DemoBundle/Form/Type/GenderType.php - namespace Acme\DemoBundle\Form\Type; + // src/AppBundle/Form/Type/GenderType.php + namespace AppBundle\Form\Type; use Symfony\Component\OptionsResolver\OptionsResolverInterface; @@ -371,8 +373,8 @@ Great! The ``GenderType`` is now fueled by the configuration parameters and registered as a service. Additionally, because you used the ``form.type`` alias in its configuration, using the field is now much easier:: - // src/Acme/DemoBundle/Form/Type/AuthorType.php - namespace Acme\DemoBundle\Form\Type; + // src/AppBundle/Form/Type/AuthorType.php + namespace AppBundle\Form\Type; use Symfony\Component\Form\FormBuilderInterface; @@ -383,7 +385,7 @@ configuration, using the field is now much easier:: public function buildForm(FormBuilderInterface $builder, array $options) { $builder->add('gender_code', 'gender', array( - 'empty_value' => 'Choose a gender', + 'placeholder' => 'Choose a gender', )); } } diff --git a/cookbook/form/direct_submit.rst b/cookbook/form/direct_submit.rst index 5a40a0aec3f..221f7f534d8 100644 --- a/cookbook/form/direct_submit.rst +++ b/cookbook/form/direct_submit.rst @@ -37,6 +37,8 @@ submissions:: To see more about this method, read :ref:`book-form-handling-form-submissions`. +.. _cookbook-form-call-submit-directly: + Calling Form::submit() manually ------------------------------- diff --git a/cookbook/form/dynamic_form_modification.rst b/cookbook/form/dynamic_form_modification.rst index 36b4b9e9800..faf2608b162 100644 --- a/cookbook/form/dynamic_form_modification.rst +++ b/cookbook/form/dynamic_form_modification.rst @@ -36,8 +36,8 @@ Customizing your Form Based on the Underlying Data Before jumping right into dynamic form generation, hold on and recall what a bare form class looks like:: - // src/Acme/DemoBundle/Form/Type/ProductType.php - namespace Acme\DemoBundle\Form\Type; + // src/AppBundle/Form/Type/ProductType.php + namespace AppBundle\Form\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; @@ -54,7 +54,7 @@ a bare form class looks like:: public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( - 'data_class' => 'Acme\DemoBundle\Entity\Product' + 'data_class' => 'AppBundle\Entity\Product' )); } @@ -90,8 +90,8 @@ Adding an Event Listener to a Form Class So, instead of directly adding that ``name`` widget, the responsibility of creating that particular field is delegated to an event listener:: - // src/Acme/DemoBundle/Form/Type/ProductType.php - namespace Acme\DemoBundle\Form\Type; + // src/AppBundle/Form/Type/ProductType.php + namespace AppBundle\Form\Type; // ... use Symfony\Component\Form\FormEvent; @@ -133,11 +133,6 @@ the event listener might look like the following:: }); } -.. versionadded:: 2.2 - The ability to pass a string into - :method:`FormInterface::add ` - was introduced in Symfony 2.2. - .. note:: The ``FormEvents::PRE_SET_DATA`` line actually resolves to the string @@ -156,11 +151,11 @@ For better reusability or if there is some heavy logic in your event listener, you can also move the logic for creating the ``name`` field to an :ref:`event subscriber `:: - // src/Acme/DemoBundle/Form/Type/ProductType.php - namespace Acme\DemoBundle\Form\Type; + // src/AppBundle/Form/Type/ProductType.php + namespace AppBundle\Form\Type; // ... - use Acme\DemoBundle\Form\EventListener\AddNameFieldSubscriber; + use AppBundle\Form\EventListener\AddNameFieldSubscriber; class ProductType extends AbstractType { @@ -177,8 +172,8 @@ you can also move the logic for creating the ``name`` field to an Now the logic for creating the ``name`` field resides in it own subscriber class:: - // src/Acme/DemoBundle/Form/EventListener/AddNameFieldSubscriber.php - namespace Acme\DemoBundle\Form\EventListener; + // src/AppBundle/Form/EventListener/AddNameFieldSubscriber.php + namespace AppBundle\Form\EventListener; use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\FormEvents; @@ -221,14 +216,14 @@ Creating the Form Type Using an event listener, your form might look like this:: - // src/Acme/DemoBundle/Form/Type/FriendMessageFormType.php - namespace Acme\DemoBundle\Form\Type; + // src/AppBundle/Form/Type/FriendMessageFormType.php + namespace AppBundle\Form\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormEvent; - use Symfony\Component\Security\Core\SecurityContext; + use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\OptionsResolver\OptionsResolverInterface; class FriendMessageFormType extends AbstractType @@ -246,7 +241,7 @@ Using an event listener, your form might look like this:: public function getName() { - return 'acme_friend_message'; + return 'friend_message'; } public function setDefaultOptions(OptionsResolverInterface $resolver) @@ -260,17 +255,17 @@ contains only this user's friends. Luckily it is pretty easy to inject a service inside of the form. This can be done in the constructor:: - private $securityContext; + private $tokenStorage; - public function __construct(SecurityContext $securityContext) + public function __construct(TokenStorageInterface $tokenStorage) { - $this->securityContext = $securityContext; + $this->tokenStorage = $tokenStorage; } .. note:: - You might wonder, now that you have access to the User (through the security - context), why not just use it directly in ``buildForm`` and omit the + You might wonder, now that you have access to the User (through the token + storage), why not just use it directly in ``buildForm`` and omit the event listener? This is because doing so in the ``buildForm`` method would result in the whole form type being modified and not just this one form instance. This may not usually be a problem, but technically @@ -280,22 +275,22 @@ done in the constructor:: Customizing the Form Type ~~~~~~~~~~~~~~~~~~~~~~~~~ -Now that you have all the basics in place you can take advantage of the ``SecurityContext`` +Now that you have all the basics in place you can take advantage of the ``TokenStorageInterface`` and fill in the listener logic:: - // src/Acme/DemoBundle/FormType/FriendMessageFormType.php + // src/AppBundle/FormType/FriendMessageFormType.php - use Symfony\Component\Security\Core\SecurityContext; + use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Doctrine\ORM\EntityRepository; // ... class FriendMessageFormType extends AbstractType { - private $securityContext; + private $tokenStorage; - public function __construct(SecurityContext $securityContext) + public function __construct(TokenStorageInterface $tokenStorage) { - $this->securityContext = $securityContext; + $this->tokenStorage = $tokenStorage; } public function buildForm(FormBuilderInterface $builder, array $options) @@ -306,7 +301,7 @@ and fill in the listener logic:: ; // grab the user, do a quick sanity check that one exists - $user = $this->securityContext->getToken()->getUser(); + $user = $this->tokenStorage->getToken()->getUser(); if (!$user) { throw new \LogicException( 'The FriendMessageFormType cannot be used without an authenticated user!' @@ -319,7 +314,7 @@ and fill in the listener logic:: $form = $event->getForm(); $formOptions = array( - 'class' => 'Acme\DemoBundle\Entity\User', + 'class' => 'AppBundle\Entity\User', 'property' => 'fullName', 'query_builder' => function (EntityRepository $er) use ($user) { // build a custom query @@ -341,6 +336,11 @@ and fill in the listener logic:: // ... } +.. versionadded:: 2.6 + The :class:`Symfony\\Component\\Security\\Core\\Authentication\\Token\\Storage\\TokenStorageInterface` was + introduced in Symfony 2.6. Prior, you had to use the ``getToken()`` method of + :class:`Symfony\\Component\\Security\\Core\\SecurityContextInterface`. + .. note:: The ``multiple`` and ``expanded`` form options will default to false @@ -352,7 +352,7 @@ Using the Form Our form is now ready to use and there are two possible ways to use it inside of a controller: -a) create it manually and remember to pass the security context to it; +a) create it manually and remember to pass the token storage to it; or @@ -368,9 +368,9 @@ your new form type in many places or embedding it into other forms:: { public function newAction(Request $request) { - $securityContext = $this->container->get('security.context'); + $tokenStorage = $this->container->get('security.token_storage'); $form = $this->createForm( - new FriendMessageFormType($securityContext) + new FriendMessageFormType($tokenStorage) ); // ... @@ -389,31 +389,31 @@ it with :ref:`dic-tags-form-type`. # app/config/config.yml services: - acme.form.friend_message: - class: Acme\DemoBundle\Form\Type\FriendMessageFormType - arguments: ["@security.context"] + app.form.friend_message: + class: AppBundle\Form\Type\FriendMessageFormType + arguments: ["@security.token_storage"] tags: - - { name: form.type, alias: acme_friend_message } + - { name: form.type, alias: friend_message } .. code-block:: xml - + - + .. code-block:: php // app/config/config.php - $definition = new Definition('Acme\DemoBundle\Form\Type\FriendMessageFormType'); - $definition->addTag('form.type', array('alias' => 'acme_friend_message')); + $definition = new Definition('AppBundle\Form\Type\FriendMessageFormType'); + $definition->addTag('form.type', array('alias' => 'friend_message')); $container->setDefinition( - 'acme.form.friend_message', + 'app.form.friend_message', $definition, - array('security.context') + array('security.token_storage') ); If you wish to create it from within a controller or any other service that has @@ -425,7 +425,7 @@ access to the form factory, you then use:: { public function newAction(Request $request) { - $form = $this->get('form.factory')->create('acme_friend_message'); + $form = $this->get('form.factory')->create('friend_message'); // ... } @@ -433,14 +433,14 @@ access to the form factory, you then use:: If you extend the ``Symfony\Bundle\FrameworkBundle\Controller\Controller`` class, you can simply call:: - $form = $this->createForm('acme_friend_message'); + $form = $this->createForm('friend_message'); You can also easily embed the form type into another form:: // inside some other "form type" class public function buildForm(FormBuilderInterface $builder, array $options) { - $builder->add('message', 'acme_friend_message'); + $builder->add('message', 'friend_message'); } .. _cookbook-form-events-submitted-data: @@ -459,8 +459,8 @@ will need the correct options in order for validation to pass. The meetup is passed as an entity field to the form. So we can access each sport like this:: - // src/Acme/DemoBundle/Form/Type/SportMeetupType.php - namespace Acme\DemoBundle\Form\Type; + // src/AppBundle/Form/Type/SportMeetupType.php + namespace AppBundle\Form\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; @@ -474,8 +474,8 @@ sport like this:: { $builder ->add('sport', 'entity', array( - 'class' => 'AcmeDemoBundle:Sport', - 'empty_value' => '', + 'class' => 'AppBundle:Sport', + 'placeholder' => '', )) ; @@ -491,8 +491,8 @@ sport like this:: $positions = null === $sport ? array() : $sport->getAvailablePositions(); $form->add('position', 'entity', array( - 'class' => 'AcmeDemoBundle:Position', - 'empty_value' => '', + 'class' => 'AppBundle:Position', + 'placeholder' => '', 'choices' => $positions, )); } @@ -502,6 +502,10 @@ sport like this:: // ... } +.. versionadded:: 2.6 + The ``placeholder`` option was introduced in Symfony 2.6 in favor of + ``empty_value``, which is available prior to 2.6. + When you're building this form to display to the user for the first time, then this example works perfectly. @@ -521,10 +525,6 @@ On a form, we can usually listen to the following events: The events ``PRE_SUBMIT``, ``SUBMIT`` and ``POST_SUBMIT`` were introduced in Symfony 2.3. Before, they were named ``PRE_BIND``, ``BIND`` and ``POST_BIND``. -.. versionadded:: 2.2.6 - The behavior of the ``POST_SUBMIT`` event changed slightly in 2.2.6, which the - below example uses. - The key is to add a ``POST_SUBMIT`` listener to the field that your new field depends on. If you add a ``POST_SUBMIT`` listener to a form child (e.g. ``sport``), and add new children to the parent form, the Form component will detect the @@ -532,12 +532,12 @@ new field automatically and map it to the submitted client data. The type would now look like:: - // src/Acme/DemoBundle/Form/Type/SportMeetupType.php - namespace Acme\DemoBundle\Form\Type; + // src/AppBundle/Form/Type/SportMeetupType.php + namespace AppBundle\Form\Type; // ... use Symfony\Component\Form\FormInterface; - use Acme\DemoBundle\Entity\Sport; + use AppBundle\Entity\Sport; class SportMeetupType extends AbstractType { @@ -545,8 +545,8 @@ The type would now look like:: { $builder ->add('sport', 'entity', array( - 'class' => 'AcmeDemoBundle:Sport', - 'empty_value' => '', + 'class' => 'AppBundle:Sport', + 'placeholder' => '', )); ; @@ -554,8 +554,8 @@ The type would now look like:: $positions = null === $sport ? array() : $sport->getAvailablePositions(); $form->add('position', 'entity', array( - 'class' => 'AcmeDemoBundle:Position', - 'empty_value' => '', + 'class' => 'AppBundle:Position', + 'placeholder' => '', 'choices' => $positions, )); }; @@ -596,13 +596,13 @@ One piece that is still missing is the client-side updating of your form after the sport is selected. This should be handled by making an AJAX call back to your application. Assume that you have a sport meetup creation controller:: - // src/Acme/DemoBundle/Controller/MeetupController.php - namespace Acme\DemoBundle\Controller; + // src/AppBundle/Controller/MeetupController.php + namespace AppBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Request; - use Acme\DemoBundle\Entity\SportMeetup; - use Acme\DemoBundle\Form\Type\SportMeetupType; + use AppBundle\Entity\SportMeetup; + use AppBundle\Form\Type\SportMeetupType; // ... class MeetupController extends Controller @@ -617,7 +617,7 @@ your application. Assume that you have a sport meetup creation controller:: } return $this->render( - 'AcmeDemoBundle:Meetup:create.html.twig', + 'AppBundle:Meetup:create.html.twig', array('form' => $form->createView()) ); } @@ -632,7 +632,7 @@ field according to the current selection in the ``sport`` field: .. code-block:: html+jinja - {# src/Acme/DemoBundle/Resources/views/Meetup/create.html.twig #} + {# src/AppBundle/Resources/views/Meetup/create.html.twig #} {{ form_start(form) }} {{ form_row(form.sport) }} {# + + + + + {# + If you want to control the URL the user + is redirected to on success (more details below) + + #} + + + + + .. code-block:: html+php + + + +
      getMessage() ?>
      + + +
      + + + + + + + + + +
      + + +.. tip:: + + The ``error`` variable passed into the template is an instance of + :class:`Symfony\\Component\\Security\\Core\\Exception\\AuthenticationException`. + It may contain more information - or even sensitive information - about + the authentication failure, so use it wisely! + +The form can look like anything, but has a few requirements: + +* The form must POST to ``/login_check``, since that's what you configured + under the ``form_login`` key in ``security.yml``. + +* The username must have the name ``_username`` and the password must have + the name ``_password``. + +.. tip:: + + Actually, all of this can be configured under the ``form_login`` key. See + :ref:`reference-security-firewall-form-login` for more details. + +.. caution:: + + This login form is currently not protected against CSRF attacks. Read + :doc:`/cookbook/security/csrf_in_login_form` on how to protect your login + form. + +And that's it! When you submit the form, the security system will automatically +check the user's credentials and either authenticate the user or send the +user back to the login form where the error can be displayed. + +To review the whole process: + +#. The user tries to access a resource that is protected; +#. The firewall initiates the authentication process by redirecting the + user to the login form (``/login``); +#. The ``/login`` page renders login form via the route and controller created + in this example; +#. The user submits the login form to ``/login_check``; +#. The security system intercepts the request, checks the user's submitted + credentials, authenticates the user if they are correct, and sends the + user back to the login form if they are not. + +Redirecting after Success +------------------------- + +If the submitted credentials are correct, the user will be redirected to +the original page that was requested (e.g. ``/admin/foo``). If the user originally +went straight to the login page, they'll be redirected to the homepage. This +can all be customized, allowing you to, for example, redirect the user to +a specific URL. + +For more details on this and how to customize the form login process in general, +see :doc:`/cookbook/security/form_login`. + +.. _book-security-common-pitfalls: + +Avoid common Pitfalls +--------------------- + +When setting up your login form, watch out for a few common pitfalls. + +**1. Create the correct routes** + +First, be sure that you've defined the ``/login`` and ``/login_check`` +routes correctly and that they correspond to the ``login_path`` and +``check_path`` config values. A misconfiguration here can mean that you're +redirected to a 404 page instead of the login page, or that submitting +the login form does nothing (you just see the login form over and over +again). + +**2. Be sure the login page isn't secure (redirect loop!)** + +Also, be sure that the login page is accessible by anonymous users. For example, +the following configuration - which requires the ``ROLE_ADMIN`` role for +all URLs (including the ``/login`` URL), will cause a redirect loop: + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/security.yml + + # ... + access_control: + - { path: ^/, roles: ROLE_ADMIN } + + .. code-block:: xml + + + + + + + + + .. code-block:: php + + // app/config/security.php + + // ... + 'access_control' => array( + array('path' => '^/', 'role' => 'ROLE_ADMIN'), + ), + +Adding an access control that matches ``/login/*`` and requires *no* authentication +fixes the problem: + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/security.yml + + # ... + access_control: + - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY } + - { path: ^/, roles: ROLE_ADMIN } + + .. code-block:: xml + + + + + + + + + + .. code-block:: php + + // app/config/security.php + + // ... + 'access_control' => array( + array('path' => '^/login', 'role' => 'IS_AUTHENTICATED_ANONYMOUSLY'), + array('path' => '^/', 'role' => 'ROLE_ADMIN'), + ), + +Also, if your firewall does *not* allow for anonymous users (no ``anonymous`` +key), you'll need to create a special firewall that allows anonymous users +for the login page: + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/security.yml + + # ... + firewalls: + # order matters! This must be before the ^/ firewall + login_firewall: + pattern: ^/login$ + anonymous: ~ + secured_area: + pattern: ^/ + form_login: ~ + + .. code-block:: xml + + + + + + + + + + + + .. code-block:: php + + // app/config/security.php + + // ... + 'firewalls' => array( + 'login_firewall' => array( + 'pattern' => '^/login$', + 'anonymous' => array(), + ), + 'secured_area' => array( + 'pattern' => '^/', + 'form_login' => array(), + ), + ), + +**3. Be sure /login_check is behind a firewall** + +Next, make sure that your ``check_path`` URL (e.g. ``/login_check``) is behind +the firewall you're using for your form login (in this example, the single +firewall matches *all* URLs, including ``/login_check``). If ``/login_check`` +doesn't match any firewall, you'll receive a ``Unable to find the controller +for path "/login_check"`` exception. + +**4. Multiple firewalls don't share the same security context** + +If you're using multiple firewalls and you authenticate against one firewall, +you will *not* be authenticated against any other firewalls automatically. +Different firewalls are like different security systems. To do this you have +to explicitly specify the same :ref:`reference-security-firewall-context` +for different firewalls. But usually for most applications, having one +main firewall is enough. + +**5. Routing error pages are not covered by firewalls** + +As routing is done *before* security, 404 error pages are not covered by +any firewall. This means you can't check for security or even access the +user object on these pages. See :doc:`/cookbook/controller/error_pages` +for more details. + +.. _`FOSUserBundle`: https://github.com/FriendsOfSymfony/FOSUserBundle diff --git a/cookbook/security/host_restriction.rst b/cookbook/security/host_restriction.rst index 2dea53f155e..0ee0d334e30 100644 --- a/cookbook/security/host_restriction.rst +++ b/cookbook/security/host_restriction.rst @@ -1,4 +1,6 @@ How to Restrict Firewalls to a Specific Host ============================================ -This entry has moved to ":doc:`/cookbook/security/firewall_restriction`". +As of Symfony 2.5, more possibilities to restrict firewalls have been added. +You can read everything about all the possibilities (including ``host``) +in ":doc:`/cookbook/security/firewall_restriction`". diff --git a/cookbook/security/impersonating_user.rst b/cookbook/security/impersonating_user.rst index c3bd0b708c6..8966357a198 100644 --- a/cookbook/security/impersonating_user.rst +++ b/cookbook/security/impersonating_user.rst @@ -84,7 +84,7 @@ to show a link to exit impersonation: > Exit impersonation - + Of course, this feature needs to be made available to a small group of users. By default, access is restricted to users having the ``ROLE_ALLOWED_TO_SWITCH`` diff --git a/cookbook/security/index.rst b/cookbook/security/index.rst index cdf7f049e5f..79af0f7904c 100644 --- a/cookbook/security/index.rst +++ b/cookbook/security/index.rst @@ -4,6 +4,7 @@ Security .. toctree:: :maxdepth: 2 + form_login_setup entity_provider remember_me impersonating_user @@ -23,3 +24,6 @@ Security pre_authenticated target_path csrf_in_login_form + named_encoders + access_control + multiple_user_providers diff --git a/cookbook/security/multiple_user_providers.rst b/cookbook/security/multiple_user_providers.rst new file mode 100644 index 00000000000..4766ed92e44 --- /dev/null +++ b/cookbook/security/multiple_user_providers.rst @@ -0,0 +1,148 @@ +How to Use multiple User Providers +================================== + +Each authentication mechanism (e.g. HTTP Authentication, form login, etc) +uses exactly one user provider, and will use the first declared user provider +by default. But what if you want to specify a few users via configuration +and the rest of your users in the database? This is possible by creating +a new provider that chains the two together: + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/security.yml + security: + providers: + chain_provider: + chain: + providers: [in_memory, user_db] + in_memory: + memory: + users: + foo: { password: test } + user_db: + entity: { class: Acme\UserBundle\Entity\User, property: username } + + .. code-block:: xml + + + + + + + + + in_memory + user_db + + + + + + + + + + + + + + .. code-block:: php + + // app/config/security.php + $container->loadFromExtension('security', array( + 'providers' => array( + 'chain_provider' => array( + 'chain' => array( + 'providers' => array('in_memory', 'user_db'), + ), + ), + 'in_memory' => array( + 'memory' => array( + 'users' => array( + 'foo' => array('password' => 'test'), + ), + ), + ), + 'user_db' => array( + 'entity' => array( + 'class' => 'Acme\UserBundle\Entity\User', + 'property' => 'username', + ), + ), + ), + )); + +Now, all authentication mechanisms will use the ``chain_provider``, since +it's the first specified. The ``chain_provider`` will, in turn, try to load +the user from both the ``in_memory`` and ``user_db`` providers. + +You can also configure the firewall or individual authentication mechanisms +to use a specific provider. Again, unless a provider is specified explicitly, +the first provider is always used: + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/security.yml + security: + firewalls: + secured_area: + # ... + pattern: ^/ + provider: user_db + http_basic: + realm: "Secured Demo Area" + provider: in_memory + form_login: ~ + + .. code-block:: xml + + + + + + + + + + + + + + + .. code-block:: php + + // app/config/security.php + $container->loadFromExtension('security', array( + 'firewalls' => array( + 'secured_area' => array( + // ... + 'pattern' => '^/', + 'provider' => 'user_db', + 'http_basic' => array( + // ... + 'provider' => 'in_memory', + ), + 'form_login' => array(), + ), + ), + )); + +In this example, if a user tries to log in via HTTP authentication, the authentication +system will use the ``in_memory`` user provider. But if the user tries to +log in via the form login, the ``user_db`` provider will be used (since it's +the default for the firewall as a whole). + +For more information about user provider and firewall configuration, see +the :doc:`/reference/configuration/security`. diff --git a/cookbook/security/named_encoders.rst b/cookbook/security/named_encoders.rst new file mode 100644 index 00000000000..3b22a2d9343 --- /dev/null +++ b/cookbook/security/named_encoders.rst @@ -0,0 +1,127 @@ +.. index:: + single: Security; Named Encoders + +How to Choose the Password Encoder Algorithm Dynamically +======================================================== + +.. versionadded:: 2.5 + Named encoders were introduced in Symfony 2.5. + +Usually, the same password encoder is used for all users by configuring it +to apply to all instances of a specific class: + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/security.yml + security: + # ... + encoders: + Symfony\Component\Security\Core\User\User: sha512 + + .. code-block:: xml + + + + + + + + + + + .. code-block:: php + + // app/config/security.php + $container->loadFromExtension('security', array( + // ... + 'encoders' => array( + 'Symfony\Component\Security\Core\User\User' => array( + 'algorithm' => 'sha512', + ), + ), + )); + +Another option is to use a "named" encoder and then select which encoder +you want to use dynamically. + +In the previous example, you've set the ``sha512`` algorithm for ``Acme\UserBundle\Entity\User``. +This may be secure enough for a regular user, but what if you want your admins +to have a stronger algorithm, for example ``bcrypt``. This can be done with +named encoders: + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/security.yml + security: + # ... + encoders: + harsh: + algorithm: bcrypt + cost: 15 + + .. code-block:: xml + + + + + + + + + + + + .. code-block:: php + + // app/config/security.php + $container->loadFromExtension('security', array( + // ... + 'encoders' => array( + 'harsh' => array( + 'algorithm' => 'bcrypt', + 'cost' => '15' + ), + ), + )); + +This creates an encoder named ``harsh``. In order for a ``User`` instance +to use it, the class must implement +:class:`Symfony\\Component\\Security\\Core\\Encoder\\EncoderAwareInterface`. +The interface requires one method - ``getEncoderName`` - which should return +the name of the encoder to use:: + + // src/Acme/UserBundle/Entity/User.php + namespace Acme\UserBundle\Entity; + + use Symfony\Component\Security\Core\User\UserInterface; + use Symfony\Component\Security\Core\Encoder\EncoderAwareInterface; + + class User implements UserInterface, EncoderAwareInterface + { + public function getEncoderName() + { + if ($this->isAdmin()) { + return 'harsh'; + } + + return null; // use the default encoder + } + } diff --git a/cookbook/security/pre_authenticated.rst b/cookbook/security/pre_authenticated.rst index fe77000422c..7a0775a8ab8 100644 --- a/cookbook/security/pre_authenticated.rst +++ b/cookbook/security/pre_authenticated.rst @@ -34,8 +34,8 @@ Enable the x509 authentication for a particular firewall in the security configu .. code-block:: xml - + @@ -66,14 +66,79 @@ the user provider, and sets the ``SSL_CLIENT_S_DN`` as credentials in the You can override these by setting the ``user`` and the ``credentials`` keys in the x509 firewall configuration respectively. +.. _cookbook-security-pre-authenticated-user-provider-note: + .. note:: An authentication provider will only inform the user provider of the username that made the request. You will need to create (or use) a "user provider" that is referenced by the ``provider`` configuration parameter (``your_user_provider`` - in the configuration example). This provider will turn the username into a User - object of your choice. For more information on creating or configuring a user + in the configuration example). This provider will turn the username into a User + object of your choice. For more information on creating or configuring a user provider, see: * :doc:`/cookbook/security/custom_provider` - * :doc:`/cookbook/security/entity_provider` \ No newline at end of file + * :doc:`/cookbook/security/entity_provider` + +REMOTE_USER Based Authentication +-------------------------------- + +.. versionadded:: 2.6 + REMOTE_USER pre authenticated firewall was introduced in Symfony 2.6. + +A lot of authentication modules, like ``auth_kerb`` for Apache provide the username +using the ``REMOTE_USER`` environment variable. This variable can be trusted by +the application since the authentication happened before the request reached it. + +To configure Symfony using the ``REMOTE_USER`` environment variable, simply enable the +corresponding firewall in your security configuration: + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/security.yml + security: + firewalls: + secured_area: + pattern: ^/ + remote_user: + provider: your_user_provider + + .. code-block:: xml + + + + + + + + + + + + + .. code-block:: php + + // app/config/security.php + $container->loadFromExtension('security', array( + 'firewalls' => array( + 'secured_area' => array( + 'pattern' => '^/' + 'remote_user' => array( + 'provider' => 'your_user_provider', + ), + ), + ), + )); + +The firewall will then provide the ``REMOTE_USER`` environment variable to +your user provider. You can change the variable name used by setting the ``user`` +key in the ``remote_user`` firewall configuration. + +.. note:: + + Just like for X509 authentication, you will need to configure a "user provider". + See :ref:`the note previous note ` + for more information. diff --git a/cookbook/security/remember_me.rst b/cookbook/security/remember_me.rst index 7efb39f277d..b6eefdebc9b 100644 --- a/cookbook/security/remember_me.rst +++ b/cookbook/security/remember_me.rst @@ -61,7 +61,7 @@ remember me functionality, as it will not always be appropriate. The usual way of doing this is to add a checkbox to the login form. By giving the checkbox the name ``_remember_me``, the cookie will automatically be set when the checkbox is checked and the user successfully logs in. So, your specific login form -might ultimately looks like this: +might ultimately look like this: .. configuration-block:: @@ -90,7 +90,7 @@ might ultimately looks like this:
      getMessage() ?>
      - +
      @@ -162,7 +162,7 @@ In the following example, the action is only allowed if the user has the public function editAction() { - if (false === $this->get('security.context')->isGranted( + if (false === $this->get('security.authorization_checker')->isGranted( 'IS_AUTHENTICATED_FULLY' )) { throw new AccessDeniedException(); @@ -171,21 +171,29 @@ In the following example, the action is only allowed if the user has the // ... } -You can also choose to install and use the optional JMSSecurityExtraBundle_, -which can secure your controller using annotations: +.. versionadded:: 2.6 + The ``security.authorization_checker`` service was introduced in Symfony 2.6. Prior + to Symfony 2.6, you had to use the ``isGranted()`` method of the ``security.context`` service. + +If your application is based on the Symfony Standard Edition, you can also secure +your controller using annotations: .. code-block:: php - use JMS\SecurityExtraBundle\Annotation\Secure; + use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security; /** - * @Secure(roles="IS_AUTHENTICATED_FULLY") + * @Security("has_role('IS_AUTHENTICATED_FULLY')") */ public function editAction($name) { // ... } +.. versionadded:: 2.4 + The ``@Security`` annotation was introduced in SensioFrameworkExtraBundle 3.0, + which can only be used with Symfony 2.4 or later. + .. tip:: If you also had an access control in your security configuration that @@ -208,5 +216,3 @@ which can secure your controller using annotations: For more information on securing services or methods in this way, see :doc:`/cookbook/security/securing_services`. - -.. _JMSSecurityExtraBundle: https://github.com/schmittjoh/JMSSecurityExtraBundle diff --git a/cookbook/security/securing_services.rst b/cookbook/security/securing_services.rst index 641a43f04ec..f877cffd125 100644 --- a/cookbook/security/securing_services.rst +++ b/cookbook/security/securing_services.rst @@ -6,7 +6,7 @@ How to Secure any Service or Method in your Application ======================================================= In the security chapter, you can see how to :ref:`secure a controller ` -by requesting the ``security.context`` service from the Service Container +by requesting the ``security.authorization_checker`` service from the Service Container and checking the current user's role:: // ... @@ -14,14 +14,18 @@ and checking the current user's role:: public function helloAction($name) { - if (false === $this->get('security.context')->isGranted('ROLE_ADMIN')) { + if (false === $this->get('security.authorization_checker')->isGranted('ROLE_ADMIN')) { throw new AccessDeniedException(); } // ... } -You can also secure *any* service in a similar way by injecting the ``security.context`` +.. versionadded:: 2.6 + The ``security.authorization_checker`` service was introduced in Symfony 2.6. Prior + to Symfony 2.6, you had to use the ``isGranted()`` method of the ``security.context`` service. + +You can also secure *any* service in a similar way by injecting the ``security.authorization_checker`` service into it. For a general introduction to injecting dependencies into services see the :doc:`/book/service_container` chapter of the book. For example, suppose you have a ``NewsletterManager`` class that sends out emails @@ -30,8 +34,8 @@ role. Before you add security, the class looks something like this: .. code-block:: php - // src/Acme/HelloBundle/Newsletter/NewsletterManager.php - namespace Acme\HelloBundle\Newsletter; + // src/AppBundle/Newsletter/NewsletterManager.php + namespace AppBundle\Newsletter; class NewsletterManager { @@ -45,23 +49,23 @@ role. Before you add security, the class looks something like this: } Your goal is to check the user's role when the ``sendNewsletter()`` method is -called. The first step towards this is to inject the ``security.context`` +called. The first step towards this is to inject the ``security.authorization_checker`` service into the object. Since it won't make sense *not* to perform the security check, this is an ideal candidate for constructor injection, which guarantees -that the security context object will be available inside the ``NewsletterManager`` +that the authorization checker object will be available inside the ``NewsletterManager`` class:: - namespace Acme\HelloBundle\Newsletter; + namespace AppBundle\Newsletter; - use Symfony\Component\Security\Core\SecurityContextInterface; + use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; class NewsletterManager { - protected $securityContext; + protected $authorizationChecker; - public function __construct(SecurityContextInterface $securityContext) + public function __construct(AuthorizationCheckerInterface $authorizationChecker) { - $this->securityContext = $securityContext; + $this->authorizationChecker = $authorizationChecker; } // ... @@ -73,62 +77,53 @@ Then in your service configuration, you can inject the service: .. code-block:: yaml - # src/Acme/HelloBundle/Resources/config/services.yml - parameters: - newsletter_manager.class: Acme\HelloBundle\Newsletter\NewsletterManager - + # app/config/services.yml services: newsletter_manager: - class: "%newsletter_manager.class%" - arguments: ["@security.context"] + class: "AppBundle\Newsletter\NewsletterManager" + arguments: ["@security.authorization_checker"] .. code-block:: xml - - - Acme\HelloBundle\Newsletter\NewsletterManager - - + - - + + .. code-block:: php - // src/Acme/HelloBundle/Resources/config/services.php + // app/config/services.php use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; - $container->setParameter('newsletter_manager.class', 'Acme\HelloBundle\Newsletter\NewsletterManager'); - $container->setDefinition('newsletter_manager', new Definition( - '%newsletter_manager.class%', - array(new Reference('security.context')) + 'AppBundle\Newsletter\NewsletterManager', + array(new Reference('security.authorization_checker')) )); The injected service can then be used to perform the security check when the ``sendNewsletter()`` method is called:: - namespace Acme\HelloBundle\Newsletter; + namespace AppBundle\Newsletter; + use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Component\Security\Core\Exception\AccessDeniedException; - use Symfony\Component\Security\Core\SecurityContextInterface; // ... class NewsletterManager { - protected $securityContext; + protected $authorizationChecker; - public function __construct(SecurityContextInterface $securityContext) + public function __construct(AuthorizationCheckerInterface $authorizationChecker) { - $this->securityContext = $securityContext; + $this->authorizationChecker = $authorizationChecker; } public function sendNewsletter() { - if (false === $this->securityContext->isGranted('ROLE_NEWSLETTER_ADMIN')) { + if (false === $this->authorizationChecker->isGranted('ROLE_NEWSLETTER_ADMIN')) { throw new AccessDeniedException(); } @@ -157,7 +152,7 @@ the :ref:`sidebar ` below): .. code-block:: yaml - # src/Acme/HelloBundle/Resources/config/services.yml + # app/services.yml # ... services: @@ -168,11 +163,11 @@ the :ref:`sidebar ` below): .. code-block:: xml - + - + @@ -180,20 +175,20 @@ the :ref:`sidebar ` below): .. code-block:: php - // src/Acme/HelloBundle/Resources/config/services.php + // app/services.php use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; $definition = new Definition( - '%newsletter_manager.class%', - array(new Reference('security.context')) + 'AppBundle\Newsletter\NewsletterManager', + // ... )); $definition->addTag('security.secure_service'); $container->setDefinition('newsletter_manager', $definition); You can then achieve the same results as above using an annotation:: - namespace Acme\HelloBundle\Newsletter; + namespace AppBundle\Newsletter; use JMS\SecurityExtraBundle\Annotation\Secure; // ... diff --git a/cookbook/security/target_path.rst b/cookbook/security/target_path.rst index 6f7a0536da1..e9da39cf9ca 100644 --- a/cookbook/security/target_path.rst +++ b/cookbook/security/target_path.rst @@ -25,29 +25,29 @@ configuration file. This can be done from your main configuration file (in .. code-block:: yaml - # src/Acme/HelloBundle/Resources/config/services.yml + # app/config/services.yml parameters: # ... - security.exception_listener.class: Acme\HelloBundle\Security\Firewall\ExceptionListener + security.exception_listener.class: AppBundle\Security\Firewall\ExceptionListener .. code-block:: xml - + - Acme\HelloBundle\Security\Firewall\ExceptionListener + AppBundle\Security\Firewall\ExceptionListener .. code-block:: php - // src/Acme/HelloBundle/Resources/config/services.php + // app/config/services.php // ... - $container->setParameter('security.exception_listener.class', 'Acme\HelloBundle\Security\Firewall\ExceptionListener'); + $container->setParameter('security.exception_listener.class', 'AppBundle\Security\Firewall\ExceptionListener'); Next, create your own ``ExceptionListener``:: - // src/Acme/HelloBundle/Security/Firewall/ExceptionListener.php - namespace Acme\HelloBundle\Security\Firewall; + // src/AppBundle/Security/Firewall/ExceptionListener.php + namespace AppBundle\Security\Firewall; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Security\Http\Firewall\ExceptionListener as BaseExceptionListener; @@ -67,4 +67,4 @@ Next, create your own ``ExceptionListener``:: } } -Add as much or few logic here as required for your scenario! +Add as much or as little logic here as required for your scenario! diff --git a/cookbook/security/voters.rst b/cookbook/security/voters.rst index 6d39cee8ec3..236ff9c166e 100644 --- a/cookbook/security/voters.rst +++ b/cookbook/security/voters.rst @@ -37,8 +37,8 @@ and compare the IP address against a set of blacklisted IP addresses: .. code-block:: php - // src/Acme/DemoBundle/Security/Authorization/Voter/ClientIpVoter.php - namespace Acme\DemoBundle\Security\Authorization\Voter; + // src/AppBundle/Security/Authorization/Voter/ClientIpVoter.php + namespace AppBundle\Security\Authorization\Voter; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; @@ -105,9 +105,9 @@ and tag it as a ``security.voter``: # src/Acme/AcmeBundle/Resources/config/services.yml services: security.access.blacklist_voter: - class: Acme\DemoBundle\Security\Authorization\Voter\ClientIpVoter - arguments: ["@request_stack", [123.123.123.123, 171.171.171.171]] - public: false + class: AppBundle\Security\Authorization\Voter\ClientIpVoter + arguments: ["@request_stack", [123.123.123.123, 171.171.171.171]] + public: false tags: - { name: security.voter } @@ -115,7 +115,7 @@ and tag it as a ``security.voter``: + class="AppBundle\Security\Authorization\Voter\ClientIpVoter" public="false"> 123.123.123.123 @@ -131,7 +131,7 @@ and tag it as a ``security.voter``: use Symfony\Component\DependencyInjection\Reference; $definition = new Definition( - 'Acme\DemoBundle\Security\Authorization\Voter\ClientIpVoter', + 'AppBundle\Security\Authorization\Voter\ClientIpVoter', array( new Reference('request_stack'), array('123.123.123.123', '171.171.171.171'), diff --git a/cookbook/security/voters_data_permission.rst b/cookbook/security/voters_data_permission.rst index 071f3733176..31bf0b3de99 100644 --- a/cookbook/security/voters_data_permission.rst +++ b/cookbook/security/voters_data_permission.rst @@ -25,8 +25,8 @@ How Symfony Uses Voters In order to use voters, you have to understand how Symfony works with them. All voters are called each time you use the ``isGranted()`` method on Symfony's -security context (i.e. the ``security.context`` service). Each one decides -if the current user should have access to some resource. +authorization checker (i.e. the ``security.authorization_checker`` service). Each +one decides if the current user should have access to some resource. Ultimately, Symfony uses one of three different approaches on what to do with the feedback from all voters: affirmative, consensus and unanimous. @@ -54,10 +54,12 @@ Creating the custom Voter ------------------------- The goal is to create a voter that checks if a user has access to view or -edit a particular object. Here's an example implementation:: +edit a particular object. Here's an example implementation: - // src/Acme/DemoBundle/Security/Authorization/Voter/PostVoter.php - namespace Acme\DemoBundle\Security\Authorization\Voter; +.. code-block:: php + + // src/AppBundle/Security/Authorization/Voter/PostVoter.php + namespace AppBundle\Security\Authorization\Voter; use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; @@ -78,13 +80,13 @@ edit a particular object. Here's an example implementation:: public function supportsClass($class) { - $supportedClass = 'Acme\DemoBundle\Entity\Post'; + $supportedClass = 'AppBundle\Entity\Post'; return $supportedClass === $class || is_subclass_of($class, $supportedClass); } /** - * @var \Acme\DemoBundle\Entity\Post $post + * @var \AppBundle\Entity\Post $post */ public function vote(TokenInterface $token, $post, array $attributes) { @@ -96,7 +98,7 @@ edit a particular object. Here's an example implementation:: // check if the voter is used correct, only allow one attribute // this isn't a requirement, it's just one easy way for you to // design your voter - if(1 !== count($attributes)) { + if (1 !== count($attributes)) { throw new \InvalidArgumentException( 'Only one attribute is allowed for VIEW or EDIT' ); @@ -153,24 +155,24 @@ and tag it with ``security.voter``: .. code-block:: yaml - # src/Acme/DemoBundle/Resources/config/services.yml + # src/AppBundle/Resources/config/services.yml services: security.access.post_voter: - class: Acme\DemoBundle\Security\Authorization\Voter\PostVoter + class: AppBundle\Security\Authorization\Voter\PostVoter public: false tags: - { name: security.voter } .. code-block:: xml - + @@ -179,11 +181,11 @@ and tag it with ``security.voter``: .. code-block:: php - // src/Acme/DemoBundle/Resources/config/services.php + // src/AppBundle/Resources/config/services.php $container ->register( 'security.access.post_document_voter', - 'Acme\DemoBundle\Security\Authorization\Voter\PostVoter' + 'AppBundle\Security\Authorization\Voter\PostVoter' ) ->addTag('security.voter') ; @@ -192,12 +194,12 @@ How to Use the Voter in a Controller ------------------------------------ The registered voter will then always be asked as soon as the method ``isGranted()`` -from the security context is called. +from the authorization checker is called. .. code-block:: php - // src/Acme/DemoBundle/Controller/PostController.php - namespace Acme\DemoBundle\Controller; + // src/AppBundle/Controller/PostController.php + namespace AppBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Response; @@ -211,7 +213,7 @@ from the security context is called. $post = ...; // keep in mind, this will call all registered security voters - if (false === $this->get('security.context')->isGranted('view', $post)) { + if (false === $this->get('security.authorization_checker')->isGranted('view', $post)) { throw new AccessDeniedException('Unauthorised access!'); } @@ -219,4 +221,8 @@ from the security context is called. } } +.. versionadded:: 2.6 + The ``security.authorization_checker`` service was introduced in Symfony 2.6. Prior + to Symfony 2.6, you had to use the ``isGranted()`` method of the ``security.context`` service. + It's that easy! diff --git a/cookbook/service_container/event_listener.rst b/cookbook/service_container/event_listener.rst index 00b66670a48..d96f8553a34 100644 --- a/cookbook/service_container/event_listener.rst +++ b/cookbook/service_container/event_listener.rst @@ -14,8 +14,8 @@ you will create a service that will act as an Exception Listener, allowing you to modify how exceptions are shown by your application. The ``KernelEvents::EXCEPTION`` event is just one of the core kernel events:: - // src/Acme/DemoBundle/EventListener/AcmeExceptionListener.php - namespace Acme\DemoBundle\EventListener; + // src/AppBundle/EventListener/AcmeExceptionListener.php + namespace AppBundle\EventListener; use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; use Symfony\Component\HttpFoundation\Response; @@ -51,15 +51,18 @@ event is just one of the core kernel events:: } } -.. versionadded:: 2.4 - Support for HTTP status code constants was introduced in Symfony 2.4. - .. tip:: Each event receives a slightly different type of ``$event`` object. For the ``kernel.exception`` event, it is :class:`Symfony\\Component\\HttpKernel\\Event\\GetResponseForExceptionEvent`. To see what type of object each event listener receives, see :class:`Symfony\\Component\\HttpKernel\\KernelEvents`. +.. note:: + + When setting a response for the ``kernel.request``, ``kernel.view`` or + ``kernel.exception`` events, the propagation is stopped, so the lower + priority listeners on that event don't get called. + Now that the class is created, you just need to register it as a service and notify Symfony that it is a "listener" on the ``kernel.exception`` event by using a special "tag": @@ -68,25 +71,25 @@ using a special "tag": .. code-block:: yaml - # app/config/config.yml + # app/config/services.yml services: kernel.listener.your_listener_name: - class: Acme\DemoBundle\EventListener\AcmeExceptionListener + class: AppBundle\EventListener\AcmeExceptionListener tags: - { name: kernel.event_listener, event: kernel.exception, method: onKernelException } .. code-block:: xml - - + + .. code-block:: php - // app/config/config.php + // app/config/services.php $container - ->register('kernel.listener.your_listener_name', 'Acme\DemoBundle\EventListener\AcmeExceptionListener') + ->register('kernel.listener.your_listener_name', 'AppBundle\EventListener\AcmeExceptionListener') ->addTag('kernel.event_listener', array('event' => 'kernel.exception', 'method' => 'onKernelException')) ; @@ -100,17 +103,13 @@ using a special "tag": Request Events, Checking Types ------------------------------ -.. versionadded:: 2.4 - The ``isMasterRequest()`` method was introduced in Symfony 2.4. - Prior, the ``getRequestType()`` method must be used. - A single page can make several requests (one master request, and then multiple sub-requests), which is why when working with the ``KernelEvents::REQUEST`` event, you might need to check the type of the request. This can be easily done as follow:: - // src/Acme/DemoBundle/EventListener/AcmeRequestListener.php - namespace Acme\DemoBundle\EventListener; + // src/AppBundle/EventListener/AcmeRequestListener.php + namespace AppBundle\EventListener; use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\HttpKernel\HttpKernel; @@ -133,3 +132,23 @@ done as follow:: Two types of request are available in the :class:`Symfony\\Component\\HttpKernel\\HttpKernelInterface` interface: ``HttpKernelInterface::MASTER_REQUEST`` and ``HttpKernelInterface::SUB_REQUEST``. + +Debugging Event Listeners +------------------------- + +.. versionadded:: 2.6 + The ``debug:event-dispatcher`` command was introduced in Symfony 2.6. + +You can find out what listeners are registered in the event dispatcher +using the console. To show all events and their listeners, run: + +.. code-block:: bash + + $ php app/console debug:event-dispatcher + +You can get registered listeners for a particular event by specifying +its name: + +.. code-block:: bash + + $ php app/console debug:event-dispatcher kernel.exception diff --git a/cookbook/service_container/scopes.rst b/cookbook/service_container/scopes.rst index cf338caff60..06e0d94d5ff 100644 --- a/cookbook/service_container/scopes.rst +++ b/cookbook/service_container/scopes.rst @@ -131,7 +131,7 @@ marked as ``synchronized``: # app/config/config.yml services: client_configuration: - class: Acme\HelloBundle\Client\ClientConfiguration + class: AppBundle\Client\ClientConfiguration scope: client synchronized: true synthetic: true @@ -153,7 +153,7 @@ marked as ``synchronized``: scope="client" synchronized="true" synthetic="true" - class="Acme\HelloBundle\Client\ClientConfiguration" + class="AppBundle\Client\ClientConfiguration" /> @@ -164,7 +164,7 @@ marked as ``synchronized``: use Symfony\Component\DependencyInjection\Definition; $definition = new Definition( - 'Acme\HelloBundle\Client\ClientConfiguration', + 'AppBundle\Client\ClientConfiguration', array() ); $definition->setScope('client'); @@ -175,10 +175,10 @@ marked as ``synchronized``: Now, if you inject this service using setter injection, there are no drawbacks and everything works without any special code in your service or in your definition:: - // src/Acme/HelloBundle/Mail/Mailer.php - namespace Acme\HelloBundle\Mail; + // src/AppBundle/Mail/Mailer.php + namespace AppBundle\Mail; - use Acme\HelloBundle\Client\ClientConfiguration; + use AppBundle\Client\ClientConfiguration; class Mailer { @@ -213,19 +213,19 @@ your code. This should also be taken into account when declaring your service: .. code-block:: yaml - # src/Acme/HelloBundle/Resources/config/services.yml + # app/config/services.yml services: my_mailer: - class: Acme\HelloBundle\Mail\Mailer + class: AppBundle\Mail\Mailer calls: - [setClientConfiguration, ["@?client_configuration="]] .. code-block:: xml - + setDefinition( 'my_mailer', - new Definition('Acme\HelloBundle\Mail\Mailer') + new Definition('AppBundle\Mail\Mailer') ) ->addMethodCall('setClientConfiguration', array( new Reference( @@ -269,19 +269,19 @@ argument is the ``ClientConfiguration`` object: .. code-block:: yaml - # src/Acme/HelloBundle/Resources/config/services.yml + # app/config/services.yml services: my_mailer: - class: Acme\HelloBundle\Mail\Mailer + class: AppBundle\Mail\Mailer scope: client arguments: ["@client_configuration"] .. code-block:: xml - + @@ -289,13 +289,13 @@ argument is the ``ClientConfiguration`` object: .. code-block:: php - // src/Acme/HelloBundle/Resources/config/services.php + // app/config/services.php use Symfony\Component\DependencyInjection\Definition; $definition = $container->setDefinition( 'my_mailer', new Definition( - 'Acme\HelloBundle\Mail\Mailer', + 'AppBundle\Mail\Mailer', array(new Reference('client_configuration'), )) )->setScope('client'); @@ -310,8 +310,8 @@ twig extension must be in the ``container`` scope as the Twig environment needs it as a dependency). In these cases, you can pass the entire container into your service:: - // src/Acme/HelloBundle/Mail/Mailer.php - namespace Acme\HelloBundle\Mail; + // src/AppBundle/Mail/Mailer.php + namespace AppBundle\Mail; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -344,42 +344,30 @@ The service config for this class would look something like this: .. code-block:: yaml - # src/Acme/HelloBundle/Resources/config/services.yml - parameters: - # ... - my_mailer.class: Acme\HelloBundle\Mail\Mailer - + # app/config/services.yml services: my_mailer: - class: "%my_mailer.class%" + class: AppBundle\Mail\Mailer arguments: ["@service_container"] # scope: container can be omitted as it is the default .. code-block:: xml - - - - Acme\HelloBundle\Mail\Mailer - - + - + .. code-block:: php - // src/Acme/HelloBundle/Resources/config/services.php + // app/config/services.php use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; - // ... - $container->setParameter('my_mailer.class', 'Acme\HelloBundle\Mail\Mailer'); - $container->setDefinition('my_mailer', new Definition( - '%my_mailer.class%', + 'AppBundle\Mail\Mailer', array(new Reference('service_container')) )); diff --git a/cookbook/session/limit_metadata_writes.rst b/cookbook/session/limit_metadata_writes.rst index 4a96cec7843..5708a793567 100644 --- a/cookbook/session/limit_metadata_writes.rst +++ b/cookbook/session/limit_metadata_writes.rst @@ -4,9 +4,6 @@ Limit Session Metadata Writes ============================= -.. versionadded:: 2.4 - The ability to limit session metadata writes was introduced in Symfony 2.4. - The default behavior of PHP session is to persist the session regardless of whether the session data has changed or not. In Symfony, each time the session is accessed, metadata is recorded (session created/last used) which can be used diff --git a/cookbook/session/locale_sticky_session.rst b/cookbook/session/locale_sticky_session.rst index 172930869d7..2aa61eed497 100644 --- a/cookbook/session/locale_sticky_session.rst +++ b/cookbook/session/locale_sticky_session.rst @@ -19,8 +19,8 @@ The listener will look something like this. Typically, ``_locale`` is used as a routing parameter to signify the locale, though it doesn't really matter how you determine the desired locale from the request:: - // src/Acme/LocaleBundle/EventListener/LocaleListener.php - namespace Acme\LocaleBundle\EventListener; + // src/AppBundle/EventListener/LocaleListener.php + namespace AppBundle\EventListener; use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\HttpKernel\KernelEvents; @@ -67,16 +67,16 @@ Then register the listener: .. code-block:: yaml services: - acme_locale.locale_listener: - class: Acme\LocaleBundle\EventListener\LocaleListener + app.locale_listener: + class: AppBundle\EventListener\LocaleListener arguments: ["%kernel.default_locale%"] tags: - { name: kernel.event_subscriber } .. code-block:: xml - + %kernel.default_locale% @@ -87,8 +87,8 @@ Then register the listener: use Symfony\Component\DependencyInjection\Definition; $container - ->setDefinition('acme_locale.locale_listener', new Definition( - 'Acme\LocaleBundle\EventListener\LocaleListener', + ->setDefinition('app.locale_listener', new Definition( + 'AppBundle\EventListener\LocaleListener', array('%kernel.default_locale%') )) ->addTag('kernel.event_subscriber') diff --git a/cookbook/symfony1.rst b/cookbook/symfony1.rst index a393534e8ec..10ba32149ff 100644 --- a/cookbook/symfony1.rst +++ b/cookbook/symfony1.rst @@ -165,7 +165,7 @@ defined in the ``composer.json`` file. If you look at the ``HelloController`` from the Symfony2 Standard Edition you can see that it lives in the ``Acme\DemoBundle\Controller`` namespace. Yet, the AcmeDemoBundle is not defined in your ``composer.json`` file. Nonetheless are -the files autoloaded. This is because you can tell composer to autoload files +the files autoloaded. This is because you can tell Composer to autoload files from specific directories without defining a dependency: .. code-block:: json diff --git a/cookbook/templating/PHP.rst b/cookbook/templating/PHP.rst index 9d09afbfa4b..60dbe0c6232 100644 --- a/cookbook/templating/PHP.rst +++ b/cookbook/templating/PHP.rst @@ -49,22 +49,21 @@ You can now render a PHP template instead of a Twig one simply by using the ``.php`` extension in the template name instead of ``.twig``. The controller below renders the ``index.html.php`` template:: - // src/Acme/HelloBundle/Controller/HelloController.php + // src/AppBundle/Controller/HelloController.php // ... public function indexAction($name) { return $this->render( - 'AcmeHelloBundle:Hello:index.html.php', + 'AppBundle:Hello:index.html.php', array('name' => $name) ); } -You can also use the :doc:`/bundles/SensioFrameworkExtraBundle/annotations/view` -shortcut to render the default ``AcmeHelloBundle:Hello:index.html.php`` template:: - - // src/Acme/HelloBundle/Controller/HelloController.php +You can also use the `@Template`_ shortcut to render the default +``AppBundle:Hello:index.html.php`` template:: + // src/AppBundle/Controller/HelloController.php use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; // ... @@ -89,19 +88,19 @@ shortcut to render the default ``AcmeHelloBundle:Hello:index.html.php`` template // ... // namespaced templates will no longer work in controllers - $this->render('@Acme/Default/index.html.twig'); + $this->render('@App/Default/index.html.twig'); // you must use the traditional template notation - $this->render('AcmeBundle:Default:index.html.twig'); + $this->render('AppBundle:Default:index.html.twig'); } .. code-block:: jinja {# inside a Twig template, namespaced templates work as expected #} - {{ include('@Acme/Default/index.html.twig') }} + {{ include('@App/Default/index.html.twig') }} {# traditional template notation will also work #} - {{ include('AcmeBundle:Default:index.html.twig') }} + {{ include('AppBundle:Default:index.html.twig') }} .. index:: @@ -120,12 +119,12 @@ the ``extend()`` call: .. code-block:: html+php - - extend('AcmeHelloBundle::layout.html.php') ?> + + extend('AppBundle::layout.html.php') ?> Hello ! -The ``AcmeHelloBundle::layout.html.php`` notation sounds familiar, doesn't it? It +The ``AppBundle::layout.html.php`` notation sounds familiar, doesn't it? It is the same notation used to reference a template. The ``::`` part simply means that the controller element is empty, so the corresponding file is directly stored under ``views/``. @@ -134,7 +133,7 @@ Now, have a look at the ``layout.html.php`` file: .. code-block:: html+php - + extend('::base.html.php') ?>

      Hello Application

      @@ -182,8 +181,8 @@ decorating the template. In the ``index.html.php`` template, define a .. code-block:: html+php - - extend('AcmeHelloBundle::layout.html.php') ?> + + extend('AppBundle::layout.html.php') ?> set('title', 'Hello World Application') ?> @@ -224,17 +223,17 @@ Create a ``hello.html.php`` template: .. code-block:: html+php - + Hello ! And change the ``index.html.php`` template to include it: .. code-block:: html+php - - extend('AcmeHelloBundle::layout.html.php') ?> + + extend('AppBundle::layout.html.php') ?> - render('AcmeHelloBundle:Hello:hello.html.php', array('name' => $name)) ?> + render('AppBundle:Hello:hello.html.php', array('name' => $name)) ?> The ``render()`` method evaluates and returns the content of another template (this is the exact same method as the one used in the controller). @@ -254,18 +253,18 @@ If you create a ``fancy`` action, and want to include it into the .. code-block:: html+php - + render( - new \Symfony\Component\HttpKernel\Controller\ControllerReference('AcmeHelloBundle:Hello:fancy', array( + new \Symfony\Component\HttpKernel\Controller\ControllerReference('AppBundle:Hello:fancy', array( 'name' => $name, 'color' => 'green', )) ) ?> -Here, the ``AcmeHelloBundle:Hello:fancy`` string refers to the ``fancy`` action of the +Here, the ``AppBundle:Hello:fancy`` string refers to the ``fancy`` action of the ``Hello`` controller:: - // src/Acme/HelloBundle/Controller/HelloController.php + // src/AppBundle/Controller/HelloController.php class HelloController extends Controller { @@ -274,7 +273,7 @@ Here, the ``AcmeHelloBundle:Hello:fancy`` string refers to the ``fancy`` action // create some object, based on the $color variable $object = ...; - return $this->render('AcmeHelloBundle:Hello:fancy.html.php', array( + return $this->render('AppBundle:Hello:fancy.html.php', array( 'name' => $name, 'object' => $object )); @@ -318,10 +317,10 @@ pattern: .. code-block:: yaml - # src/Acme/HelloBundle/Resources/config/routing.yml + # src/AppBundle/Resources/config/routing.yml hello: # The route name path: /hello/{name} - defaults: { _controller: AcmeHelloBundle:Hello:index } + defaults: { _controller: AppBundle:Hello:index } Using Assets: Images, JavaScripts and Stylesheets ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -368,3 +367,5 @@ within an HTML context. The second argument lets you change the context. For instance, to output something in a JavaScript script, use the ``js`` context:: escape($var, 'js') ?> + +.. _`@Template`: http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/view` diff --git a/cookbook/templating/namespaced_paths.rst b/cookbook/templating/namespaced_paths.rst index e4ce2331f71..164fde49487 100644 --- a/cookbook/templating/namespaced_paths.rst +++ b/cookbook/templating/namespaced_paths.rst @@ -4,9 +4,6 @@ How to Use and Register Namespaced Twig Paths ============================================= -.. versionadded:: 2.2 - Namespaced path support was introduced in 2.2. - Usually, when you refer to a template, you'll use the ``MyBundle:Subdir:filename.html.twig`` format (see :ref:`template-naming-locations`). @@ -17,15 +14,15 @@ Take the following paths as an example: .. code-block:: jinja - {% extends "AcmeDemoBundle::layout.html.twig" %} - {% include "AcmeDemoBundle:Foo:bar.html.twig" %} + {% extends "AppBundle::layout.html.twig" %} + {% include "AppBundle:Foo:bar.html.twig" %} With namespaced paths, the following works as well: .. code-block:: jinja - {% extends "@AcmeDemo/layout.html.twig" %} - {% include "@AcmeDemo/Foo/bar.html.twig" %} + {% extends "@App/layout.html.twig" %} + {% include "@App/Foo/bar.html.twig" %} Both paths are valid and functional by default in Symfony. diff --git a/cookbook/templating/render_without_controller.rst b/cookbook/templating/render_without_controller.rst index 004a8816a55..aab469a2340 100644 --- a/cookbook/templating/render_without_controller.rst +++ b/cookbook/templating/render_without_controller.rst @@ -10,7 +10,7 @@ a simple template that doesn't need any data passed into it, you can avoid creating the controller entirely, by using the built-in ``FrameworkBundle:Template:template`` controller. -For example, suppose you want to render a ``AcmeBundle:Static:privacy.html.twig`` +For example, suppose you want to render a ``AppBundle:Static:privacy.html.twig`` template, which doesn't require that any variables are passed to it. You can do this without creating a controller: @@ -22,7 +22,7 @@ can do this without creating a controller: path: /privacy defaults: _controller: FrameworkBundle:Template:template - template: 'AcmeBundle:Static:privacy.html.twig' + template: 'AppBundle:Static:privacy.html.twig' .. code-block:: xml @@ -34,7 +34,7 @@ can do this without creating a controller: FrameworkBundle:Template:template - AcmeBundle:Static:privacy.html.twig + AppBundle:Static:privacy.html.twig @@ -46,7 +46,7 @@ can do this without creating a controller: $collection = new RouteCollection(); $collection->add('acme_privacy', new Route('/privacy', array( '_controller' => 'FrameworkBundle:Template:template', - 'template' => 'AcmeBundle:Static:privacy.html.twig', + 'template' => 'AppBundle:Static:privacy.html.twig', ))); return $collection; @@ -77,10 +77,6 @@ this is probably only useful if you'd like to cache this page partial (see Caching the static Template --------------------------- -.. versionadded:: 2.2 - The ability to cache templates rendered via ``FrameworkBundle:Template:template`` - was introduced in Symfony 2.2. - Since templates that are rendered in this way are typically static, it might make sense to cache them. Fortunately, this is easy! By configuring a few other variables in your route, you can control exactly how your page is cached: @@ -93,7 +89,7 @@ other variables in your route, you can control exactly how your page is cached: path: /privacy defaults: _controller: FrameworkBundle:Template:template - template: 'AcmeBundle:Static:privacy.html.twig' + template: 'AppBundle:Static:privacy.html.twig' maxAge: 86400 sharedAge: 86400 @@ -107,7 +103,7 @@ other variables in your route, you can control exactly how your page is cached: FrameworkBundle:Template:template - AcmeBundle:Static:privacy.html.twig + AppBundle:Static:privacy.html.twig 86400 86400 @@ -121,7 +117,7 @@ other variables in your route, you can control exactly how your page is cached: $collection = new RouteCollection(); $collection->add('acme_privacy', new Route('/privacy', array( '_controller' => 'FrameworkBundle:Template:template', - 'template' => 'AcmeBundle:Static:privacy.html.twig', + 'template' => 'AppBundle:Static:privacy.html.twig', 'maxAge' => 86400, 'sharedAge' => 86400, ))); diff --git a/cookbook/templating/twig_extension.rst b/cookbook/templating/twig_extension.rst index f3f17dfbbc0..0b580406dd8 100644 --- a/cookbook/templating/twig_extension.rst +++ b/cookbook/templating/twig_extension.rst @@ -30,10 +30,10 @@ Create the Extension Class To get your custom functionality you must first create a Twig Extension class. As an example you'll create a price filter to format a given number into price:: - // src/Acme/DemoBundle/Twig/AcmeExtension.php - namespace Acme\DemoBundle\Twig; + // src/AppBundle/Twig/AppExtension.php + namespace AppBundle\Twig; - class AcmeExtension extends \Twig_Extension + class AppExtension extends \Twig_Extension { public function getFilters() { @@ -52,7 +52,7 @@ As an example you'll create a price filter to format a given number into price:: public function getName() { - return 'acme_extension'; + return 'app_extension'; } } @@ -70,38 +70,44 @@ Now you must let the Service Container know about your newly created Twig Extens .. code-block:: yaml - # src/Acme/DemoBundle/Resources/config/services.yml + # app/config/services.yml services: - acme.twig.acme_extension: - class: Acme\DemoBundle\Twig\AcmeExtension + app.twig_extension: + class: AppBundle\Twig\AppExtension + public: false tags: - { name: twig.extension } .. code-block:: xml - + - + .. code-block:: php - // src/Acme/DemoBundle/Resources/config/services.php + // app/config/services.php use Symfony\Component\DependencyInjection\Definition; $container - ->register('acme.twig.acme_extension', '\Acme\DemoBundle\Twig\AcmeExtension') + ->register('app.twig_extension', '\AppBundle\Twig\AppExtension') + ->setPublic(false) ->addTag('twig.extension'); .. note:: Keep in mind that Twig Extensions are not lazily loaded. This means that - there's a higher chance that you'll get a **CircularReferenceException** - or a **ScopeWideningInjectionException** if any services - (or your Twig Extension in this case) are dependent on the request service. - For more information take a look at :doc:`/cookbook/service_container/scopes`. + there's a higher chance that you'll get a + :class:`Symfony\\Component\\DependencyInjection\\Exception\\ServiceCircularReferenceException` + or a + :class:`Symfony\\Component\\DependencyInjection\\Exception\\ScopeWideningInjectionException` + if any services (or your Twig Extension in this case) are dependent on + the request service. For more information take a look at :doc:`/cookbook/service_container/scopes`. Using the custom Extension -------------------------- @@ -126,7 +132,7 @@ Learning further For a more in-depth look into Twig Extensions, please take a look at the `Twig extensions documentation`_. -.. _`Twig official extension repository`: https://github.com/fabpot/Twig-extensions +.. _`Twig official extension repository`: https://github.com/twigphp/Twig-extensions .. _`Twig extensions documentation`: http://twig.sensiolabs.org/doc/advanced.html#creating-an-extension .. _`global variables`: http://twig.sensiolabs.org/doc/advanced.html#id1 .. _`functions`: http://twig.sensiolabs.org/doc/advanced.html#id2 diff --git a/cookbook/testing/database.rst b/cookbook/testing/database.rst index bd73d70b889..5a8709ac3db 100644 --- a/cookbook/testing/database.rst +++ b/cookbook/testing/database.rst @@ -33,7 +33,7 @@ class. Suppose the class you want to test looks like this:: - namespace Acme\DemoBundle\Salary; + namespace AppBundle\Salary; use Doctrine\Common\Persistence\ObjectManager; @@ -48,7 +48,7 @@ Suppose the class you want to test looks like this:: public function calculateTotalSalary($id) { - $employeeRepository = $this->entityManager->getRepository('AcmeDemoBundle::Employee'); + $employeeRepository = $this->entityManager->getRepository('AppBundle::Employee'); $employee = $employeeRepository->find($id); return $employee->getSalary() + $employee->getBonus(); @@ -58,14 +58,14 @@ Suppose the class you want to test looks like this:: Since the ``ObjectManager`` gets injected into the class through the constructor, it's easy to pass a mock object within a test:: - use Acme\DemoBundle\Salary\SalaryCalculator; + use AppBundle\Salary\SalaryCalculator; class SalaryCalculatorTest extends \PHPUnit_Framework_TestCase { public function testCalculateTotalSalary() { // First, mock the object to be used in the test - $employee = $this->getMock('\Acme\DemoBundle\Entity\Employee'); + $employee = $this->getMock('\AppBundle\Entity\Employee'); $employee->expects($this->once()) ->method('getSalary') ->will($this->returnValue(1000)); diff --git a/cookbook/testing/doctrine.rst b/cookbook/testing/doctrine.rst index b4f1cda682a..e3e3e4b1b3a 100644 --- a/cookbook/testing/doctrine.rst +++ b/cookbook/testing/doctrine.rst @@ -17,15 +17,15 @@ Functional Testing ------------------ If you need to actually execute a query, you will need to boot the kernel -to get a valid connection. In this case, you'll extend the ``WebTestCase``, +to get a valid connection. In this case, you'll extend the ``KernelTestCase``, which makes all of this quite easy:: // src/Acme/StoreBundle/Tests/Entity/ProductRepositoryFunctionalTest.php namespace Acme\StoreBundle\Tests\Entity; - use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; + use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; - class ProductRepositoryFunctionalTest extends WebTestCase + class ProductRepositoryFunctionalTest extends KernelTestCase { /** * @var \Doctrine\ORM\EntityManager @@ -37,8 +37,7 @@ which makes all of this quite easy:: */ public function setUp() { - static::$kernel = static::createKernel(); - static::$kernel->boot(); + self::bootKernel(); $this->em = static::$kernel->getContainer() ->get('doctrine') ->getManager() diff --git a/cookbook/testing/insulating_clients.rst b/cookbook/testing/insulating_clients.rst index cca8b80e97a..dd4362e7e47 100644 --- a/cookbook/testing/insulating_clients.rst +++ b/cookbook/testing/insulating_clients.rst @@ -18,9 +18,6 @@ chat for instance), create several clients:: $this->assertEquals(Response::HTTP_CREATED, $harry->getResponse()->getStatusCode()); $this->assertRegExp('/Hello/', $sally->getResponse()->getContent()); -.. versionadded:: 2.4 - Support for HTTP status code constants was introduced in Symfony 2.4. - This works except when your code maintains a global state or if it depends on a third-party library that has some kind of global state. In such a case, you can insulate your clients:: diff --git a/cookbook/testing/simulating_authentication.rst b/cookbook/testing/simulating_authentication.rst index 9004fe0832a..f2e04acd612 100644 --- a/cookbook/testing/simulating_authentication.rst +++ b/cookbook/testing/simulating_authentication.rst @@ -15,14 +15,14 @@ Another way would be to create a token yourself and store it in a session. While doing this, you have to make sure that an appropriate cookie is sent with a request. The following example demonstrates this technique:: - // src/Acme/DemoBundle/Tests/Controller/DemoControllerTest.php - namespace Acme\DemoBundle\Tests\Controller; + // src/AppBundle/Tests/Controller/DefaultControllerTest.php + namespace Appbundle\Tests\Controller; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Component\BrowserKit\Cookie; use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; - class DemoControllerTest extends WebTestCase + class DefaultControllerTest extends WebTestCase { private $client = null; @@ -35,10 +35,10 @@ with a request. The following example demonstrates this technique:: { $this->logIn(); - $crawler = $this->client->request('GET', '/demo/secured/hello/Fabien'); + $crawler = $this->client->request('GET', '/admin'); $this->assertTrue($this->client->getResponse()->isSuccessful()); - $this->assertGreaterThan(0, $crawler->filter('html:contains("Hello Fabien")')->count()); + $this->assertGreaterThan(0, $crawler->filter('html:contains("Admin Dashboard")')->count()); } private function logIn() diff --git a/cookbook/upgrading.rst b/cookbook/upgrading.rst new file mode 100644 index 00000000000..0dc36fcd160 --- /dev/null +++ b/cookbook/upgrading.rst @@ -0,0 +1,141 @@ +How to Upgrade Your Symfony Project +=================================== + +So a new Symfony release has come out and you want to upgrade, great! Fortunately, +because Symfony protects backwards-compatibility very closely, this *should* +be quite easy. + +There are two types of upgrades, and both are a little different: + +* :ref:`upgrading-patch-version` +* :ref:`upgrading-minor-version` + +.. _upgrading-patch-version: + +Upgrading a Patch Version (e.g. 2.6.0 to 2.6.1) +----------------------------------------------- + +If you're upgrading and only the patch version (the last number) is changing, +then it's *really* easy: + +.. code-block:: bash + + $ composer update symfony/symfony + +That's it! You should not encounter any backwards-compatibility breaks or +need to change anything else in your code. That's because when you started +your project, your ``composer.json`` included Symfony using a constraint +like ``2.6.*``, where only the *last* version number will change when you +update. + +You may also want to upgrade the rest of your libraries. If you've done a +good job with your `version constraints`_ in ``composer.json``, you can do +this safely by running: + +.. code-block:: bash + + $ composer update + +But beware. If you have some bad `version constraints`_ in your ``composer.json``, +(e.g. ``dev-master``), then this could upgrade some non-Symfony libraries +to new versions that contain backwards-compatibility breaking changes. + +.. _upgrading-minor-version: + +Upgrading a Minor Version (e.g. 2.5.3 to 2.6.1) +----------------------------------------------- + +If you're upgrading a minor version (where the middle number changes), then +you should also *not* encounter significant backwards compatibility changes. +For details, see our :doc:`/contributing/code/bc`. + +However, some backwards-compatibility breaks *are* possible, and you'll learn +in a second how to prepare for them. + +There are two steps to upgrading: + +:ref:`upgrade-minor-symfony-composer`; +:ref:`upgrade-minor-symfony-code` + +.. _`upgrade-minor-symfony-composer`: + +1) Update the Symfony Library via Composer +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +First, you need to update Symfony by modifying your ``composer.json`` file +to use the new version: + +.. code-block:: json + + { + "...": "...", + + "require": { + "php": ">=5.3.3", + "symfony/symfony": "2.6.*", + "...": "... no changes to anything else..." + }, + "...": "...", + } + +Next, use Composer to download new versions of the libraries: + +.. code-block:: bash + + $ composer update symfony/symfony + +You may also want to upgrade the rest of your libraries. If you've done a +good job with your `version constraints`_ in ``composer.json``, you can do +this safely by running: + +.. code-block:: bash + + $ composer update + +But beware. If you have some bad `version constraints`_ in your ``composer.json``, +(e.g. ``dev-master``), then this could upgrade some non-Symfony libraries +to new versions that contain backwards-compatibility breaking changes. + +.. _`upgrade-minor-symfony-code`: + +2) Updating Your Code to Work with the new Version +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In theory, you should be done! However, you *may* need to make a few changes +to your code to get everything working. Additionally, some features you're +using might still work, but might now be deprecated. That's actually ok, +but if you know about these deprecations, you can start to fix them over +time. + +Every version of Symfony comes with an UPGRADE file that describes these +changes. Below are links to the file for each version, which you'll need +to read to see if you need any code changes. + +.. tip:: + + Don't see the version here that you're upgrading to? Just find the + UPGRADE-X.X.md file for the appropriate version on the `Symfony Repository`_. + +Upgrading to Symfony 2.6 +........................ + +First, of course, update your ``composer.json`` file with the ``2.6`` version +of Symfony as described above in :ref:`upgrade-minor-symfony-composer`. + +Next, check the `UPGRADE-2.6`_ document for details about any code changes +that you might need to make in your project. + +Upgrading to Symfony 2.5 +........................ + +First, of course, update your ``composer.json`` file with the ``2.5`` version +of Symfony as described above in :ref:`upgrade-minor-symfony-composer`. + +Next, check the `UPGRADE-2.5`_ document for details about any code changes +that you might need to make in your project. + +.. _`UPGRADE-2.5`: https://github.com/symfony/symfony/blob/2.5/UPGRADE-2.5.md +.. _`UPGRADE-2.6`: https://github.com/symfony/symfony/blob/2.6/UPGRADE-2.6.md +.. _`Symfony Repository`: https://github.com/symfony/symfony +.. _`Composer Package Versions`: https://getcomposer.org/doc/01-basic-usage.md#package-versions +.. _`version constraints`: https://getcomposer.org/doc/01-basic-usage.md#package-versions \ No newline at end of file diff --git a/cookbook/validation/custom_constraint.rst b/cookbook/validation/custom_constraint.rst index 5cfd2312759..ad112456d77 100644 --- a/cookbook/validation/custom_constraint.rst +++ b/cookbook/validation/custom_constraint.rst @@ -14,8 +14,8 @@ Creating the Constraint Class First you need to create a Constraint class and extend :class:`Symfony\\Component\\Validator\\Constraint`:: - // src/Acme/DemoBundle/Validator/Constraints/ContainsAlphanumeric.php - namespace Acme\DemoBundle\Validator\Constraints; + // src/AppBundle/Validator/Constraints/ContainsAlphanumeric.php + namespace AppBundle\Validator\Constraints; use Symfony\Component\Validator\Constraint; @@ -54,8 +54,8 @@ when actually performing the validation. The validator class is also simple, and only has one required method ``validate()``:: - // src/Acme/DemoBundle/Validator/Constraints/ContainsAlphanumericValidator.php - namespace Acme\DemoBundle\Validator\Constraints; + // src/AppBundle/Validator/Constraints/ContainsAlphanumericValidator.php + namespace AppBundle\Validator\Constraints; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; @@ -65,22 +65,33 @@ The validator class is also simple, and only has one required method ``validate( public function validate($value, Constraint $constraint) { if (!preg_match('/^[a-zA-Za0-9]+$/', $value, $matches)) { + // If you're using the new 2.5 validation API (you probably are!) + $this->context->buildViolation($constraint->message) + ->setParameter('%string%', $value) + ->addViolation(); + + // If you're using the old 2.4 validation API + /* $this->context->addViolation( $constraint->message, array('%string%' => $value) ); + */ } } } -.. note:: +Inside ``validate``, you don't need to return a value. Instead, you add violations +to the validator's ``context`` property and a value will be considered valid +if it causes no violations. The ``buildViolation`` method takes the error +message as its argument and returns an instance of +:class:`Symfony\\Component\\Validator\\Violation\\ConstraintViolationBuilderInterface`. +The ``addViolation`` method call finally adds the violation to the context. - The ``validate`` method does not return a value; instead, it adds violations - to the validator's ``context`` property with an ``addViolation`` method - call if there are validation failures. Therefore, a value could be considered - as being valid if it causes no violations to be added to the context. - The first parameter of the ``addViolation`` call is the error message to - use for that violation. +.. versionadded:: 2.5 + The ``buildViolation`` method was added in Symfony 2.5. For usage examples + with older Symfony versions, see the corresponding versions of this documentation + page. Using the new Validator ----------------------- @@ -91,18 +102,18 @@ Using custom validators is very easy, just as the ones provided by Symfony itsel .. code-block:: yaml - # src/Acme/BlogBundle/Resources/config/validation.yml - Acme\DemoBundle\Entity\AcmeEntity: + # src/AppBundle/Resources/config/validation.yml + AppBundle\Entity\AcmeEntity: properties: name: - NotBlank: ~ - - Acme\DemoBundle\Validator\Constraints\ContainsAlphanumeric: ~ + - AppBundle\Validator\Constraints\ContainsAlphanumeric: ~ .. code-block:: php-annotations - // src/Acme/DemoBundle/Entity/AcmeEntity.php + // src/AppBundle/Entity/AcmeEntity.php use Symfony\Component\Validator\Constraints as Assert; - use Acme\DemoBundle\Validator\Constraints as AcmeAssert; + use AppBundle\Validator\Constraints as AcmeAssert; class AcmeEntity { @@ -119,26 +130,26 @@ Using custom validators is very easy, just as the ones provided by Symfony itsel .. code-block:: xml - + - + - + .. code-block:: php - // src/Acme/DemoBundle/Entity/AcmeEntity.php + // src/AppBundle/Entity/AcmeEntity.php use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Constraints\NotBlank; - use Acme\DemoBundle\Validator\Constraints\ContainsAlphanumeric; + use AppBundle\Validator\Constraints\ContainsAlphanumeric; class AcmeEntity { @@ -167,6 +178,7 @@ tag and an ``alias`` attribute: .. code-block:: yaml + # app/config/services.yml services: validator.unique.your_validator_name: class: Fully\Qualified\Validator\Class\Name @@ -175,6 +187,7 @@ tag and an ``alias`` attribute: .. code-block:: xml + @@ -182,6 +195,7 @@ tag and an ``alias`` attribute: .. code-block:: php + // app/config/services.php $container ->register('validator.unique.your_validator_name', 'Fully\Qualified\Validator\Class\Name') ->addTag('validator.constraint_validator', array('alias' => 'alias_name')); @@ -219,12 +233,20 @@ With this, the validator ``validate()`` method gets an object as its first argum public function validate($protocol, Constraint $constraint) { if ($protocol->getFoo() != $protocol->getBar()) { + // If you're using the new 2.5 validation API (you probably are!) + $this->context->buildViolation($constraint->message) + ->atPath('foo') + ->addViolation(); + + // If you're using the old 2.4 validation API + /* $this->context->addViolationAt( 'foo', $constraint->message, array(), null ); + */ } } } @@ -236,10 +258,10 @@ not to the property: .. code-block:: yaml - # src/Acme/BlogBundle/Resources/config/validation.yml - Acme\DemoBundle\Entity\AcmeEntity: + # src/AppBundle/Resources/config/validation.yml + AppBundle\Entity\AcmeEntity: constraints: - - Acme\DemoBundle\Validator\Constraints\ContainsAlphanumeric: ~ + - AppBundle\Validator\Constraints\ContainsAlphanumeric: ~ .. code-block:: php-annotations @@ -253,7 +275,7 @@ not to the property: .. code-block:: xml - - - + + + diff --git a/cookbook/web_server/built_in.rst b/cookbook/web_server/built_in.rst index f7ef8d1f039..33289907614 100644 --- a/cookbook/web_server/built_in.rst +++ b/cookbook/web_server/built_in.rst @@ -4,6 +4,10 @@ How to Use PHP's built-in Web Server ==================================== +.. versionadded:: 2.6 + The ability to run the server as a background process was introduced + in Symfony 2.6. + Since PHP 5.4 the CLI SAPI comes with a `built-in web server`_. It can be used to run your PHP applications locally during development, for testing or for application demonstrations. This way, you don't have to bother configuring @@ -19,25 +23,63 @@ Starting the Web Server ----------------------- Running a Symfony application using PHP's built-in web server is as easy as -executing the ``server:run`` command: +executing the ``server:start`` command: .. code-block:: bash - $ php app/console server:run + $ php app/console server:start -This starts a server at ``localhost:8000`` that executes your Symfony application. -The command will wait and will respond to incoming HTTP requests until you -terminate it (this is usually done by pressing Ctrl and C). +This starts the web server at ``localhost:8000`` in the background that serves +your Symfony application. By default, the web server listens on port 8000 on the loopback device. You -can change the socket passing an ip address and a port as a command-line argument: +can change the socket passing an IP address and a port as a command-line argument: .. code-block:: bash $ php app/console server:run 192.168.0.1:8080 +.. note:: + + You can use the ``server:status`` command to check if a web server is + listening on a certain socket: + + .. code-block:: bash + + $ php app/console server:status + + $ php app/console server:status 192.168.0.1:8080 + + The first command shows if your Symfony application will be server through + ``localhost:8000``, the second one does the same for ``192.168.0.1:8080``. + +.. note:: + + Before Symfony 2.6, the ``server:run`` command was used to start the built-in + web server. This command is still available and behaves slightly different. + Instead of starting the server in the background, it will block the current + terminal until you terminate it (this is usually done by pressing Ctrl + and C). + +.. sidebar:: Using the built-in Web Server from inside a Virtual Machine + + If you want to use the built-in web server from inside a virtual machine + and then load the site from a browser on your host machine, you'll need + to listen on the ``0.0.0.0:8000`` address (i.e. on all IP addresses that + are assigned to the virtual machine): + + .. code-block:: bash + + $ php app/console server:start 0.0.0.0:8000 + + .. caution:: + + You should **NEVER** listen to all interfaces on a computer that is + directly accessible from the Internet. The built-in web server is + not designed to be used on public networks. + Command Options ---------------- +~~~~~~~~~~~~~~~ The built-in web server expects a "router" script (read about the "router" script on `php.net`_) as an argument. Symfony already passes such a router @@ -47,14 +89,32 @@ script: .. code-block:: bash - $ php app/console server:run --env=test --router=app/config/router_test.php + $ php app/console server:start --env=test --router=app/config/router_test.php If your application's document root differs from the standard directory layout, you have to pass the correct location using the ``--docroot`` option: .. code-block:: bash - $ php app/console server:run --docroot=public_html + $ php app/console server:start --docroot=public_html + +Stopping the Server +------------------- + +When you are finished, you can simply stop the web server using the ``server:stop`` +command: + +.. code-block:: bash + + $ php app/console server:stop + +Like with the start command, if you omit the socket information, Symfony will +stop the web server bound to ``localhost:8000``. Just pass the socket information +when the web server listens to another IP address or to another port: + +.. code-block:: bash + + $ php app/console server:stop 192.168.0.1:8080 .. _`built-in web server`: http://www.php.net/manual/en/features.commandline.webserver.php .. _`php.net`: http://php.net/manual/en/features.commandline.webserver.php#example-401 diff --git a/cookbook/web_services/php_soap_extension.rst b/cookbook/web_services/php_soap_extension.rst index f01516f61c2..49ec8c323b1 100644 --- a/cookbook/web_services/php_soap_extension.rst +++ b/cookbook/web_services/php_soap_extension.rst @@ -59,7 +59,7 @@ a ``HelloService`` object properly: .. code-block:: yaml - # app/config/config.yml + # app/config/services.yml services: hello_service: class: Acme\SoapBundle\Services\HelloService @@ -67,7 +67,7 @@ a ``HelloService`` object properly: .. code-block:: xml - + @@ -76,7 +76,7 @@ a ``HelloService`` object properly: .. code-block:: php - // app/config/config.php + // app/config/services.php $container ->register('hello_service', 'Acme\SoapBundle\Services\HelloService') ->addArgument(new Reference('mailer')); @@ -190,7 +190,7 @@ An example WSDL is below. -.. _`PHP SOAP`: http://php.net/manual/en/book.soap.php -.. _`NuSOAP`: http://sourceforge.net/projects/nusoap -.. _`output buffering`: http://php.net/manual/en/book.outcontrol.php -.. _`Zend SOAP`: http://framework.zend.com/manual/en/zend.soap.server.html +.. _`PHP SOAP`: http://php.net/manual/en/book.soap.php +.. _`NuSOAP`: http://sourceforge.net/projects/nusoap +.. _`output buffering`: http://php.net/manual/en/book.outcontrol.php +.. _`Zend SOAP`: http://framework.zend.com/manual/en/zend.soap.server.html diff --git a/cookbook/workflow/_vendor_deps.rst.inc b/cookbook/workflow/_vendor_deps.rst.inc index 23ab3c3e073..322336627ca 100644 --- a/cookbook/workflow/_vendor_deps.rst.inc +++ b/cookbook/workflow/_vendor_deps.rst.inc @@ -9,44 +9,27 @@ way or another the goal is to download these files into your ``vendor/`` directory and, ideally, to give you some sane way to manage the exact version you need for each. -By default, these libraries are downloaded by running a ``php composer.phar install`` -"downloader" binary. This ``composer.phar`` file is from a library called -`Composer`_ and you can read more about installing it in the :ref:`Installation ` +By default, these libraries are downloaded by running a ``composer install`` +"downloader" binary. This ``composer`` file is from a library called `Composer`_ +and you can read more about installing it in the :ref:`Installation ` chapter. -The ``composer.phar`` file reads from the ``composer.json`` file at the root +The ``composer`` command reads from the ``composer.json`` file at the root of your project. This is an JSON-formatted file, which holds a list of each of the external packages you need, the version to be downloaded and more. -The ``composer.phar`` file also reads from a ``composer.lock`` file, which -allows you to pin each library to an **exact** version. In fact, if a ``composer.lock`` +``composer`` also reads from a ``composer.lock`` file, which allows you to +pin each library to an **exact** version. In fact, if a ``composer.lock`` file exists, the versions inside will override those in ``composer.json``. -To upgrade your libraries to new versions, run ``php composer.phar update``. +To upgrade your libraries to new versions, run ``composer update``. .. tip:: - If you want to add a new package to your application, modify the ``composer.json`` - file: - - .. code-block:: json - - { - "require": { - ... - "doctrine/doctrine-fixtures-bundle": "@dev" - } - } - - and then execute the ``update`` command for this specific package, i.e.: - - .. code-block:: bash - - $ php composer.phar update doctrine/doctrine-fixtures-bundle - - You can also combine both steps into a single command: + If you want to add a new package to your application, run the composer + ``require`` command: .. code-block:: bash - $ php composer.phar require doctrine/doctrine-fixtures-bundle:@dev + $ composer require doctrine/doctrine-fixtures-bundle To learn more about Composer, see `GetComposer.org`_: @@ -54,12 +37,12 @@ It's important to realize that these vendor libraries are *not* actually part of *your* repository. Instead, they're simply un-tracked files that are downloaded into the ``vendor/``. But since all the information needed to download these files is saved in ``composer.json`` and ``composer.lock`` (which *are* stored -in the repository), any other developer can use the project, run ``php composer.phar install``, +in the repository), any other developer can use the project, run ``composer install``, and download the exact same set of vendor libraries. This means that you're controlling exactly what each vendor library looks like, without needing to actually commit them to *your* repository. -So, whenever a developer uses your project, they should run the ``php composer.phar install`` +So, whenever a developer uses your project, they should run the ``composer install`` script to ensure that all of the needed vendor libraries are downloaded. .. sidebar:: Upgrading Symfony diff --git a/cookbook/workflow/new_project_git.rst b/cookbook/workflow/new_project_git.rst index c40cba6d53f..66a5a251761 100644 --- a/cookbook/workflow/new_project_git.rst +++ b/cookbook/workflow/new_project_git.rst @@ -19,18 +19,10 @@ that's stored using the `Git`_ source control management system. Initial Project Setup --------------------- -To get started, you'll need to download Symfony and initialize your local -git repository: +To get started, you'll need to download Symfony and get things running. See +the :doc:`/book/installation` chapter for details. -#. Download the `Symfony Standard Edition`_ using Composer: - - .. code-block:: bash - - $ php composer.phar create-project symfony/framework-standard-edition path/ '~2.3' - - Composer will now download the Standard Distribution along with all of the - required vendor libraries. For more information about downloading Symfony using - Composer, see `Installing Symfony using Composer`_. +Once your project is running, just follow these simple steps: #. Initialize your Git repository: @@ -84,16 +76,6 @@ to learn more about how to configure and develop inside your application. .. include:: _vendor_deps.rst.inc -Vendors and Submodules -~~~~~~~~~~~~~~~~~~~~~~ - -Instead of using the ``composer.json`` system for managing your vendor -libraries, you may instead choose to use native `git submodules`_. There -is nothing wrong with this approach, though the ``composer.json`` system -is the official way to solve this problem and probably much easier to -deal with. Unlike Git submodules, Composer is smart enough to calculate -which libraries depend on which other libraries. - Storing your Project on a remote Server --------------------------------------- @@ -113,7 +95,6 @@ manage this is `Gitolite`_. .. _`Git`: http://git-scm.com/ .. _`Symfony Standard Edition`: http://symfony.com/download -.. _`Installing Symfony using Composer`: http://symfony.com/doc/current/book/installation.html#option-1-composer .. _`git submodules`: http://git-scm.com/book/en/Git-Tools-Submodules .. _`GitHub`: https://github.com/ .. _`barebones repository`: http://git-scm.com/book/en/Git-Basics-Getting-a-Git-Repository diff --git a/cookbook/workflow/new_project_svn.rst b/cookbook/workflow/new_project_svn.rst index c8140d1f09a..934faa4ce2a 100644 --- a/cookbook/workflow/new_project_svn.rst +++ b/cookbook/workflow/new_project_svn.rst @@ -46,28 +46,27 @@ widespread standard structure: Initial Project Setup --------------------- -To get started, you'll need to download Symfony and get the basic Subversion setup: +To get started, you'll need to download Symfony and get the basic Subversion setup. +First, download and get your Symfony project running by following the +:doc:`Installation ` chapter. -1. Download the `Symfony Standard Edition`_ with or without vendors. +Once you have your new project directory and things are working, follow along +with these steps: -2. Unzip/untar the distribution. It will create a folder called Symfony with - your new project structure, config files, etc. Rename it to whatever you - like. - -3. Checkout the Subversion repository that will host this project. Suppose +#. Checkout the Subversion repository that will host this project. Suppose it is hosted on `Google code`_ and called ``myproject``: .. code-block:: bash $ svn checkout http://myproject.googlecode.com/svn/trunk myproject -4. Copy the Symfony project files in the Subversion folder: +#. Copy the Symfony project files in the Subversion folder: .. code-block:: bash $ mv Symfony/* myproject/ -5. Now, set the ignore rules. Not everything *should* be stored in your Subversion +#. Now, set the ignore rules. Not everything *should* be stored in your Subversion repository. Some files (like the cache) are generated and others (like the database configuration) are meant to be customized on each machine. This makes use of the ``svn:ignore`` property, so that specific files can @@ -88,27 +87,19 @@ To get started, you'll need to download Symfony and get the basic Subversion set $ svn ci -m "commit basic Symfony ignore list (vendor, app/bootstrap*, app/config/parameters.yml, app/cache/*, app/logs/*, web/bundles)" -6. The rest of the files can now be added and committed to the project: +#. The rest of the files can now be added and committed to the project: .. code-block:: bash $ svn add --force . $ svn ci -m "add basic Symfony Standard 2.X.Y" -7. Copy ``app/config/parameters.yml`` to ``app/config/parameters.yml.dist``. - The ``parameters.yml`` file is ignored by svn (see above) so that - machine-specific settings like database passwords aren't committed. By - creating the ``parameters.yml.dist`` file, new developers can quickly clone - the project, copy this file to ``parameters.yml``, customize it, and start - developing. - -8. Finally, download all of the third-party vendor libraries by - executing Composer. For details, see :ref:`installation-updating-vendors`. - -.. tip:: - - If you rely on any "dev" versions, then Git may be used to install - those libraries, since there is no archive available for download. +That's it! Since the ``app/config/parameters.yml`` file is ignored, you can +store machine-specific settings like database passwords here without committing +them. The ``parameters.yml.dist`` file *is* committed, but is not read by +Symfony. And by adding any new keys you need to both files, new developers +can quickly clone the project, copy this file to ``parameters.yml``, customize +it, and start developing. At this point, you have a fully-functional Symfony project stored in your Subversion repository. The development can start with commits in the Subversion diff --git a/images/book/security_admin_role_access.png b/images/book/security_admin_role_access.png deleted file mode 100644 index 8cbc86d26d4..00000000000 Binary files a/images/book/security_admin_role_access.png and /dev/null differ diff --git a/images/book/security_anonymous_user_access.png b/images/book/security_anonymous_user_access.png deleted file mode 100644 index 89694aa1ade..00000000000 Binary files a/images/book/security_anonymous_user_access.png and /dev/null differ diff --git a/images/book/security_anonymous_user_denied_authorization.png b/images/book/security_anonymous_user_denied_authorization.png deleted file mode 100644 index e793f10e953..00000000000 Binary files a/images/book/security_anonymous_user_denied_authorization.png and /dev/null differ diff --git a/images/book/security_anonymous_wdt.png b/images/book/security_anonymous_wdt.png new file mode 100644 index 00000000000..d780c894d5a Binary files /dev/null and b/images/book/security_anonymous_wdt.png differ diff --git a/images/book/security_authentication_authorization.png b/images/book/security_authentication_authorization.png deleted file mode 100644 index 6b085cf817e..00000000000 Binary files a/images/book/security_authentication_authorization.png and /dev/null differ diff --git a/images/book/security_full_step_authorization.png b/images/book/security_full_step_authorization.png deleted file mode 100644 index ceff81899a4..00000000000 Binary files a/images/book/security_full_step_authorization.png and /dev/null differ diff --git a/images/book/security_http_basic_popup.png b/images/book/security_http_basic_popup.png new file mode 100644 index 00000000000..2841b00e277 Binary files /dev/null and b/images/book/security_http_basic_popup.png differ diff --git a/images/book/security_ryan_no_role_admin_access.png b/images/book/security_ryan_no_role_admin_access.png deleted file mode 100644 index 3cb5078fe33..00000000000 Binary files a/images/book/security_ryan_no_role_admin_access.png and /dev/null differ diff --git a/images/book/symfony_loggedin_wdt.png b/images/book/symfony_loggedin_wdt.png new file mode 100644 index 00000000000..7280d9978bb Binary files /dev/null and b/images/book/symfony_loggedin_wdt.png differ diff --git a/images/book/translation/debug_1.png b/images/book/translation/debug_1.png new file mode 100644 index 00000000000..8f175f4d7ff Binary files /dev/null and b/images/book/translation/debug_1.png differ diff --git a/images/book/translation/debug_2.png b/images/book/translation/debug_2.png new file mode 100644 index 00000000000..04a57fa41d4 Binary files /dev/null and b/images/book/translation/debug_2.png differ diff --git a/images/book/translation/debug_3.png b/images/book/translation/debug_3.png new file mode 100644 index 00000000000..6ed595e097b Binary files /dev/null and b/images/book/translation/debug_3.png differ diff --git a/images/book/translation/debug_4.png b/images/book/translation/debug_4.png new file mode 100644 index 00000000000..db642b1773f Binary files /dev/null and b/images/book/translation/debug_4.png differ diff --git a/images/components/console/debug_formatter.png b/images/components/console/debug_formatter.png new file mode 100644 index 00000000000..7482f39851f Binary files /dev/null and b/images/components/console/debug_formatter.png differ diff --git a/images/components/console/process-helper-debug.png b/images/components/console/process-helper-debug.png new file mode 100644 index 00000000000..282e1336389 Binary files /dev/null and b/images/components/console/process-helper-debug.png differ diff --git a/images/components/console/process-helper-error-debug.png b/images/components/console/process-helper-error-debug.png new file mode 100644 index 00000000000..8d1145478f2 Binary files /dev/null and b/images/components/console/process-helper-error-debug.png differ diff --git a/images/components/console/process-helper-verbose.png b/images/components/console/process-helper-verbose.png new file mode 100644 index 00000000000..c4c912e1433 Binary files /dev/null and b/images/components/console/process-helper-verbose.png differ diff --git a/images/components/console/progressbar.gif b/images/components/console/progressbar.gif new file mode 100644 index 00000000000..6c80e6e897f Binary files /dev/null and b/images/components/console/progressbar.gif differ diff --git a/images/components/var_dumper/01-simple.png b/images/components/var_dumper/01-simple.png new file mode 100644 index 00000000000..a4d03147667 Binary files /dev/null and b/images/components/var_dumper/01-simple.png differ diff --git a/images/components/var_dumper/02-multi-line-str.png b/images/components/var_dumper/02-multi-line-str.png new file mode 100644 index 00000000000..b40949bd981 Binary files /dev/null and b/images/components/var_dumper/02-multi-line-str.png differ diff --git a/images/components/var_dumper/03-object.png b/images/components/var_dumper/03-object.png new file mode 100644 index 00000000000..47fc5e5e245 Binary files /dev/null and b/images/components/var_dumper/03-object.png differ diff --git a/images/components/var_dumper/04-dynamic-property.png b/images/components/var_dumper/04-dynamic-property.png new file mode 100644 index 00000000000..de7938c20cf Binary files /dev/null and b/images/components/var_dumper/04-dynamic-property.png differ diff --git a/images/components/var_dumper/05-soft-ref.png b/images/components/var_dumper/05-soft-ref.png new file mode 100644 index 00000000000..964af97ffd3 Binary files /dev/null and b/images/components/var_dumper/05-soft-ref.png differ diff --git a/images/components/var_dumper/06-constants.png b/images/components/var_dumper/06-constants.png new file mode 100644 index 00000000000..26c735bd613 Binary files /dev/null and b/images/components/var_dumper/06-constants.png differ diff --git a/images/components/var_dumper/07-hard-ref.png b/images/components/var_dumper/07-hard-ref.png new file mode 100644 index 00000000000..02dc17c9c40 Binary files /dev/null and b/images/components/var_dumper/07-hard-ref.png differ diff --git a/images/components/var_dumper/08-virtual-property.png b/images/components/var_dumper/08-virtual-property.png new file mode 100644 index 00000000000..564a2731ec1 Binary files /dev/null and b/images/components/var_dumper/08-virtual-property.png differ diff --git a/images/components/var_dumper/09-cut.png b/images/components/var_dumper/09-cut.png new file mode 100644 index 00000000000..5229f48820c Binary files /dev/null and b/images/components/var_dumper/09-cut.png differ diff --git a/index.rst b/index.rst index 23959281381..2ef2df24f45 100644 --- a/index.rst +++ b/index.rst @@ -45,6 +45,16 @@ Cookbook Read the :doc:`Cookbook `. +Best Practices +-------------- + +.. toctree:: + :hidden: + + best_practices/index + +Read the :doc:`Official Best Practices `. + Components ---------- @@ -67,29 +77,6 @@ Get answers quickly with reference documents: .. include:: /reference/map.rst.inc -Bundles -------- - -The Symfony Standard Edition comes with some bundles. Learn more about them: - -.. toctree:: - :hidden: - - bundles/index - -.. include:: /bundles/map.rst.inc - -CMF ---- - -The Symfony CMF project makes it easier for developers to add CMS functionality -to applications built with the Symfony PHP framework. - -.. toctree:: - :hidden: - - cmf/index - Contributing ------------ diff --git a/install.sh b/install.sh deleted file mode 100644 index c50451959a0..00000000000 --- a/install.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash - -function sparse_checkout { - mkdir sparse_checkout - cd sparse_checkout - git init - git config core.sparsecheckout true - git remote add -f origin http://github.com/$1/$2 - echo Resources/doc > .git/info/sparse-checkout - git checkout master - rm -rf ../bundles/$2 - mv Resources/doc ../bundles/$2 - cd .. - rm -rf sparse_checkout -} - -sparse_checkout sensiolabs SensioFrameworkExtraBundle -sparse_checkout sensiolabs SensioGeneratorBundle -sparse_checkout doctrine DoctrineFixturesBundle -sparse_checkout doctrine DoctrineMigrationsBundle -sparse_checkout doctrine DoctrineMongoDBBundle -rm -rf cmf -git clone http://github.com/symfony-cmf/symfony-cmf-docs cmf - diff --git a/make.bat b/make.bat index a37807af545..cfda326358d 100644 --- a/make.bat +++ b/make.bat @@ -1,153 +1,263 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = _build - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -# the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - -clean: - -rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Symfony.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Symfony.qhc" - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/Symfony" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Symfony" - @echo "# devhelp" - -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +set I18NSPHINXOPTS=%SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. xml to make Docutils-native XML files + echo. pseudoxml to make pseudoxml-XML files for display purposes + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + echo. coverage to run coverage check of the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + + +REM Check if sphinx-build is available and fallback to Python version if any +%SPHINXBUILD% 2> nul +if errorlevel 9009 goto sphinx_python +goto sphinx_ok + +:sphinx_python + +set SPHINXBUILD=python -m sphinx.__init__ +%SPHINXBUILD% 2> nul +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +:sphinx_ok + + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Symfony.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Symfony.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdf" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdfja" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf-ja + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +if "%1" == "coverage" ( + %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage + if errorlevel 1 exit /b 1 + echo. + echo.Testing of coverage in the sources finished, look at the ^ +results in %BUILDDIR%/coverage/python.txt. + goto end +) + +if "%1" == "xml" ( + %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The XML files are in %BUILDDIR%/xml. + goto end +) + +if "%1" == "pseudoxml" ( + %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. + goto end +) + +:end diff --git a/quick_tour/the_architecture.rst b/quick_tour/the_architecture.rst index 927f4512e60..536210c6529 100644 --- a/quick_tour/the_architecture.rst +++ b/quick_tour/the_architecture.rst @@ -13,17 +13,21 @@ Understanding the Directory Structure The directory structure of a Symfony :term:`application` is rather flexible, but the recommended structure is as follows: -* ``app/``: the application configuration; -* ``src/``: the project's PHP code; -* ``vendor/``: the third-party dependencies; -* ``web/``: the web root directory. +``app/`` + The application configuration, templates and translations. +``src/`` + The project's PHP code. +``vendor/`` + The third-party dependencies. +``web/`` + The web root directory. The ``web/`` Directory ~~~~~~~~~~~~~~~~~~~~~~ The web root directory is the home of all public and static files like images, stylesheets, and JavaScript files. It is also where each :term:`front controller` -lives:: +lives, such as the production controller shown here:: // web/app.php require_once __DIR__.'/../app/bootstrap.php.cache'; @@ -33,7 +37,9 @@ lives:: $kernel = new AppKernel('prod', false); $kernel->loadClassCache(); - $kernel->handle(Request::createFromGlobals())->send(); + $request = Request::createFromGlobals(); + $response = $kernel->handle($request); + $response->send(); The controller first bootstraps the application using a kernel class (``AppKernel`` in this case). Then, it creates the ``Request`` object using the PHP's global @@ -50,11 +56,11 @@ configuration and as such, it is stored in the ``app/`` directory. This class must implement two methods: -* ``registerBundles()`` must return an array of all bundles needed to run the - application; - -* ``registerContainerConfiguration()`` loads the application configuration - (more on this later). +``registerBundles()`` + Must return an array of all bundles needed to run the application, as explained + in the next section. +``registerContainerConfiguration()`` + Loads the application configuration (more on this later). Autoloading is handled automatically via `Composer`_, which means that you can use any PHP class without doing anything at all! All dependencies @@ -73,18 +79,27 @@ A bundle is kind of like a plugin in other software. So why is it called a Symfony, from the core framework features to the code you write for your application. +All the code you write for your application is organized in bundles. In Symfony +speak, a bundle is a structured set of files (PHP files, stylesheets, JavaScripts, +images, ...) that implements a single feature (a blog, a forum, ...) and which +can be easily shared with other developers. + Bundles are first-class citizens in Symfony. This gives you the flexibility to use pre-built features packaged in third-party bundles or to distribute your own bundles. It makes it easy to pick and choose which features to enable in your application and optimize them the way you want. And at the end of the day, your application code is just as *important* as the core framework itself. +Symfony already includes an AppBundle that you may use to start developing your +application. Then, if you need to split the application into reusable +components, you can create your own bundles. + Registering a Bundle ~~~~~~~~~~~~~~~~~~~~ An application is made up of bundles as defined in the ``registerBundles()`` method of the ``AppKernel`` class. Each bundle is a directory that contains -a single ``Bundle`` class that describes it:: +a single Bundle class that describes it:: // app/AppKernel.php public function registerBundles() @@ -98,10 +113,10 @@ a single ``Bundle`` class that describes it:: new Symfony\Bundle\DoctrineBundle\DoctrineBundle(), new Symfony\Bundle\AsseticBundle\AsseticBundle(), new Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(), + new AppBundle\AppBundle(); ); if (in_array($this->getEnvironment(), array('dev', 'test'))) { - $bundles[] = new Acme\DemoBundle\AcmeDemoBundle(); $bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle(); $bundles[] = new Sensio\Bundle\DistributionBundle\SensioDistributionBundle(); $bundles[] = new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle(); @@ -110,16 +125,15 @@ a single ``Bundle`` class that describes it:: return $bundles; } -In addition to the AcmeDemoBundle that was already talked about, notice -that the kernel also enables other bundles such as the FrameworkBundle, -DoctrineBundle, SwiftmailerBundle and AsseticBundle bundle. They are all part -of the core framework. +In addition to the AppBundle that was already talked about, notice that the +kernel also enables other bundles that are part of Symfony, such as FrameworkBundle, +DoctrineBundle, SwiftmailerBundle and AsseticBundle. Configuring a Bundle ~~~~~~~~~~~~~~~~~~~~ Each bundle can be customized via configuration files written in YAML, XML, or -PHP. Have a look at the default Symfony configuration: +PHP. Have a look at this sample of the default Symfony configuration: .. code-block:: yaml @@ -127,6 +141,7 @@ PHP. Have a look at the default Symfony configuration: imports: - { resource: parameters.yml } - { resource: security.yml } + - { resource: services.yml } framework: #esi: ~ @@ -138,7 +153,7 @@ PHP. Have a look at the default Symfony configuration: form: true csrf_protection: true validation: { enable_annotations: true } - templating: { engines: ['twig'] } #assets_version: SomeVersionScheme + templating: { engines: ['twig'] } default_locale: "%locale%" trusted_proxies: ~ session: ~ @@ -148,34 +163,6 @@ PHP. Have a look at the default Symfony configuration: debug: "%kernel.debug%" strict_variables: "%kernel.debug%" - # Assetic Configuration - assetic: - debug: "%kernel.debug%" - use_controller: false - bundles: [ ] - #java: /usr/bin/java - filters: - cssrewrite: ~ - #closure: - # jar: "%kernel.root_dir%/Resources/java/compiler.jar" - #yui_css: - # jar: "%kernel.root_dir%/Resources/java/yuicompressor-2.4.7.jar" - - # Doctrine Configuration - doctrine: - dbal: - driver: "%database_driver%" - host: "%database_host%" - port: "%database_port%" - dbname: "%database_name%" - user: "%database_user%" - password: "%database_password%" - charset: UTF8 - - orm: - auto_generate_proxy_classes: "%kernel.debug%" - auto_mapping: true - # Swift Mailer Configuration swiftmailer: transport: "%mailer_transport%" @@ -184,9 +171,11 @@ PHP. Have a look at the default Symfony configuration: password: "%mailer_password%" spool: { type: memory } -Each first level entry like ``framework``, ``twig`` or ``doctrine`` defines the -configuration for a specific bundle. For example, ``framework`` configures the -FrameworkBundle while ``swiftmailer`` configures the SwiftmailerBundle. + # ... + +Each first level entry like ``framework``, ``twig`` and ``swiftmailer`` defines +the configuration for a specific bundle. For example, ``framework`` configures +the FrameworkBundle while ``swiftmailer`` configures the SwiftmailerBundle. Each :term:`environment` can override the default configuration by providing a specific configuration file. For example, the ``dev`` environment loads the @@ -207,18 +196,7 @@ and then modifies it to add some debugging tools: toolbar: true intercept_redirects: false - monolog: - handlers: - main: - type: stream - path: "%kernel.logs_dir%/%kernel.environment%.log" - level: debug - firephp: - type: firephp - level: info - - assetic: - use_controller: true + # ... Extending a Bundle ~~~~~~~~~~~~~~~~~~ @@ -226,8 +204,6 @@ Extending a Bundle In addition to being a nice way to organize and configure your code, a bundle can extend another bundle. Bundle inheritance allows you to override any existing bundle in order to customize its controllers, templates, or any of its files. -This is where the logical names (e.g. ``@AcmeDemoBundle/Controller/SecuredController.php``) -come in handy: they abstract where the resource is actually stored. Logical File Names .................. @@ -235,36 +211,27 @@ Logical File Names When you want to reference a file from a bundle, use this notation: ``@BUNDLE_NAME/path/to/file``; Symfony will resolve ``@BUNDLE_NAME`` to the real path to the bundle. For instance, the logical path -``@AcmeDemoBundle/Controller/DemoController.php`` would be converted to -``src/Acme/DemoBundle/Controller/DemoController.php``, because Symfony knows -the location of the AcmeDemoBundle. +``@AppBundle/Controller/DefaultController.php`` would be converted to +``src/AppBundle/Controller/DefaultController.php``, because Symfony knows +the location of the AppBundle. Logical Controller Names ........................ -For controllers, you need to reference method names using the format +For controllers, you need to reference actions using the format ``BUNDLE_NAME:CONTROLLER_NAME:ACTION_NAME``. For instance, -``AcmeDemoBundle:Welcome:index`` maps to the ``indexAction`` method from the -``Acme\DemoBundle\Controller\WelcomeController`` class. - -Logical Template Names -...................... - -For templates, the logical name ``AcmeDemoBundle:Welcome:index.html.twig`` is -converted to the file path ``src/Acme/DemoBundle/Resources/views/Welcome/index.html.twig``. -Templates become even more interesting when you realize they don't need to be -stored on the filesystem. You can easily store them in a database table for -instance. +``AppBundle:Default:index`` maps to the ``indexAction`` method from the +``AppBundle\Controller\DefaultController`` class. Extending Bundles ................. -If you follow these conventions, then you can use :doc:`bundle inheritance` -to "override" files, controllers or templates. For example, you can create -a bundle - AcmeNewBundle - and specify that it overrides AcmeDemoBundle. -When Symfony loads the ``AcmeDemoBundle:Welcome:index`` controller, it will -first look for the ``WelcomeController`` class in AcmeNewBundle and, if -it doesn't exist, then look inside AcmeDemoBundle. This means that one bundle +If you follow these conventions, then you can use :doc:`bundle inheritance ` +to override files, controllers or templates. For example, you can create +a bundle - NewBundle - and specify that it overrides AppBundle. +When Symfony loads the ``AppBundle:Default:index`` controller, it will +first look for the ``DefaultController`` class in NewBundle and, if +it doesn't exist, then look inside AppBundle. This means that one bundle can override almost any part of another bundle! Do you understand now why Symfony is so flexible? Share your bundles between @@ -276,22 +243,28 @@ Using Vendors ------------- Odds are that your application will depend on third-party libraries. Those -should be stored in the ``vendor/`` directory. This directory already contains -the Symfony libraries, the SwiftMailer library, the Doctrine ORM, the Twig -templating system, and some other third party libraries and bundles. +should be stored in the ``vendor/`` directory. You should never touch anything +in this directory, because it is exclusively managed by Composer. This directory +already contains the Symfony libraries, the SwiftMailer library, the Doctrine ORM, +the Twig templating system and some other third party libraries and bundles. Understanding the Cache and Logs -------------------------------- -Symfony is probably one of the fastest full-stack frameworks around. But how -can it be so fast if it parses and interprets tens of YAML and XML files for -each request? The speed is partly due to its cache system. The application +Symfony applications can contain several configuration files defined in several +formats (YAML, XML, PHP, etc.) Instead of parsing and combining all those files +for each request, Symfony uses its own cache system. In fact, the application configuration is only parsed for the very first request and then compiled down -to plain PHP code stored in the ``app/cache/`` directory. In the development -environment, Symfony is smart enough to flush the cache when you change a -file. But in the production environment, to speed things up, it is your -responsibility to clear the cache when you update your code or change its -configuration. +to plain PHP code stored in the ``app/cache/`` directory. + +In the development environment, Symfony is smart enough to update the cache when +you change a file. But in the production environment, to speed things up, it is +your responsibility to clear the cache when you update your code or change its +configuration. Execute this command to clear the cache in the ``prod`` environment: + +.. code-block:: bash + + $ php app/console cache:clear --env=prod When developing a web application, things can go wrong in many ways. The log files in the ``app/logs/`` directory tell you everything about the requests @@ -314,7 +287,7 @@ The ``--help`` option helps you discover the usage of a command: .. code-block:: bash - $ php app/console router:debug --help + $ php app/console debug:router --help Final Thoughts -------------- diff --git a/quick_tour/the_big_picture.rst b/quick_tour/the_big_picture.rst index 3cfc648f2d5..1dd9e8fa48a 100644 --- a/quick_tour/the_big_picture.rst +++ b/quick_tour/the_big_picture.rst @@ -1,312 +1,276 @@ The Big Picture =============== -Start using Symfony in 10 minutes! This chapter will walk you through some of -the most important concepts behind Symfony and explain how you can get started -quickly by showing you a simple project in action. +Start using Symfony in 10 minutes! This chapter will walk you through the most +important concepts behind Symfony and explain how you can get started quickly +by showing you a simple project in action. If you've used a web framework before, you should feel right at home with Symfony. If not, welcome to a whole new way of developing web applications. +The only technical requisite to follow this tutorial is to have **PHP 5.4 or higher +installed on your computer**. If you use a packaged PHP solution such as WAMP, +XAMP or MAMP, check out that they are using PHP 5.4 or a more recent version. +You can also execute the following command in your terminal or command console +to display the installed PHP version: + +.. code-block:: bash + + $ php --version + .. _installing-symfony2: Installing Symfony ------------------ -First, check that the PHP version installed on your computer meets the Symfony -requirements: 5.3.3 or higher. Then, open a console and execute the following -command to install the latest version of Symfony in the ``myproject/`` -directory: +In the past, Symfony had to be installed manually for each new project. Now you +can use the **Symfony Installer**, which has to be installed the very first time +you use Symfony on a computer. + +On **Linux** and **Mac OS X** systems, execute the following console commands: .. code-block:: bash - $ composer create-project symfony/framework-standard-edition myproject/ '~2.3' + $ curl -LsS http://symfony.com/installer > symfony.phar + $ sudo mv symfony.phar /usr/local/bin/symfony + $ chmod a+x /usr/local/bin/symfony .. note:: - `Composer`_ is the package manager used by modern PHP applications and the - only recommended way to install Symfony. To install Composer on your - Linux or Mac system, execute the following commands: + If your system doesn't have cURL installed, execute the following + commands instead: .. code-block:: bash - $ curl -sS https://getcomposer.org/installer | php - $ sudo mv composer.phar /usr/local/bin/composer - - To install Composer on a Windows system, download the `executable installer`_. + $ php -r "readfile('http://symfony.com/installer');" > symfony.phar + $ sudo mv symfony.phar /usr/local/bin/symfony + $ chmod a+x /usr/local/bin/symfony -Beware that the first time you install Symfony, it may take a few minutes to -download all its components. At the end of the installation process, the -installer will ask you to provide some configuration options for the Symfony -project. For this first project you can safely ignore this configuration by -pressing the ```` key repeatedly. +After installing the Symfony installer, you'll have to open a new console window +to be able to execute the new ``symfony`` command: -.. _running-symfony2: +.. code-block:: bash -Running Symfony ---------------- + $ symfony -Before running Symfony for the first time, execute the following command to -make sure that your system meets all the technical requirements: +On **Windows** systems, execute the following console command: .. code-block:: bash - $ cd myproject/ - $ php app/check.php + c:\> php -r "readfile('http://symfony.com/installer');" > symfony.phar -Fix any error reported by the command and then use the PHP built-in web server -to run Symfony: +This command downloads a file called ``symfony.phar`` which contains the Symfony +installer. Save or move that file to the directory where you create the Symfony +projects and then, execute the Symfony installer right away with this command: .. code-block:: bash - $ php app/console server:run + c:\> php symfony.phar -.. seealso:: +Creating Your First Symfony Project +----------------------------------- - Read more about the internal server :doc:`in the cookbook `. +Once the Symfony Installer is set up, use the ``new`` command to create new +Symfony projects. Let's create a new project called ``myproject``: -If you get the error `There are no commands defined in the "server" namespace.`, -then you are probably using PHP 5.3. That's ok! But the built-in web server is -only available for PHP 5.4.0 or higher. If you have an older version of PHP or -if you prefer a traditional web server such as Apache or Nginx, read the -:doc:`/cookbook/configuration/web_server_configuration` article. +.. code-block:: bash -Open your browser and access the ``http://localhost:8000`` URL to see the -Welcome page of Symfony: + # Linux and Mac OS X + $ symfony new myproject -.. image:: /images/quick_tour/welcome.png - :align: center - :alt: Symfony Welcome Page + # Windows + c:\> php symfony.phar new myproject -Understanding the Fundamentals ------------------------------- +This command downloads the latest Symfony stable version and creates an empty +project in the ``myproject/`` directory so you can start developing your +application right away. -One of the main goals of a framework is to keep your code organized and to allow -your application to evolve easily over time by avoiding the mixing of database -calls, HTML tags and business logic in the same script. To achieve this goal -with Symfony, you'll first need to learn a few fundamental concepts and terms. +.. _running-symfony2: -Symfony comes with some sample code that you can use to learn more about its -main concepts. Go to the following URL to be greeted by Symfony (replace -*Fabien* with your first name): +Running Symfony +--------------- + +This tutorial leverages the internal web server provided by PHP to run Symfony +applications. Therefore, running a Symfony application is a matter of browsing +the project directory and executing this command: + +.. code-block:: bash -.. code-block:: text + $ cd myproject/ + $ php app/console server:run - http://localhost:8000/app_dev.php/demo/hello/Fabien +Open your browser and access the ``http://localhost:8000`` URL to see the +Welcome page of Symfony: -.. image:: /images/quick_tour/hello_fabien.png +.. image:: /images/quick_tour/welcome.png :align: center + :alt: Symfony Welcome Page + +Congratulations! Your first Symfony project is up and running! .. note:: - Instead of the greeting page, you may see a blank page or an error page. + Instead of the welcome page, you may see a blank page or an error page. This is caused by a directory permission misconfiguration. There are several possible solutions depending on your operating system. All of them are explained in the :ref:`Setting up Permissions ` section of the official book. -What's going on here? Have a look at each part of the URL: - -* ``app_dev.php``: This is a :term:`front controller`. It is the unique entry - point of the application and it responds to all user requests; - -* ``/demo/hello/Fabien``: This is the *virtual path* to the resource the user - wants to access. - -Your responsibility as a developer is to write the code that maps the user's -*request* (``/demo/hello/Fabien``) to the *resource* associated with it -(the ``Hello Fabien!`` HTML page). - -Routing -~~~~~~~ - -Symfony routes the request to the code that handles it by matching the -requested URL (i.e. the virtual path) against some configured paths. The demo -paths are defined in the ``app/config/routing_dev.yml`` configuration file: - -.. code-block:: yaml - - # app/config/routing_dev.yml - # ... - - # AcmeDemoBundle routes (to be removed) - _acme_demo: - resource: "@AcmeDemoBundle/Resources/config/routing.yml" - -This imports a ``routing.yml`` file that lives inside the AcmeDemoBundle: +When you are finished working on your Symfony application, you can stop the +server with the ``server:stop`` command: -.. code-block:: yaml - - # src/Acme/DemoBundle/Resources/config/routing.yml - _welcome: - path: / - defaults: { _controller: AcmeDemoBundle:Welcome:index } - - _demo: - resource: "@AcmeDemoBundle/Controller/DemoController.php" - type: annotation - prefix: /demo - - # ... +.. code-block:: bash -The first three lines (after the comment) define the code that is executed -when the user requests the "``/``" resource (i.e. the welcome page you saw -earlier). When requested, the ``AcmeDemoBundle:Welcome:index`` controller -will be executed. In the next section, you'll learn exactly what that means. + $ php app/console server:stop .. tip:: - In addition to YAML files, routes can be configured in XML or PHP files - and can even be embedded in PHP annotations. This flexibility is one of the - main features of Symfony, a framework that never imposes a particular - configuration format on you. + If you prefer a traditional web server such as Apache or Nginx, read the + :doc:`/cookbook/configuration/web_server_configuration` article. + +Understanding the Fundamentals +------------------------------ -Controllers -~~~~~~~~~~~ +One of the main goals of a framework is to keep your code organized and to allow +your application to evolve easily over time by avoiding the mixing of database +calls, HTML tags and other PHP code in the same script. To achieve this goal +with Symfony, you'll first need to learn a few fundamental concepts. -A controller is a PHP function or method that handles incoming *requests* and -returns *responses* (often HTML code). Instead of using the PHP global variables -and functions (like ``$_GET`` or ``header()``) to manage these HTTP messages, -Symfony uses objects: :ref:`Request ` -and :ref:`Response `. The simplest possible -controller might create the response by hand, based on the request:: +When developing a Symfony application, your responsibility as a developer is to +write the code that maps the user's *request* (e.g. ``http://localhost:8000/``) +to the *resource* associated with it (the ``Welcome to Symfony!`` HTML page). - use Symfony\Component\HttpFoundation\Response; +The code to execute is defined in **actions** and **controllers**. The mapping +between user's requests and that code is defined via the **routing** configuration. +And the contents displayed in the browser are usually rendered using **templates**. - $name = $request->get('name'); +When you browsed ``http://localhost:8000/`` earlier, Symfony executed the +controller defined in the ``src/AppBundle/Controller/DefaultController.php`` +file and rendered the ``app/Resources/views/default/index.html.twig`` template. +In the following sections you'll learn in detail the inner workings of Symfony +controllers, routes and templates. - return new Response('Hello '.$name); +Actions and Controllers +~~~~~~~~~~~~~~~~~~~~~~~ -Symfony chooses the controller based on the ``_controller`` value from the -routing configuration: ``AcmeDemoBundle:Welcome:index``. This string is the -controller *logical name*, and it references the ``indexAction`` method from -the ``Acme\DemoBundle\Controller\WelcomeController`` class:: +Open the ``src/AppBundle/Controller/DefaultController.php`` file and you'll see +the following code (for now, don't look at the ``@Route`` configuration because +that will be explained in the next section):: - // src/Acme/DemoBundle/Controller/WelcomeController.php - namespace Acme\DemoBundle\Controller; + namespace AppBundle\Controller; + use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Symfony\Bundle\FrameworkBundle\Controller\Controller; - class WelcomeController extends Controller + class DefaultController extends Controller { + /** + * @Route("/", name="homepage") + */ public function indexAction() { - return $this->render('AcmeDemoBundle:Welcome:index.html.twig'); + return $this->render('default/index.html.twig'); } } -.. tip:: - - You could have used the full class and method name - - ``Acme\DemoBundle\Controller\WelcomeController::indexAction`` - for the - ``_controller`` value. But using the logical name is shorter and allows - for more flexibility. - -The ``WelcomeController`` class extends the built-in ``Controller`` class, -which provides useful shortcut methods, like the -:ref:`render()` method that loads and renders -a template (``AcmeDemoBundle:Welcome:index.html.twig``). The returned value -is a ``Response`` object populated with the rendered content. So, if the need -arises, the ``Response`` can be tweaked before it is sent to the browser:: - - public function indexAction() - { - $response = $this->render('AcmeDemoBundle:Welcome:index.txt.twig'); - $response->headers->set('Content-Type', 'text/plain'); +In Symfony applications, **controllers** are usually PHP classes whose names are +suffixed with the ``Controller`` word. In this example, the controller is called +``Default`` and the PHP class is called ``DefaultController``. - return $response; - } +The methods defined in a controller are called **actions**, they are usually +associated with one URL of the application and their names are suffixed with +``Action``. In this example, the ``Default`` controller has only one action +called ``index`` and defined in the ``indexAction`` method. -No matter how you do it, the end goal of your controller is always to return -the ``Response`` object that should be delivered back to the user. This ``Response`` -object can be populated with HTML code, represent a client redirect, or even -return the contents of a JPG image with a ``Content-Type`` header of ``image/jpg``. +Actions are usually very short - around 10-15 lines of code - because they just +call other parts of the application to get or generate the needed information and +then they render a template to show the results to the user. -The template name, ``AcmeDemoBundle:Welcome:index.html.twig``, is the template -*logical name* and it references the ``Resources/views/Welcome/index.html.twig`` -file inside the AcmeDemoBundle (located at ``src/Acme/DemoBundle``). -The `Bundles`_ section below will explain why this is useful. +In this example, the ``index`` action is practically empty because it doesn't +need to call any other method. The action just renders a template with the +*Welcome to Symfony!* content. -Now, take a look at the routing configuration again and find the ``_demo`` -key: +Routing +~~~~~~~ -.. code-block:: yaml +Symfony routes each request to the action that handles it by matching the +requested URL against the paths configured by the application. Open again the +``src/AppBundle/Controller/DefaultController.php`` file and take a look at the +three lines of code above the ``indexAction`` method: - # src/Acme/DemoBundle/Resources/config/routing.yml - # ... - _demo: - resource: "@AcmeDemoBundle/Controller/DemoController.php" - type: annotation - prefix: /demo +.. code-block:: php -The *logical name* of the file containing the ``_demo`` routes is -``@AcmeDemoBundle/Controller/DemoController.php`` and refers -to the ``src/Acme/DemoBundle/Controller/DemoController.php`` file. In this -file, routes are defined as annotations on action methods:: + // src/AppBundle/Controller/DefaultController.php + namespace AppBundle\Controller; - // src/Acme/DemoBundle/Controller/DemoController.php use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; - use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; + use Symfony\Bundle\FrameworkBundle\Controller\Controller; - class DemoController extends Controller + class DefaultController extends Controller { /** - * @Route("/hello/{name}", name="_demo_hello") - * @Template() + * @Route("/", name="homepage") */ - public function helloAction($name) + public function indexAction() { - return array('name' => $name); + return $this->render('default/index.html.twig'); } - - // ... } -The ``@Route()`` annotation creates a new route matching the ``/hello/{name}`` -path to the ``helloAction()`` method. Any string enclosed in curly brackets, -like ``{name}``, is considered a variable that can be directly retrieved as a -method argument with the same name. +These three lines define the routing configuration via the ``@Route()`` annotation. +A **PHP annotation** is a convenient way to configure a method without having to +write regular PHP code. Beware that annotation blocks start with ``/**``, whereas +regular PHP comments start with ``/*``. + +The first value of ``@Route()`` defines the URL that will trigger the execution +of the action. As you don't have to add the host of your application to the URL +(e.g. ```http://example.com``), these URLs are always relative and they are usually +called *paths*. In this case, the ``/`` path refers to the application homepage. +The second value of ``@Route()`` (e.g. ``name="homepage"``) is optional and sets +the name of this route. For now this name is not needed, but later it'll be useful +for linking pages. + +Considering all this, the ``@Route("/", name="homepage")`` annotation creates a +new route called ``homepage`` which makes Symfony execute the ``index`` action +of the ``Default`` controller when the user browses the ``/`` path of the application. + +.. tip:: -If you take a closer look at the controller code, you can see that instead of -rendering a template and returning a ``Response`` object like before, it -just returns an array of parameters. The ``@Template()`` annotation tells -Symfony to render the template for you, passing to it each variable of the -returned array. The name of the template that's rendered follows the name -of the controller. So, in this example, the ``AcmeDemoBundle:Demo:hello.html.twig`` -template is rendered (located at ``src/Acme/DemoBundle/Resources/views/Demo/hello.html.twig``). + In addition to PHP annotations, routes can be configured in YAML, XML or + PHP files, as explained in :doc:`the Routing chapter of the Symfony book `. + This flexibility is one of the main features of Symfony, a framework that + never imposes a particular configuration format on you. Templates ~~~~~~~~~ -The controller renders the ``src/Acme/DemoBundle/Resources/views/Demo/hello.html.twig`` -template (or ``AcmeDemoBundle:Demo:hello.html.twig`` if you use the logical name): +The only content of the ``index`` action is this PHP instruction: -.. code-block:: jinja +.. code-block:: php - {# src/Acme/DemoBundle/Resources/views/Demo/hello.html.twig #} - {% extends "AcmeDemoBundle::layout.html.twig" %} + return $this->render('default/index.html.twig'); - {% block title "Hello " ~ name %} +The ``$this->render()`` method is a convenient shortcut to render a template. +Symfony provides some useful shortcuts to any controller extending from the +``Controller`` class. - {% block content %} -

      Hello {{ name }}!

      - {% endblock %} +By default, application templates are stored in the ``app/Resources/views/`` +directory. Therefore, the ``default/index.html.twig`` template corresponds to the +``app/Resources/views/default/index.html.twig``. Open that file and you'll see +the following code: -By default, Symfony uses `Twig`_ as its template engine but you can also use -traditional PHP templates if you choose. The -:doc:`second part of this tutorial` will introduce how -templates work in Symfony. +.. code-block:: html+jinja -Bundles -~~~~~~~ + {# app/Resources/views/default/index.html.twig #} + {% extends 'base.html.twig' %} + + {% block body %} +

      Welcome to Symfony!

      + {% endblock %} -You might have wondered why the :term:`Bundle` word is used in many names you -have seen so far. All the code you write for your application is organized in -bundles. In Symfony speak, a bundle is a structured set of files (PHP files, -stylesheets, JavaScripts, images, ...) that implements a single feature (a -blog, a forum, ...) and which can be easily shared with other developers. As -of now, you have manipulated one bundle, AcmeDemoBundle. You will learn -more about bundles in the :doc:`last part of this tutorial`. +This template is created with `Twig`_, a new template engine created for modern +PHP applications. The :doc:`second part of this tutorial ` +will introduce how templates work in Symfony. .. _quick-tour-big-picture-environments: @@ -328,9 +292,13 @@ the request, the query parameters, security details, and database queries: .. image:: /images/quick_tour/profiler.png :align: center -Of course, it would be unwise to have this tool enabled when you deploy your -application, so by default, the profiler is not enabled in the ``prod`` -environment. +This tool provides so much internal information about your application that you +may be worried about your visitors accessing sensible information. Symfony is +aware of this issue and for that reason, it won't display this bar when your +application is running in the production server. + +How does Symfony know whether your application is running locally or on a +production server? Keep reading to discover the concept of **execution environments**. .. _quick-tour-big-picture-environments-intro: @@ -342,6 +310,22 @@ your application. Symfony defines two environments by default: ``dev`` (suited for when developing the application locally) and ``prod`` (optimized for when executing the application on production). +When you visit the ``http://localhost:8000`` URL in your browser, you're executing +your Symfony application in the ``dev`` environment. To visit your application +in the ``prod`` environment, visit the ``http://localhost:8000/app.php`` URL instead. +If you prefer to always show the ``dev`` environment in the URL, you can visit +``http://localhost:8000/app_dev.php`` URL. + +The main difference between environments is that ``dev`` is optimized to provide +lots of information to the developer, which means worse application performance. +Meanwhile, ``prod`` is optimized to get the best performance, which means that +debug information is disabled, as well as the Web Debug Toolbar. + +The other difference between environments is the configuration options used to +execute the application. When you access the ``dev`` environment, Symfony loads +the ``app/config/config_dev.yml`` configuration file. When you access the ``prod`` +environment, Symfony loads ``app/config/config_prod.yml`` file. + Typically, the environments share a large amount of configuration options. For that reason, you put your common configuration in ``config.yml`` and override the specific configuration file for each environment where necessary: @@ -356,29 +340,9 @@ the specific configuration file for each environment where necessary: toolbar: true intercept_redirects: false -In this example, the ``dev`` environment loads the ``config_dev.yml`` configuration -file, which itself imports the common ``config.yml`` file and then modifies it -by enabling the web debug toolbar. - -When you visit the ``app_dev.php`` file in your browser, you're executing -your Symfony application in the ``dev`` environment. To visit your application -in the ``prod`` environment, visit the ``app.php`` file instead. - -The demo routes in our application are only available in the ``dev`` environment. -Therefore, if you try to access the ``http://localhost/app.php/demo/hello/Fabien`` -URL, you'll get a 404 error. - -.. tip:: - - If instead of using PHP's built-in webserver, you use Apache with - ``mod_rewrite`` enabled and take advantage of the ``.htaccess`` file - Symfony provides in ``web/``, you can even omit the ``app.php`` part of the - URL. The default ``.htaccess`` points all requests to the ``app.php`` front - controller: - - .. code-block:: text - - http://localhost/demo/hello/Fabien +In this example, the ``config_dev.yml`` configuration file imports the common +``config.yml`` file and then overrides any existing web debug toolbar configuration +with its own options. For more details on environments, see ":ref:`Environments & Front Controllers `" article. @@ -390,8 +354,8 @@ Congratulations! You've had your first taste of Symfony code. That wasn't so hard, was it? There's a lot more to explore, but you should already see how Symfony makes it really easy to implement web sites better and faster. If you are eager to learn more about Symfony, dive into the next section: -":doc:`The View`". +":doc:`The View `". -.. _Composer: https://getcomposer.org/ +.. _Composer: https://getcomposer.org/ .. _executable installer: http://getcomposer.org/download -.. _Twig: http://twig.sensiolabs.org/ +.. _Twig: http://twig.sensiolabs.org/ diff --git a/quick_tour/the_controller.rst b/quick_tour/the_controller.rst index decb50b98a7..114b2e587b3 100644 --- a/quick_tour/the_controller.rst +++ b/quick_tour/the_controller.rst @@ -1,8 +1,100 @@ The Controller ============== -Still here after the first two parts? You are already becoming a Symfony -addict! Without further ado, discover what controllers can do for you. +Still here after the first two parts? You are already becoming a Symfony fan! +Without further ado, discover what controllers can do for you. + +Returning Raw Responses +----------------------- + +Symfony defines itself as a Request-Response framework. When the user makes a +request to your application, Symfony creates a ``Request`` object to encapsulate +all the information related to that request. Similarly, the result of executing +any action of any controller is the creation of a ``Response`` object which +Symfony uses to generate the HTML content returned to the user. + +So far, all the actions shown in this tutorial used the ``$this->render()`` +shortcut to return a rendered response as result. In case you need it, you can +also create a raw ``Response`` object to return any text content:: + + // src/AppBundle/Controller/DefaultController.php + namespace AppBundle\Controller; + + use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; + use Symfony\Bundle\FrameworkBundle\Controller\Controller; + use Symfony\Component\HttpFoundation\Response; + + class DefaultController extends Controller + { + /** + * @Route("/", name="homepage") + */ + public function indexAction() + { + return new Response('Welcome to Symfony!'); + } + } + +Route Parameters +---------------- + +Most of the time, the URLs of applications include variable parts on them. If you +are creating for example a blog application, the URL to display the articles should +include their title or some other unique identifier to let the application know +the exact article to display. + +In Symfony applications, the variable parts of the routes are enclosed in curly +braces (e.g. ``/blog/read/{article_title}/``). Each variable part is assigned a +unique name that can be used later in the controller to retrieve each value. + +Let's create a new action with route variables to show this feature in action. +Open the ``src/AppBundle/Controller/DefaultController.php`` file and add a new +method called ``helloAction`` with the following content:: + + // src/AppBundle/Controller/DefaultController.php + namespace AppBundle\Controller; + + use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; + use Symfony\Bundle\FrameworkBundle\Controller\Controller; + + class DefaultController extends Controller + { + // ... + + /** + * @Route("/hello/{name}", name="hello") + */ + public function helloAction($name) + { + return $this->render('default/hello.html.twig', array( + 'name' => $name + )); + } + } + +Open your browser and access the ``http://localhost:8000/hello/fabien`` URL to +see the result of executing this new action. Instead of the action result, you'll +see an error page. As you probably guessed, the cause of this error is that we're +trying to render a template (``default/hello.html.twig``) that doesn't exist yet. + +Create the new ``app/Resources/views/default/hello.html.twig`` template with the +following content: + +.. code-block:: html+jinja + + {# app/Resources/views/default/hello.html.twig #} + {% extends 'base.html.twig' %} + + {% block body %} +

      Hi {{ name }}! Welcome to Symfony!

      + {% endblock %} + +Browse again the ``http://localhost:8000/hello/fabien`` URL and you'll see this +new template rendered with the information passed by the controller. If you +change the last part of the URL (e.g. ``http://localhost:8000/hello/thomas``) +and reload your browser, the page will display a different message. And if you +remove the last part of the URL (e.g. ``http://localhost:8000/hello``), Symfony +will display an error because the route expects a name and you haven't provided it. Using Formats ------------- @@ -10,86 +102,113 @@ Using Formats Nowadays, a web application should be able to deliver more than just HTML pages. From XML for RSS feeds or Web Services, to JSON for Ajax requests, there are plenty of different formats to choose from. Supporting those formats -in Symfony is straightforward. Tweak the route by adding a default value of -``xml`` for the ``_format`` variable:: +in Symfony is straightforward thanks to a special variable called ``_format`` +which stores the format requested by the user. - // src/Acme/DemoBundle/Controller/DemoController.php +Tweak the ``hello`` route by adding a new ``_format`` variable with ``html`` as +its default value:: + + // src/AppBundle/Controller/DefaultController.php use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; // ... /** - * @Route("/hello/{name}", defaults={"_format"="xml"}, name="_demo_hello") - * @Template() + * @Route("/hello/{name}.{_format}", defaults={"_format"="html"}, name="hello") */ - public function helloAction($name) + public function helloAction($name, $_format) { - return array('name' => $name); + return $this->render('default/hello.'.$_format.'.twig', array( + 'name' => $name + )); } -By using the request format (as defined by the special ``_format`` variable), -Symfony automatically selects the right template, here ``hello.xml.twig``: +Obviously, when you support several request formats, you have to provide a +template for each of the supported formats. In this case, you should create a +new ``hello.xml.twig`` template: .. code-block:: xml+php - + {{ name }} +Now, when you browse to ``http://localhost:8000/hello/fabien``, you'll see the +regular HTML page because ``html`` is the default format. When visiting +``http://localhost:8000/hello/fabien.html`` you'll get again the HTML page, this +time because you explicitly asked for the ``html`` format. Lastly, if you visit +``http://localhost:8000/hello/fabien.xml`` you'll see the new XML template rendered +in your browser. + That's all there is to it. For standard formats, Symfony will also -automatically choose the best ``Content-Type`` header for the response. If -you want to support different formats for a single action, use the ``{_format}`` -placeholder in the route path instead:: +automatically choose the best ``Content-Type`` header for the response. To +restrict the formats supported by a given action, use the ``requirements`` +option of the ``@Route()`` annotation:: - // src/Acme/DemoBundle/Controller/DemoController.php + // src/AppBundle/Controller/DefaultController.php use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; // ... /** - * @Route( - * "/hello/{name}.{_format}", - * defaults = { "_format" = "html" }, + * @Route("/hello/{name}.{_format}", + * defaults = {"_format"="html"}, * requirements = { "_format" = "html|xml|json" }, - * name = "_demo_hello" + * name = "hello" * ) - * @Template() */ - public function helloAction($name) + public function helloAction($name, $_format) { - return array('name' => $name); + return $this->render('default/hello.'.$_format.'.twig', array( + 'name' => $name + )); } -The controller will now match URLs like ``/demo/hello/Fabien.xml`` or -``/demo/hello/Fabien.json``. - -The ``requirements`` entry defines regular expressions that variables must -match. In this example, if you try to request the ``/demo/hello/Fabien.js`` -resource, you will get a 404 HTTP error, as it does not match the ``_format`` -requirement. +The ``hello`` action will now match URLs like ``/hello/fabien.xml`` or +``/hello/fabien.json``, but it will show a 404 error if you try to get URLs +like ``/hello/fabien.js``, because the value of the ``_format`` variable doesn't +meet its requirements. Redirecting and Forwarding -------------------------- -If you want to redirect the user to another page, use the ``redirect()`` +If you want to redirect the user to another page, use the ``redirectToRoute()`` method:: - return $this->redirect($this->generateUrl('_demo_hello', array('name' => 'Lucas'))); + // src/AppBundle/Controller/DefaultController.php + class DefaultController extends Controller + { + /** + * @Route("/", name="homepage") + */ + public function indexAction() + { + return $this->redirectToRoute('hello', array('name' => 'Fabien')); + } + } -The ``generateUrl()`` is the same method as the ``path()`` function used in the -templates. It takes the route name and an array of parameters as arguments and -returns the associated friendly URL. +The ``redirectToRoute()`` method takes as arguments the route name and an optional +array of parameters and redirects the user to the URL generated with those arguments. -You can also internally forward the action to another using the ``forward()`` -method:: +You can also internally forward the action to another action of the same or +different controller using the ``forward()`` method:: - return $this->forward('AcmeDemoBundle:Hello:fancy', array( - 'name' => $name, - 'color' => 'green' - )); + // src/AppBundle/Controller/DefaultController.php + class DefaultController extends Controller + { + /** + * @Route("/", name="homepage") + */ + public function indexAction() + { + return $this->forward('AppBundle:Blog:index', array( + 'name' => $name + ); + } + } Displaying Error Pages ---------------------- @@ -98,40 +217,85 @@ Errors will inevitably happen during the execution of every web application. In the case of ``404`` errors, Symfony includes a handy shortcut that you can use in your controllers:: - throw $this->createNotFoundException(); + // src/AppBundle/Controller/DefaultController.php + // ... + + class DefaultController extends Controller + { + /** + * @Route("/", name="homepage") + */ + public function indexAction() + { + // ... + throw $this->createNotFoundException(); + } + } For ``500`` errors, just throw a regular PHP exception inside the controller and Symfony will transform it into a proper ``500`` error page:: - throw new \Exception('Something went wrong!'); + // src/AppBundle/Controller/DefaultController.php + // ... + + class DefaultController extends Controller + { + /** + * @Route("/", name="homepage") + */ + public function indexAction() + { + // ... + throw new \Exception('Something went horribly wrong!'); + } + } Getting Information from the Request ------------------------------------ -Symfony automatically injects the ``Request`` object when the controller has an -argument that's type hinted with ``Symfony\Component\HttpFoundation\Request``:: +Sometimes your controllers need to access the information related to the user +request, such as their preferred language, IP address or the URL query parameters. +To get access to this information, add a new argument of type ``Request`` to the +action. The name of this new argument doesn't matter, but it must be preceded +by the ``Request`` type in order to work (don't forget to add the new ``use`` +statement that imports this ``Request`` class):: + + // src/AppBundle/Controller/DefaultController.php + namespace AppBundle\Controller; + use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; + use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Request; - public function indexAction(Request $request) + class DefaultController extends Controller { - $request->isXmlHttpRequest(); // is it an Ajax request? - - $request->getPreferredLanguage(array('en', 'fr')); - - $request->query->get('page'); // get a $_GET parameter - - $request->request->get('page'); // get a $_POST parameter + /** + * @Route("/", name="homepage") + */ + public function indexAction(Request $request) + { + // is it an Ajax request? + $isAjax = $request->isXmlHttpRequest(); + + // what's the preferred language of the user? + $language = $request->getPreferredLanguage(array('en', 'fr')); + + // get the value of a $_GET parameter + $pageName = $request->query->get('page'); + + // get the value of a $_POST parameter + $pageName = $request->request->get('page'); + } } -In a template, you can also access the ``Request`` object via the -``app.request`` variable: +In a template, you can also access the ``Request`` object via the special +``app.request`` variable automatically provided by Symfony: .. code-block:: html+jinja {{ app.request.query.get('page') }} - {{ app.request.parameter('page') }} + {{ app.request.request.get('page') }} Persisting Data in the Session ------------------------------ @@ -164,40 +328,21 @@ You can also store "flash messages" that will auto-delete after the next request They are useful when you need to set a success message before redirecting the user to another page (which will then show the message):: - // store a message for the very next request (in a controller) - $session->getFlashBag()->add('notice', 'Congratulations, your action succeeded!'); - -.. code-block:: html+jinja - - {# display the flash message in the template #} -
      {{ app.session.flashbag.get('notice') }}
      + public function indexAction(Request $request) + { + // ... -Caching Resources ------------------ + // store a message for the very next request + $this->addFlash('notice', 'Congratulations, your action succeeded!'); + } -As soon as your website starts to generate more traffic, you will want to -avoid generating the same resource again and again. Symfony uses HTTP cache -headers to manage resources cache. For simple caching strategies, use the -convenient ``@Cache()`` annotation:: +And you can display the flash message in the template like this: - use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; - use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; - use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache; - - /** - * @Route("/hello/{name}", name="_demo_hello") - * @Template() - * @Cache(maxage="86400") - */ - public function helloAction($name) - { - return array('name' => $name); - } +.. code-block:: html+jinja -In this example, the resource will be cached for a day (``86400`` seconds). -Resource caching is managed by Symfony itself. But because caching is managed -using standard HTTP cache headers, you can use Varnish or Squid without having -to modify a single line of code in your application. +
      + {{ app.session.flashbag.get('notice') }} +
      Final Thoughts -------------- @@ -206,4 +351,4 @@ That's all there is to it, and I'm not even sure you'll have spent the full 10 minutes. You were briefly introduced to bundles in the first part, and all the features you've learned about so far are part of the core framework bundle. But thanks to bundles, everything in Symfony can be extended or replaced. -That's the topic of the :doc:`next part of this tutorial`. +That's the topic of the :doc:`next part of this tutorial `. diff --git a/quick_tour/the_view.rst b/quick_tour/the_view.rst index c641eb6db10..975418f480d 100644 --- a/quick_tour/the_view.rst +++ b/quick_tour/the_view.rst @@ -3,27 +3,31 @@ The View After reading the first part of this tutorial, you have decided that Symfony was worth another 10 minutes. In this second part, you will learn more about -`Twig`_, the fast, flexible, and secure template engine for PHP. Twig makes your -templates more readable and concise; it also makes them more friendly for web -designers. +`Twig`_, the fast, flexible, and secure template engine for PHP applications. +Twig makes your templates more readable and concise; it also makes them more +friendly for web designers. Getting familiar with Twig -------------------------- The official `Twig documentation`_ is the best resource to learn everything -about this new template engine. This section just gives you a quick overview of +about this template engine. This section just gives you a quick overview of its main concepts. A Twig template is a text file that can generate any type of content (HTML, CSS, -JavaScript, XML, CSV, LaTeX, ...). Twig elements are separated from the rest of +JavaScript, XML, CSV, LaTeX, etc.) Twig elements are separated from the rest of the template contents using any of these delimiters: -* ``{{ ... }}``: prints the content of a variable or the result of an expression; +``{{ ... }}`` + Prints the content of a variable or the result of evaluating an expression; -* ``{% ... %}``: controls the logic of the template; it is used for example to - execute ``for`` loops and ``if`` statements; +``{% ... %}`` + Controls the logic of the template; it is used for example to execute ``for`` + loops and ``if`` statements. -* ``{# ... #}``: allows including comments inside templates. +``{# ... #}`` + Allows including comments inside templates. Contrary to HTML comments, they + aren't included in the rendered template. Below is a minimal template that illustrates a few basics, using two variables ``page_title`` and ``navigation``, which would be passed into the template: @@ -46,34 +50,34 @@ Below is a minimal template that illustrates a few basics, using two variables -To render a template in Symfony, use the ``render`` method from within a controller -and pass the variables needed as an array using the optional second argument:: +To render a template in Symfony, use the ``render`` method from within a controller. +If the template needs variables to generate its contents, pass them as an array +using the second optional argument:: - $this->render('AcmeDemoBundle:Demo:hello.html.twig', array( - 'name' => $name, + $this->render('default/index.html.twig', array( + 'variable_name' => 'variable_value', )); -Variables passed to a template can be strings, arrays, or even objects. Twig +Variables passed to a template can be strings, arrays or even objects. Twig abstracts the difference between them and lets you access "attributes" of a variable with the dot (``.``) notation. The following code listing shows how to -display the content of a variable depending on the type of the variable passed -by the controller: +display the content of a variable passed by the controller depending on its type: .. code-block:: jinja {# 1. Simple variables #} - {# array('name' => 'Fabien') #} + {# $this->render('template.html.twig', array('name' => 'Fabien') ) #} {{ name }} {# 2. Arrays #} - {# array('user' => array('name' => 'Fabien')) #} + {# $this->render('template.html.twig', array('user' => array('name' => 'Fabien')) ) #} {{ user.name }} {# alternative syntax for arrays #} {{ user['name'] }} {# 3. Objects #} - {# array('user' => new User('Fabien')) #} + {# $this->render('template.html.twig', array('user' => new User('Fabien')) ) #} {{ user.name }} {{ user.getName }} @@ -86,60 +90,63 @@ Decorating Templates More often than not, templates in a project share common elements, like the well-known header and footer. Twig solves this problem elegantly with a concept -called "template inheritance". This feature allows you to build a base "layout" -template that contains all the common elements of your site and defines "blocks" +called "template inheritance". This feature allows you to build a base template +that contains all the common elements of your site and defines "blocks" of contents that child templates can override. -The ``hello.html.twig`` template uses the ``extends`` tag to indicate that it -inherits from the common ``layout.html.twig`` template: +The ``index.html.twig`` template uses the ``extends`` tag to indicate that it +inherits from the ``base.html.twig`` template: .. code-block:: html+jinja - {# src/Acme/DemoBundle/Resources/views/Demo/hello.html.twig #} - {% extends "AcmeDemoBundle::layout.html.twig" %} + {# app/Resources/views/default/index.html.twig #} + {% extends 'base.html.twig' %} - {% block title "Hello " ~ name %} - - {% block content %} -

      Hello {{ name }}!

      + {% block body %} +

      Welcome to Symfony!

      {% endblock %} -The ``AcmeDemoBundle::layout.html.twig`` notation sounds familiar, doesn't it? -It is the same notation used to reference a regular template. The ``::`` part -simply means that the controller element is empty, so the corresponding file -is directly stored under the ``Resources/views/`` directory of the bundle. - -Now, simplify the ``layout.html.twig`` template: +Open the ``app/Resources/views/base.html.twig`` file that corresponds to the +``base.html.twig`` template and you'll find the following Twig code: -.. code-block:: jinja +.. code-block:: html+jinja - {# src/Acme/DemoBundle/Resources/views/layout.html.twig #} -
      - {% block content %} - {% endblock %} -
      + {# app/Resources/views/base.html.twig #} + + + + + {% block title %}Welcome!{% endblock %} + {% block stylesheets %}{% endblock %} + + + + {% block body %}{% endblock %} + {% block javascripts %}{% endblock %} + + The ``{% block %}`` tags tell the template engine that a child template may -override those portions of the template. In this example, the ``hello.html.twig`` -template overrides the ``content`` block, meaning that the "Hello Fabien" text -is rendered inside the ``
      `` element. +override those portions of the template. In this example, the ``index.html.twig`` +template overrides the ``body`` block, but not the ``title`` block, which will +display the default content defined in the ``base.html.twig`` template. Using Tags, Filters, and Functions ---------------------------------- -One of the best feature of Twig is its extensibility via tags, filters, and +One of the best features of Twig is its extensibility via tags, filters, and functions. Take a look at the following sample template that uses filters extensively to modify the information before displaying it to the user: .. code-block:: jinja -

      {{ article.title|trim|capitalize }}

      +

      {{ article.title|capitalize }}

      -

      {{ article.content|striptags|slice(0, 1024) }}

      +

      {{ article.content|striptags|slice(0, 255) }} ...

      Tags: {{ article.tags|sort|join(", ") }}

      -

      Next article will be published on {{ 'next Monday'|date('M j, Y')}}

      +

      Activate your account before {{ 'next Monday'|date('M j, Y') }}

      Don't forget to check out the official `Twig documentation`_ to learn everything about filters, functions and tags. @@ -150,23 +157,28 @@ Including other Templates The best way to share a snippet of code between several templates is to create a new template fragment that can then be included from other templates. -First, create an ``embedded.html.twig`` template: +Imagine that we want to display ads on some pages of our application. First, +create a ``banner.html.twig`` template: .. code-block:: jinja - {# src/Acme/DemoBundle/Resources/views/Demo/embedded.html.twig #} - Hello {{ name }} + {# app/Resources/views/ads/banner.html.twig #} +
      + ... +
      -And change the ``index.html.twig`` template to include it: +To display this ad on any page, include the ``banner.html.twig`` template using +the ``include()`` function: -.. code-block:: jinja +.. code-block:: html+jinja + + {# app/Resources/views/default/index.html.twig #} + {% extends 'base.html.twig' %} - {# src/Acme/DemoBundle/Resources/views/Demo/hello.html.twig #} - {% extends "AcmeDemoBundle::layout.html.twig" %} + {% block body %} +

      Welcome to Symfony!

      - {# override the body block from embedded.html.twig #} - {% block content %} - {{ include("AcmeDemoBundle:Demo:embedded.html.twig") }} + {{ include('ads/banner.html.twig') }} {% endblock %} Embedding other Controllers @@ -178,28 +190,28 @@ some variable not available in the main template. Suppose you've created a ``topArticlesAction`` controller method to display the most popular articles of your website. If you want to "render" the result of -that method (e.g. ``HTML``) inside the ``index`` template, use the ``render`` -function: +that method (usually some HTML content) inside the ``index`` template, use the +``render()`` function: .. code-block:: jinja - {# src/Acme/DemoBundle/Resources/views/Demo/index.html.twig #} - {{ render(controller("AcmeDemoBundle:Demo:topArticles", {'num': 10})) }} + {# app/Resources/views/index.html.twig #} + {{ render(controller('AppBundle:Default:topArticles')) }} -Here, the ``AcmeDemoBundle:Demo:topArticles`` string refers to the -``topArticlesAction`` action of the ``Demo`` controller, and the ``num`` -argument is made available to the controller:: +Here, the ``render()`` and ``controller()`` functions use the special +``AppBundle:Default:topArticles`` syntax to refer to the ``topArticlesAction`` +action of the ``Default`` controller (the ``AppBundle`` part will be explained later):: - // src/Acme/DemoBundle/Controller/DemoController.php + // src/AppBundle/Controller/DefaultController.php - class DemoController extends Controller + class DefaultController extends Controller { - public function topArticlesAction($num) + public function topArticlesAction() { - // look for the $num most popular articles in the database + // look for the most popular articles in the database $articles = ...; - return $this->render('AcmeDemoBundle:Demo:topArticles.html.twig', array( + return $this->render('default/top_articles.html.twig', array( 'articles' => $articles, )); } @@ -217,32 +229,16 @@ updated by just changing the configuration: .. code-block:: html+jinja - Greet Thomas! - -The ``path`` function takes the route name and an array of parameters as -arguments. The route name is the key under which routes are defined and the -parameters are the values of the variables defined in the route pattern:: + Return to homepage - // src/Acme/DemoBundle/Controller/DemoController.php - use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; - use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; - - // ... - - /** - * @Route("/hello/{name}", name="_demo_hello") - * @Template() - */ - public function helloAction($name) - { - return array('name' => $name); - } +The ``path`` function takes the route name as the first argument and you can +optionally pass an array of route parameters as the second argument. .. tip:: The ``url`` function is very similar to the ``path`` function, but generates *absolute* URLs, which is very handy when rendering emails and RSS files: - ``{{ url('_demo_hello', {'name': 'Thomas'}) }}``. + ``Visit our website``. Including Assets: Images, JavaScripts and Stylesheets ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -256,18 +252,20 @@ Symfony provides the ``asset`` function to deal with them easily: -The ``asset`` function's main purpose is to make your application more portable. -Thanks to this function, you can move the application root directory anywhere -under your web root directory without changing anything in your template's -code. +The ``asset()`` function looks for the web assets inside the ``web/`` directory. +If you store them in another directory, read :doc:`this article ` +to learn how to manage web assets. + +Using the ``asset`` function, your application is more portable. The reason is +that you can move the application root directory anywhere under your web root +directory without changing anything in your template's code. Final Thoughts -------------- Twig is simple yet powerful. Thanks to layouts, blocks, templates and action inclusions, it is very easy to organize your templates in a logical and -extensible way. However, if you're not comfortable with Twig, you can always -use PHP templates inside Symfony without any issues. +extensible way. You have only been working with Symfony for about 20 minutes, but you can already do pretty amazing stuff with it. That's the power of Symfony. Learning @@ -278,5 +276,5 @@ But I'm getting ahead of myself. First, you need to learn more about the control and that's exactly the topic of the :doc:`next part of this tutorial `. Ready for another 10 minutes with Symfony? -.. _Twig: http://twig.sensiolabs.org/ +.. _Twig: http://twig.sensiolabs.org/ .. _Twig documentation: http://twig.sensiolabs.org/documentation diff --git a/redirection_map b/redirection_map index e632d829662..c0147f34c51 100644 --- a/redirection_map +++ b/redirection_map @@ -1,3 +1,4 @@ +/book/stable_api /contributing/code/bc /cookbook/deployment-tools /cookbook/deployment/tools /cookbook/doctrine/migrations /bundles/DoctrineFixturesBundle/index /cookbook/doctrine/doctrine_fixtures /bundles/DoctrineFixturesBundle/index @@ -22,3 +23,26 @@ /cookbook/console/generating_urls /cookbook/console/sending_emails /components/yaml /components/yaml/introduction /components/templating /components/templating/introduction +/components/filesystem /components/filesystem/introduction +/cmf/reference/configuration/block /cmf/bundles/block/configuration +/cmf/reference/configuration/content /cmf/bundles/content/configuration +/cmf/reference/configuration/core /cmf/bundles/core/configuration +/cmf/reference/configuration/create /cmf/bundles/create/configuration +/cmf/reference/configuration/media /cmf/bundles/media/configuration +/cmf/reference/configuration/menu /cmf/bundles/menu/configuration +/cmf/reference/configuration/phpcr_odm /cmf/bundles/phpcr_odm/configuration +/cmf/reference/configuration/routing /cmf/bundles/routing/configuration +/cmf/reference/configuration/search /cmf/bundles/search/configuration +/cmf/reference/configuration/seo /cmf/bundles/seo/configuration +/cmf/reference/configuration/simple_cms /cmf/bundles/simple_cms/configuration +/cmf/reference/configuration/tree_browser /cmf/bundles/tree_browser/configuration +/cmf/cookbook/exposing_content_via_rest /cmf/bundles/content/exposing_content_via_rest +/cmf/cookbook/creating_a_cms/auto-routing /cmf/tutorial/auto-routing +/cmf/cookbook/creating_a_cms/conclusion /cmf/tutorial/conclusion +/cmf/cookbook/creating_a_cms/content-to-controllers /cmf/tutorial/content-to-controllers +/cmf/cookbook/creating_a_cms/getting-started /cmf/tutorial/getting-started +/cmf/cookbook/creating_a_cms/index /cmf/tutorial/index +/cmf/cookbook/creating_a_cms/introduction /cmf/tutorial/introduction +/cmf/cookbook/creating_a_cms/make-homepage /cmf/tutorial/make-homepage +/cmf/cookbook/creating_a_cms/sonata-admin /cmf/tutorial/sonata-admin +/cmf/cookbook/creating_a_cms/the-frontend /cmf/tutorial/the-frontend diff --git a/reference/configuration/assetic.rst b/reference/configuration/assetic.rst index f85346394a8..ae9415ea4fe 100644 --- a/reference/configuration/assetic.rst +++ b/reference/configuration/assetic.rst @@ -4,8 +4,8 @@ AsseticBundle Configuration ("assetic") ======================================= -Full default Configuration -~~~~~~~~~~~~~~~~~~~~~~~~~~ +Full Default Configuration +-------------------------- .. configuration-block:: @@ -48,6 +48,12 @@ Full default Configuration # An array of named filters (e.g. some_filter, some_other_filter) some_filter: [] + workers: + # see https://github.com/symfony/AsseticBundle/pull/119 + # Cache can also be busted via the framework.templating.assets_version + # setting - see the "framework" configuration section + cache_busting: + enabled: false twig: functions: # An array of named functions (e.g. some_function, some_other_function) diff --git a/reference/configuration/doctrine.rst b/reference/configuration/doctrine.rst index 6f8fbf73562..d11044e632a 100644 --- a/reference/configuration/doctrine.rst +++ b/reference/configuration/doctrine.rst @@ -5,7 +5,7 @@ DoctrineBundle Configuration ("doctrine") ========================================= -Full default configuration +Full Default Configuration -------------------------- .. configuration-block:: @@ -26,9 +26,6 @@ Full default configuration #schema_filter: ^sf2_ connections: - default: - dbname: database - # A collection of different named connections (e.g. default, conn2, etc) default: dbname: ~ @@ -62,6 +59,8 @@ Full default configuration MultipleActiveResultSets: ~ driver: pdo_mysql platform_service: ~ + + # when true, queries are logged to a "doctrine" monolog channel logging: "%kernel.debug%" profiling: "%kernel.debug%" driver_class: ~ @@ -180,8 +179,10 @@ Full default configuration + xsi:schemaLocation="http://symfony.com/schema/dic/services + http://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/doctrine + http://symfony.com/schema/dic/doctrine/doctrine-1.0.xsd"> @@ -209,16 +210,44 @@ Full default configuration Acme\HelloBundle\MyCustomType - - - + + + + + - Acme\HelloBundle\DQL\StringFunction - Acme\HelloBundle\DQL\NumericFunction - Acme\HelloBundle\DQL\DatetimeFunction + + Acme\HelloBundle\DQL\StringFunction + + + + Acme\HelloBundle\DQL\NumericFunction + + + + Acme\HelloBundle\DQL\DatetimeFunction + + + xsi:schemaLocation="http://symfony.com/schema/dic/services + http://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/doctrine + http://symfony.com/schema/dic/doctrine/doctrine-1.0.xsd" + > ` +* `translator`_ + * :ref:`enabled ` + * `fallback`_ + * `logging`_ +* `property_accessor`_ + * `magic_call`_ + * `throw_exception_on_invalid_index`_ +* `validation`_ + * `cache`_ + * `enable_annotations`_ + * `translation_domain`_ + * `strict_email`_ + * `api`_ secret ~~~~~~ @@ -72,8 +86,8 @@ http_method_override This determines whether the ``_method`` request parameter is used as the intended HTTP method on POST requests. If enabled, the :method:`Request::enableHttpMethodParameterOverride ` -gets called automatically. It becomes the service container parameter named -``kernel.http_method_override``. For more information, see +method gets called automatically. It becomes the service container parameter +named ``kernel.http_method_override``. For more information, see :doc:`/cookbook/routing/method_parameters`. ide @@ -147,6 +161,17 @@ This setting should be present in your ``test`` environment (usually via .. _reference-framework-trusted-proxies: +default_locale +~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``en`` + +The default locale is used if no ``_locale`` routing parameter has been set. It +becomes the service container parameter named ``kernel.default_locale`` and it +is also available with the +:method:`Request::getDefaultLocale ` +method. + trusted_proxies ~~~~~~~~~~~~~~~ @@ -423,6 +448,10 @@ Now, the same asset will be rendered as ``/images/logo.png?v2`` If you use this feature, you **must** manually increment the ``assets_version`` value before each deployment so that the query parameters change. +It's also possible to set the version value on an asset-by-asset basis (instead +of using the global version - e.g. ``v2`` - set here). See +:ref:`Versioning by Asset ` for details. + You can also control how the query string works via the `assets_version_format`_ option. @@ -464,19 +493,24 @@ would be ``/images/logo.png?version=5``. profiler ~~~~~~~~ -.. versionadded:: 2.2 - The ``enabled`` option was introduced in Symfony 2.2. Previously, the profiler - could only be disabled by omitting the ``framework.profiler`` configuration - entirely. - .. _profiler.enabled: enabled ....... -**default**: ``true`` in the ``dev`` and ``test`` environments +.. versionadded:: 2.2 + The ``enabled`` option was introduced in Symfony 2.2. Prior to Symfony + 2.2, the profiler could only be disabled by omitting the ``framework.profiler`` + configuration entirely. + +**type**: ``boolean`` **default**: ``false`` + +The profiler can be enabled by setting this key to ``true``. When you are +using the Symfony Standard Edition, the profiler is enabled in the ``dev`` +and ``test`` environments. -The profiler can be disabled by setting this key to ``false``. +collect +....... .. versionadded:: 2.3 The ``collect`` option was introduced in Symfony 2.3. Previously, when @@ -484,10 +518,7 @@ The profiler can be disabled by setting this key to ``false``. but the collectors were disabled. Now, the profiler and the collectors can be controlled independently. -collect -....... - -**default**: ``true`` +**type**: ``boolean`` **default**: ``true`` This option configures the way the profiler behaves when it is enabled. If set to ``true``, the profiler collects data for all requests. If you want to only @@ -496,6 +527,127 @@ and activate the data collectors by hand:: $profiler->enable(); +translator +~~~~~~~~~~ + +.. _translator.enabled: + +enabled +....... + +**type**: ``boolean`` **default**: ``false`` + +Whether or not to enable the ``translator`` service in the service container. + +fallback +........ + +**type**: ``string`` **default**: ``en`` + +This option is used when the translation key for the current locale wasn't found. + +For more details, see :doc:`/book/translation`. + +.. _reference-framework-translator-logging: + +logging +....... + +.. versionadded:: 2.6 + The ``logging`` option was introduced in Symfony 2.6. + +**default**: ``true`` when the debug mode is enabled, ``false`` otherwise. + +When ``true``, a log entry is made whenever the translator cannot find a translation +for a given key. The logs are made to the ``translation`` channel and at the +``debug`` for level for keys where there is a translation in the fallback +locale and the ``warning`` level if there is no translation to use at all. + +property_accessor +~~~~~~~~~~~~~~~~~ + +magic_call +.......... + +**type**: ``boolean`` **default**: ``false`` + +When enabled, the ``property_accessor`` service uses PHP's +:ref:`magic __call() method ` when +its ``getValue()`` method is called. + +throw_exception_on_invalid_index +................................ + +**type**: ``boolean`` **default**: ``false`` + +When enabled, the ``property_accessor`` service throws an exception when you +try to access an invalid index of an array. + +validation +~~~~~~~~~~ + +cache +..... + +**type**: ``string`` + +The service that is used to persist class metadata in a cache. The service +has to implement the :class:`Symfony\\Component\\Validator\\Mapping\\Cache\\CacheInterface`. + +enable_annotations +.................. + +**type**: ``Boolean`` **default**: ``false`` + +If this option is enabled, validation constraints can be defined using annotations. + +translation_domain +.................. + +**type**: ``string`` **default**: ``validators`` + +The translation domain that is used when translating validation constraint +error messages. + +strict_email +............ + +.. versionadded:: 2.5 + The ``strict_email`` option was introduced in Symfony 2.5. + +**type**: ``Boolean`` **default**: ``false`` + +If this option is enabled, the `egulias/email-validator`_ library will be +used by the :doc:`/reference/constraints/Email` constraint validator. Otherwise, +the validator uses a simple regular expression to validate email addresses. + +api +... + +.. versionadded:: 2.5 + The ``api`` option was introduced in Symfony 2.5. + +**type**: ``string`` + +Starting with Symfony 2.5, the Validator component introduced a new validation +API. The ``api`` option is used to switch between the different implementations: + +``2.4`` + Use the vaidation API that is compatible with older Symfony versions. + +``2.5`` + Use the validation API introduced in Symfony 2.5. + +``2.5-bc`` or ``auto`` + If you omit a value or set the ``api`` option to ``2.5-bc`` or ``auto``, + Symfony will use an API implementation that is compatible with both the + legacy implementation and the ``2.5`` implementation. You have to use + PHP 5.3.9 or higher to be able to use this implementation. + +To capture these logs in the ``prod`` environment, configure a +:doc:`channel handler ` in ``config_prod.yml`` for +the ``translation`` channel and set its ``level`` to ``debug``. + Full default Configuration -------------------------- @@ -613,6 +765,7 @@ Full default Configuration translator: enabled: false fallback: en + logging: "%kernel.debug%" # validation configuration validation: @@ -629,3 +782,4 @@ Full default Configuration .. _`protocol-relative`: http://tools.ietf.org/html/rfc3986#section-4.2 .. _`PhpStormOpener`: https://github.com/pinepain/PhpStormOpener +.. _`egulias/email-validator`: https://github.com/egulias/EmailValidator diff --git a/reference/configuration/kernel.rst b/reference/configuration/kernel.rst index 2a8544aff22..6f284f25690 100644 --- a/reference/configuration/kernel.rst +++ b/reference/configuration/kernel.rst @@ -22,9 +22,9 @@ Charset **type**: ``string`` **default**: ``UTF-8`` -This returns the charset that is used in the application. To change it, override the -:method:`Symfony\\Component\\HttpKernel\\Kernel::getCharset` method and return another -charset, for instance:: +This returns the charset that is used in the application. To change it, +override the :method:`Symfony\\Component\\HttpKernel\\Kernel::getCharset` +method and return another charset, for instance:: // app/AppKernel.php @@ -40,12 +40,13 @@ charset, for instance:: Kernel Name ~~~~~~~~~~~ -**type**: ``string`` **default**: ``app`` (i.e. the directory name holding the kernel class) +**type**: ``string`` **default**: ``app`` (i.e. the directory name holding +the kernel class) To change this setting, override the :method:`Symfony\\Component\\HttpKernel\\Kernel::getName` -method. Alternatively, move your kernel into a different directory. For example, -if you moved the kernel into a ``foo`` directory (instead of ``app``), the -kernel name will be ``foo``. +method. Alternatively, move your kernel into a different directory. For +example, if you moved the kernel into a ``foo`` directory (instead of ``app``), +the kernel name will be ``foo``. The name of the kernel isn't usually directly important - it's used in the generation of cache files. If you have an application with multiple kernels, diff --git a/reference/configuration/monolog.rst b/reference/configuration/monolog.rst index cc799d7e757..224bc57c0c3 100644 --- a/reference/configuration/monolog.rst +++ b/reference/configuration/monolog.rst @@ -4,7 +4,7 @@ MonologBundle Configuration ("monolog") ======================================= -Full default Configuration +Full Default Configuration -------------------------- .. configuration-block:: @@ -74,8 +74,11 @@ Full default Configuration + xsi:schemaLocation="http://symfony.com/schema/dic/services + http://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/monolog + http://symfony.com/schema/dic/monolog/monolog-1.0.xsd" + > `". + +check_path +.......... + +**type**: ``string`` **default**: ``/login_check`` + +This is the route or path that your login form must submit to. The firewall +will intercept any requests (``POST`` requests only, by default) to this +URL and process the submitted login credentials. + +Be sure that this URL is covered by your main firewall (i.e. don't create +a separate firewall just for ``check_path`` URL). + +use_forward +........... - This path **must** be accessible by a normal, un-authenticated user, else - you may create a redirect loop. For details, see - ":ref:`Avoid Common Pitfalls `". +**type**: ``Boolean`` **default**: ``false`` -* ``check_path`` (type: ``string``, default: ``/login_check``) - This is the route or path that your login form must submit to. The - firewall will intercept any requests (``POST`` requests only, by default) - to this URL and process the submitted login credentials. +If you'd like the user to be forwarded to the login form instead of being +redirected, set this option to ``true``. - Be sure that this URL is covered by your main firewall (i.e. don't create - a separate firewall just for ``check_path`` URL). +username_parameter +.................. -* ``use_forward`` (type: ``Boolean``, default: ``false``) - If you'd like the user to be forwarded to the login form instead of being - redirected, set this option to ``true``. +**type**: ``string`` **default**: ``_username`` -* ``username_parameter`` (type: ``string``, default: ``_username``) - This is the field name that you should give to the username field of - your login form. When you submit the form to ``check_path``, the security - system will look for a POST parameter with this name. +This is the field name that you should give to the username field of your +login form. When you submit the form to ``check_path``, the security system +will look for a POST parameter with this name. -* ``password_parameter`` (type: ``string``, default: ``_password``) - This is the field name that you should give to the password field of - your login form. When you submit the form to ``check_path``, the security - system will look for a POST parameter with this name. +password_parameter +.................. -* ``post_only`` (type: ``Boolean``, default: ``true``) - By default, you must submit your login form to the ``check_path`` URL - as a POST request. By setting this option to ``false``, you can send a - GET request to the ``check_path`` URL. +**type**: ``string`` **default**: ``_password`` + +This is the field name that you should give to the password field of your +login form. When you submit the form to ``check_path``, the security system +will look for a POST parameter with this name. + +post_only +......... + +**type**: ``Boolean`` **default**: ``true`` + +By default, you must submit your login form to the ``check_path`` URL as +a POST request. By setting this option to ``false``, you can send a GET request +to the ``check_path`` URL. Redirecting after Login ~~~~~~~~~~~~~~~~~~~~~~~ @@ -290,9 +331,6 @@ Redirecting after Login Using the PBKDF2 Encoder: Security and Speed -------------------------------------------- -.. versionadded:: 2.2 - The PBKDF2 password encoder was introduced in Symfony 2.2. - The `PBKDF2`_ encoder provides a high level of Cryptographic security, as recommended by the National Institute of Standards and Technology (NIST). @@ -315,9 +353,6 @@ Using the BCrypt Password Encoder To use this encoder, you either need to use PHP Version 5.5 or install the `ircmaxell/password-compat`_ library via Composer. -.. versionadded:: 2.2 - The BCrypt password encoder was introduced in Symfony 2.2. - .. configuration-block:: .. code-block:: yaml @@ -412,20 +447,20 @@ multiple firewalls, the "context" could actually be shared: .. code-block:: xml - - - - - - - - - + + + + + + + + + .. code-block:: php - // app/config/security.php - $container->loadFromExtension('security', array( + // app/config/security.php + $container->loadFromExtension('security', array( 'firewalls' => array( 'somename' => array( // ... @@ -436,7 +471,7 @@ multiple firewalls, the "context" could actually be shared: 'context' => 'my_context' ), ), - )); + )); HTTP-Digest Authentication -------------------------- @@ -445,38 +480,38 @@ To use HTTP-Digest authentication you need to provide a realm and a key: .. configuration-block:: - .. code-block:: yaml - - # app/config/security.yml - security: - firewalls: - somename: - http_digest: - key: "a_random_string" - realm: "secure-api" - - .. code-block:: xml - - - - - - - - - .. code-block:: php - - // app/config/security.php - $container->loadFromExtension('security', array( - 'firewalls' => array( - 'somename' => array( - 'http_digest' => array( - 'key' => 'a_random_string', - 'realm' => 'secure-api', - ), - ), - ), - )); + .. code-block:: yaml + + # app/config/security.yml + security: + firewalls: + somename: + http_digest: + key: "a_random_string" + realm: "secure-api" + + .. code-block:: xml + + + + + + + + + .. code-block:: php + + // app/config/security.php + $container->loadFromExtension('security', array( + 'firewalls' => array( + 'somename' => array( + 'http_digest' => array( + 'key' => 'a_random_string', + 'realm' => 'secure-api', + ), + ), + ), + )); .. _`PBKDF2`: http://en.wikipedia.org/wiki/PBKDF2 .. _`ircmaxell/password-compat`: https://packagist.org/packages/ircmaxell/password-compat diff --git a/reference/configuration/swiftmailer.rst b/reference/configuration/swiftmailer.rst index 9295493eb68..3aaf52b858a 100644 --- a/reference/configuration/swiftmailer.rst +++ b/reference/configuration/swiftmailer.rst @@ -130,7 +130,7 @@ antiflood threshold ......... -**type**: ``string`` **default**: ``99`` +**type**: ``integer`` **default**: ``99`` Used with ``Swift_Plugins_AntiFloodPlugin``. This is the number of emails to send before restarting the transport. @@ -138,7 +138,7 @@ to send before restarting the transport. sleep ..... -**type**: ``string`` **default**: ``0`` +**type**: ``integer`` **default**: ``0`` Used with ``Swift_Plugins_AntiFloodPlugin``. This is the number of seconds to sleep for during a transport restart. diff --git a/reference/configuration/twig.rst b/reference/configuration/twig.rst index 4c72abb03c1..044c5f2e26e 100644 --- a/reference/configuration/twig.rst +++ b/reference/configuration/twig.rst @@ -10,14 +10,13 @@ TwigBundle Configuration ("twig") twig: exception_controller: twig.controller.exception:showAction - form: - resources: + form_themes: - # Default: - - form_div_layout.html.twig + # Default: + - form_div_layout.html.twig - # Example: - - MyBundle::form.html.twig + # Example: + - MyBundle::form.html.twig globals: # Examples: @@ -54,9 +53,8 @@ TwigBundle Configuration ("twig") http://symfony.com/schema/dic/twig http://symfony.com/schema/dic/twig/twig-1.0.xsd"> - - MyBundle::form.html.twig - + form_div_layout.html.twig + MyBundle::form.html.twig 3.14 @@ -65,10 +63,9 @@ TwigBundle Configuration ("twig") .. code-block:: php $container->loadFromExtension('twig', array( - 'form' => array( - 'resources' => array( - 'MyBundle::form.html.twig', - ) + 'form_themes' => array( + 'form_div_layout.html.twig', // Default + 'MyBundle::form.html.twig', ), 'globals' => array( 'foo' => '@bar', @@ -83,6 +80,12 @@ TwigBundle Configuration ("twig") 'strict_variables' => false, )); +.. caution:: + + The ``twig.form`` (```` tag for xml) configuration key + has been deprecated and will be removed in 3.0. Instead, use the ``twig.form_themes`` + option. + Configuration ------------- diff --git a/reference/constraints.rst b/reference/constraints.rst index 7f11b66c930..94077dd3052 100644 --- a/reference/constraints.rst +++ b/reference/constraints.rst @@ -18,6 +18,7 @@ Validation Constraints Reference constraints/Url constraints/Regex constraints/Ip + constraints/Uuid constraints/Range diff --git a/reference/constraints/Callback.rst b/reference/constraints/Callback.rst index 36b55e97910..8b8b204157b 100644 --- a/reference/constraints/Callback.rst +++ b/reference/constraints/Callback.rst @@ -1,11 +1,6 @@ Callback ======== -.. versionadded:: 2.4 - The ``Callback`` constraint was simplified in Symfony 2.4. For usage - examples with older Symfony versions, see the corresponding versions of this - documentation page. - The purpose of the Callback constraint is to create completely custom validation rules and to assign any validation errors to specific fields on your object. If you're using validation with forms, this means that you can @@ -50,7 +45,9 @@ Configuration namespace Acme\BlogBundle\Entity; use Symfony\Component\Validator\Constraints as Assert; - use Symfony\Component\Validator\ExecutionContextInterface; + use Symfony\Component\Validator\Context\ExecutionContextInterface; + // if you're using the older 2.4 validation API, you'll need this instead + // use Symfony\Component\Validator\ExecutionContextInterface; class Author { @@ -100,7 +97,9 @@ can set "violations" directly on this object and determine to which field those errors should be attributed:: // ... - use Symfony\Component\Validator\ExecutionContextInterface; + use Symfony\Component\Validator\Context\ExecutionContextInterface; + // if you're using the older 2.4 validation API, you'll need this instead + // use Symfony\Component\Validator\ExecutionContextInterface; class Author { @@ -114,16 +113,27 @@ those errors should be attributed:: // check if the name is actually a fake name if (in_array($this->getFirstName(), $fakeNames)) { + // If you're using the new 2.5 validation API (you probably are!) + $context->buildViolation('This name sounds totally fake!') + ->atPath('firstName') + ->addViolation(); + + // If you're using the old 2.4 validation API + /* $context->addViolationAt( 'firstName', - 'This name sounds totally fake!', - array(), - null + 'This name sounds totally fake!' ); + */ } } } +.. versionadded:: 2.5 + The ``buildViolation`` method was added in Symfony 2.5. For usage examples + with older Symfony versions, see the corresponding versions of this documentation + page. + Static Callbacks ---------------- @@ -137,11 +147,16 @@ have access to the object instance, they receive the object as the first argumen // check if the name is actually a fake name if (in_array($object->getFirstName(), $fakeNames)) { + // If you're using the new 2.5 validation API (you probably are!) + $context->buildViolation('This name sounds totally fake!') + ->atPath('firstName') + ->addViolation() + ; + + // If you're using the old 2.4 validation API $context->addViolationAt( 'firstName', - 'This name sounds totally fake!', - array(), - null + 'This name sounds totally fake!' ); } } @@ -156,7 +171,9 @@ your validation function is ``Vendor\Package\Validator::validate()``:: namespace Vendor\Package; - use Symfony\Component\Validator\ExecutionContextInterface; + use Symfony\Component\Validator\Context\ExecutionContextInterface; + // if you're using the older 2.4 validation API, you'll need this instead + // use Symfony\Component\Validator\ExecutionContextInterface; class Validator { @@ -274,7 +291,7 @@ callback method: * A closure. -Concrete callbacks receive an :class:`Symfony\\Component\\Validator\\ExecutionContextInterface` +Concrete callbacks receive an :class:`Symfony\\Component\\Validator\\Context\\ExecutionContextInterface` instance as only argument. Static or closure callbacks receive the validated object as the first argument diff --git a/reference/constraints/CardScheme.rst b/reference/constraints/CardScheme.rst index f8d706e665a..b589b4d334a 100644 --- a/reference/constraints/CardScheme.rst +++ b/reference/constraints/CardScheme.rst @@ -1,9 +1,6 @@ CardScheme ========== -.. versionadded:: 2.2 - The ``CardScheme`` constraint was introduced in Symfony 2.2. - This constraint ensures that a credit card number is valid for a given credit card company. It can be used to validate the number before trying to initiate a payment through a payment gateway. diff --git a/reference/constraints/Choice.rst b/reference/constraints/Choice.rst index 28f1628d042..aab73b59c44 100644 --- a/reference/constraints/Choice.rst +++ b/reference/constraints/Choice.rst @@ -89,11 +89,11 @@ If your valid choice list is simple, you can pass them in directly via the use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Constraints as Assert; - + class Author { protected $gender; - + public static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addPropertyConstraint('gender', new Assert\Choice(array( @@ -176,11 +176,11 @@ constraint. use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Constraints as Assert; - + class Author { protected $gender; - + public static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addPropertyConstraint('gender', new Assert\Choice(array( @@ -244,11 +244,11 @@ you can pass the class name and the method as an array. use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Constraints as Assert; - + class Author { protected $gender; - + public static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addPropertyConstraint('gender', new Assert\Choice(array( diff --git a/reference/constraints/Email.rst b/reference/constraints/Email.rst index 85e15ec85d6..9df3332a749 100644 --- a/reference/constraints/Email.rst +++ b/reference/constraints/Email.rst @@ -7,7 +7,8 @@ cast to a string before being validated. +----------------+---------------------------------------------------------------------+ | Applies to | :ref:`property or method ` | +----------------+---------------------------------------------------------------------+ -| Options | - `message`_ | +| Options | - `strict`_ | +| | - `message`_ | | | - `checkMX`_ | | | - `checkHost`_ | +----------------+---------------------------------------------------------------------+ @@ -89,6 +90,18 @@ Basic Usage Options ------- +.. versionadded:: 2.5 + The ``strict`` option was introduced in Symfony 2.5. + +strict +~~~~~~ + +**type**: ``boolean`` **default**: ``false`` + +When false, the email will be validated against a simple regular expression. +If true, then the `egulias/email-validator`_ library is required to perform +an RFC compliant validation. + message ~~~~~~~ @@ -112,3 +125,5 @@ checkHost If true, then the :phpfunction:`checkdnsrr` PHP function will be used to check the validity of the MX *or* the A *or* the AAAA record of the host of the given email. + +.. _egulias/email-validator: https://packagist.org/packages/egulias/email-validator diff --git a/reference/constraints/Expression.rst b/reference/constraints/Expression.rst index 68526372837..74d65646d01 100644 --- a/reference/constraints/Expression.rst +++ b/reference/constraints/Expression.rst @@ -1,9 +1,6 @@ Expression ========== -.. versionadded:: 2.4 - The Expression constraint was introduced in Symfony 2.4. - This constraint allows you to use an :ref:`expression ` for more complex, dynamic validation. See `Basic Usage`_ for an example. See :doc:`/reference/constraints/Callback` for a different constraint that @@ -217,12 +214,11 @@ more about the expression language syntax, see // ... } - .. caution:: - - In Symfony 2.4 and Symfony 2.5, if the property (e.g. ``isTechnicalPost``) - were ``null``, the expression would never be called and the value - would be seen as valid. To ensure that the value is not ``null``, - use the :doc:`NotNull constraint `. + .. versionadded:: 2.6 + In Symfony 2.6, the Expression constraint *is* executed if the value + is ``null``. Before 2.6, if the value was ``null``, the expression + was never executed and the value was considered valid (unless you + also had a constraint like ``NotBlank`` on the property). For more information about the expression and what variables are available to you, see the :ref:`expression ` diff --git a/reference/constraints/File.rst b/reference/constraints/File.rst index c062199744e..eb45c931475 100644 --- a/reference/constraints/File.rst +++ b/reference/constraints/File.rst @@ -20,9 +20,11 @@ form type. | Applies to | :ref:`property or method ` | +----------------+---------------------------------------------------------------------+ | Options | - `maxSize`_ | +| | - `binaryFormat`_ | | | - `mimeTypes`_ | | | - `maxSizeMessage`_ | | | - `mimeTypesMessage`_ | +| | - `disallowEmptyMessage`_ | | | - `notFoundMessage`_ | | | - `notReadableMessage`_ | | | - `uploadIniSizeErrorMessage`_ | @@ -153,19 +155,46 @@ Options maxSize ~~~~~~~ +.. versionadded:: 2.6 + The suffixes ``Ki`` and ``Mi`` were introduced in Symfony 2.6. + **type**: ``mixed`` If set, the size of the underlying file must be below this file size in order to be valid. The size of the file can be given in one of the following formats: -* **bytes**: To specify the ``maxSize`` in bytes, pass a value that is entirely - numeric (e.g. ``4096``); ++--------+-----------+-----------------+------+ +| Suffix | Unit Name | value | e.g. | ++========+===========+=================+======+ +| | byte | 1 byte | 4096 | ++--------+-----------+-----------------+------+ +| k | kilobyte | 1,000 bytes | 200k | ++--------+-----------+-----------------+------+ +| M | megabyte | 1,000,000 bytes | 2M | ++--------+-----------+-----------------+------+ +| Ki | kibibyte | 1,024 bytes | 32Ki | ++--------+-----------+-----------------+------+ +| Mi | mebibyte | 1,048,576 bytes | 8Mi | ++--------+-----------+-----------------+------+ + +For more information about the difference between binary and SI prefixes, +see `Wikipedia: Binary prefix`_. + +binaryFormat +~~~~~~~~~~~~ + +.. versionadded:: 2.6 + The ``binaryFormat`` option was introduced in Symfony 2.6. + +**type**: ``boolean`` **default**: ``null`` -* **kilobytes**: To specify the ``maxSize`` in kilobytes, pass a number and - suffix it with a lowercase "k" (e.g. ``200k``); +When ``true``, the sizes will be displayed in messages with binary-prefixed +units (KiB, MiB). When ``false``, the sizes will be displayed with SI-prefixed +units (kB, MB). When ``null``, then the binaryFormat will be guessed from +the value defined in the ``maxSize`` option. -* **megabytes**: To specify the ``maxSize`` in megabytes, pass a number and - suffix it with a capital "M" (e.g. ``4M``). +For more information about the difference between binary and SI prefixes, +see `Wikipedia: Binary prefix`_. mimeTypes ~~~~~~~~~ @@ -193,6 +222,18 @@ mimeTypesMessage The message displayed if the mime type of the file is not a valid mime type per the `mimeTypes`_ option. +disallowEmptyMessage +~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 2.6 + The ``disallowEmptyMessage`` option was introduced in Symfony 2.6. Prior to 2.6, + if the user uploaded an empty file, no validation error occurred. + +**type**: ``string`` **default**: ``An empty file is not allowed.`` + +This constraint checks if the uploaded file is empty (i.e. 0 bytes). If it is, +this message is displayed. + notFoundMessage ~~~~~~~~~~~~~~~ @@ -237,3 +278,4 @@ to disk. .. _`IANA website`: http://www.iana.org/assignments/media-types/index.html +.. _`Wikipedia: Binary prefix`: http://en.wikipedia.org/wiki/Binary_prefix diff --git a/reference/constraints/Image.rst b/reference/constraints/Image.rst index b2423282139..abb9ac7e385 100644 --- a/reference/constraints/Image.rst +++ b/reference/constraints/Image.rst @@ -8,11 +8,6 @@ automatically setup to work for image files specifically. Additionally it has options so you can validate against the width and height of the image. -.. versionadded:: 2.4 - As of Symfony 2.4, you can also validate against the image aspect ratio - (defined as ``width / height``) and selectively allow square, landscape - and portrait image orientations. - See the :doc:`File ` constraint for the bulk of the documentation on this constraint. diff --git a/reference/constraints/Isbn.rst b/reference/constraints/Isbn.rst index 84cb78b16e5..6883f0e1aa4 100644 --- a/reference/constraints/Isbn.rst +++ b/reference/constraints/Isbn.rst @@ -4,14 +4,21 @@ Isbn .. versionadded:: 2.3 The Isbn constraint was introduced in Symfony 2.3. +.. caution:: + + The ``isbn10`` and ``isbn13`` options are deprecated since Symfony 2.5 + and will be removed in Symfony 3.0. Use the ``type`` option instead. + Furthermore, when using the ``type`` option, lowercase characters are no + longer supported starting in Symfony 2.5, as they are not allowed in ISBNs. + This constraint validates that an `International Standard Book Number (ISBN)`_ -is either a valid ISBN-10, a valid ISBN-13 or both. +is either a valid ISBN-10 or a valid ISBN-13. +----------------+----------------------------------------------------------------------+ | Applies to | :ref:`property or method` | +----------------+----------------------------------------------------------------------+ -| Options | - `isbn10`_ | -| | - `isbn13`_ | +| Options | - `type`_ | +| | - `message`_ | | | - `isbn10Message`_ | | | - `isbn13Message`_ | | | - `bothIsbnMessage`_ | @@ -25,7 +32,7 @@ Basic Usage ----------- To use the ``Isbn`` validator, simply apply it to a property or method -on an object that will contain a ISBN number. +on an object that will contain an ISBN. .. configuration-block:: @@ -36,9 +43,8 @@ on an object that will contain a ISBN number. properties: isbn: - Isbn: - isbn10: true - isbn13: true - bothIsbnMessage: This value is neither a valid ISBN-10 nor a valid ISBN-13. + type: isbn10 + message: This value is not valid. .. code-block:: php-annotations @@ -51,9 +57,8 @@ on an object that will contain a ISBN number. { /** * @Assert\Isbn( - * isbn10 = true, - * isbn13 = true, - * bothIsbnMessage = "This value is neither a valid ISBN-10 nor a valid ISBN-13." + * type = isbn10, + * message: This value is not valid. * ) */ protected $isbn; @@ -70,9 +75,8 @@ on an object that will contain a ISBN number. - - - + + @@ -93,9 +97,8 @@ on an object that will contain a ISBN number. public static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addPropertyConstraint('isbn', new Assert\Isbn(array( - 'isbn10' => true, - 'isbn13' => true, - 'bothIsbnMessage' => 'This value is neither a valid ISBN-10 nor a valid ISBN-13.' + 'type' => isbn10, + 'message' => 'This value is not valid.' ))); } } @@ -103,28 +106,28 @@ on an object that will contain a ISBN number. Available Options ----------------- -isbn10 -~~~~~~ +type +~~~~ -**type**: ``boolean`` +**type**: ``string`` **default**: ``null`` -If this required option is set to ``true`` the constraint will check if the -code is a valid ISBN-10 code. +The type of ISBN to validate against. +Valid values are ``isbn10``, ``isbn13`` and ``null`` to accept any kind of ISBN. -isbn13 -~~~~~~ +message +~~~~~~~ -**type**: ``boolean`` +**type**: ``string`` **default**: ``null`` -If this required option is set to ``true`` the constraint will check if the -code is a valid ISBN-13 code. +The message that will be shown if the value is not valid. +If not ``null``, this message has priority over all the other messages. isbn10Message ~~~~~~~~~~~~~ **type**: ``string`` **default**: ``This value is not a valid ISBN-10.`` -The message that will be shown if the `isbn10`_ option is true and the given +The message that will be shown if the `type`_ option is ``isbn10`` and the given value does not pass the ISBN-10 check. isbn13Message @@ -132,7 +135,7 @@ isbn13Message **type**: ``string`` **default**: ``This value is not a valid ISBN-13.`` -The message that will be shown if the `isbn13`_ option is true and the given +The message that will be shown if the `type`_ option is ``isbn13`` and the given value does not pass the ISBN-13 check. bothIsbnMessage @@ -140,7 +143,7 @@ bothIsbnMessage **type**: ``string`` **default**: ``This value is neither a valid ISBN-10 nor a valid ISBN-13.`` -The message that will be shown if both the `isbn10`_ and `isbn13`_ options -are true and the given value does not pass the ISBN-13 nor the ISBN-13 check. +The message that will be shown if the `type`_ option is ``null`` and the given +value does not pass any of the ISBN checks. .. _`International Standard Book Number (ISBN)`: http://en.wikipedia.org/wiki/Isbn diff --git a/reference/constraints/Luhn.rst b/reference/constraints/Luhn.rst index 97d1d27d743..0dca2831300 100644 --- a/reference/constraints/Luhn.rst +++ b/reference/constraints/Luhn.rst @@ -1,9 +1,6 @@ Luhn ==== -.. versionadded:: 2.2 - The ``Luhn`` constraint was introduced in Symfony 2.2. - This constraint is used to ensure that a credit card number passes the `Luhn algorithm`_. It is useful as a first step to validating a credit card: before communicating with a payment gateway. diff --git a/reference/constraints/Time.rst b/reference/constraints/Time.rst index 3e9540801b8..251bb007eab 100644 --- a/reference/constraints/Time.rst +++ b/reference/constraints/Time.rst @@ -35,7 +35,7 @@ of the day when the event starts: // src/Acme/EventBundle/Entity/Event.php namespace Acme\EventBundle\Entity; - + use Symfony\Component\Validator\Constraints as Assert; class Event @@ -62,10 +62,10 @@ of the day when the event starts: .. code-block:: php - + // src/Acme/EventBundle/Entity/Event.php namespace Acme\EventBundle\Entity; - + use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Constraints as Assert; diff --git a/reference/constraints/UniqueEntity.rst b/reference/constraints/UniqueEntity.rst index c622244a0e4..2de1705fae8 100644 --- a/reference/constraints/UniqueEntity.rst +++ b/reference/constraints/UniqueEntity.rst @@ -23,7 +23,7 @@ using an email address that already exists in the system. Basic Usage ----------- -Suppose you have an ``AcmeUserBundle`` bundle with a ``User`` entity that has an +Suppose you have an AcmeUserBundle bundle with a ``User`` entity that has an ``email`` field. You can use the ``UniqueEntity`` constraint to guarantee that the ``email`` field remains unique between all of the constraints in your user table: diff --git a/reference/constraints/UserPassword.rst b/reference/constraints/UserPassword.rst index ee879a0d7c2..dee677b1d78 100644 --- a/reference/constraints/UserPassword.rst +++ b/reference/constraints/UserPassword.rst @@ -1,14 +1,6 @@ UserPassword ============ -.. note:: - - Since Symfony 2.2, the ``UserPassword*`` classes in the - ``Symfony\\Component\\Security\\Core\\Validator\\Constraint`` namespace are - deprecated and will be removed in Symfony 2.3. Please use the - ``UserPassword*`` classes in the - ``Symfony\\Component\\Security\\Core\\Validator\\Constraints`` namespace instead. - This validates that an input value is equal to the current authenticated user's password. This is useful in a form where a user can change their password, but needs to enter their old password for security. diff --git a/reference/constraints/Uuid.rst b/reference/constraints/Uuid.rst new file mode 100644 index 00000000000..4db4fc8ceab --- /dev/null +++ b/reference/constraints/Uuid.rst @@ -0,0 +1,125 @@ +Uuid +==== + +.. versionadded:: 2.5 + The Uuid constraint was introduced in Symfony 2.5. + +Validates that a value is a valid `Universally unique identifier (UUID)`_ per `RFC 4122`_. +By default, this will validate the format according to the RFC's guidelines, but this can +be relaxed to accept non-standard UUIDs that other systems (like PostgreSQL) accept. +UUID versions can also be restricted using a whitelist. + ++----------------+---------------------------------------------------------------------+ +| Applies to | :ref:`property or method ` | ++----------------+---------------------------------------------------------------------+ +| Options | - `message`_ | +| | - `strict`_ | +| | - `versions`_ | ++----------------+---------------------------------------------------------------------+ +| Class | :class:`Symfony\\Component\\Validator\\Constraints\\Uuid` | ++----------------+---------------------------------------------------------------------+ +| Validator | :class:`Symfony\\Component\\Validator\\Constraints\\UuidValidator` | ++----------------+---------------------------------------------------------------------+ + +Basic Usage +----------- + +.. configuration-block:: + + .. code-block:: yaml + + # src/UploadsBundle/Resources/config/validation.yml + Acme\UploadsBundle\Entity\File: + properties: + identifier: + - Uuid: ~ + + .. code-block:: php-annotations + + // src/Acme/UploadsBundle/Entity/File.php + namespace Acme\UploadsBundle\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class File + { + /** + * @Assert\Uuid + */ + protected $identifier; + } + + .. code-block:: xml + + + + + + + + + + + + + .. code-block:: php + + // src/Acme/UploadsBundle/Entity/File.php + namespace Acme\UploadsBundle\Entity; + + use Symfony\Component\Validator\Mapping\ClassMetadata; + use Symfony\Component\Validator\Constraints as Assert; + + class File + { + public static function loadValidatorMetadata(ClassMetadata $metadata) + { + $metadata->addPropertyConstraint('identifier', new Assert\Uuid()); + } + } + + +Options +------- + +message +~~~~~~~ + +**type**: ``string`` **default**: ``This is not a valid UUID.`` + +This message is shown if the string is not a valid UUID. + +strict +~~~~~~ + +**type**: ``boolean`` **default**: ``true`` + +If this option is set to ``true`` the constraint will check if the UUID is formatted per the +RFC's input format rules: ``216fff40-98d9-11e3-a5e2-0800200c9a66``. Setting this to ``false`` +will allow alternate input formats like: + +* ``216f-ff40-98d9-11e3-a5e2-0800-200c-9a66`` +* ``{216fff40-98d9-11e3-a5e2-0800200c9a66}`` +* ``216fff4098d911e3a5e20800200c9a66`` + +versions +~~~~~~~~ + +**type**: ``int[]`` **default**: ``[1,2,3,4,5]`` + +This option can be used to only allow specific `UUID versions`_. Valid versions are 1 - 5. +The following PHP constants can also be used: + +* ``Uuid::V1_MAC`` +* ``Uuid::V2_DCE`` +* ``Uuid::V3_MD5`` +* ``Uuid::V4_RANDOM`` +* ``Uuid::V5_SHA1`` + +All five versions are allowed by default. + +.. _`Universally unique identifier (UUID)`: http://en.wikipedia.org/wiki/Universally_unique_identifier +.. _`RFC 4122`: http://tools.ietf.org/html/rfc4122 +.. _`UUID versions`: http://en.wikipedia.org/wiki/Universally_unique_identifier#Variants_and_versions diff --git a/reference/constraints/Valid.rst b/reference/constraints/Valid.rst index 407c5339cbc..629f5a2d6cb 100644 --- a/reference/constraints/Valid.rst +++ b/reference/constraints/Valid.rst @@ -9,9 +9,9 @@ object and all sub-objects associated with it. | Applies to | :ref:`property or method ` | +----------------+---------------------------------------------------------------------+ | Options | - `traverse`_ | -| | - `message`_ | +| | - `deep`_ | +----------------+---------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Validator\\Constraints\\Type` | +| Class | :class:`Symfony\\Component\\Validator\\Constraints\\Valid` | +----------------+---------------------------------------------------------------------+ .. include:: /reference/forms/types/options/_error_bubbling_hint.rst.inc @@ -267,9 +267,11 @@ If this constraint is applied to a property that holds an array of objects, then each object in that array will be validated only if this option is set to ``true``. -message -~~~~~~~ +deep +~~~~ -**type**: ``string`` **default**: ``This value should be true.`` +**type**: ``boolean`` **default**: ``false`` -This is the message that will be shown if the value is false. +If this constraint is applied to a property that holds an array of objects, +then each object in that array will be validated recursively if this option is set +to ``true``. diff --git a/reference/constraints/map.rst.inc b/reference/constraints/map.rst.inc index 237329866d4..686ad22bca5 100644 --- a/reference/constraints/map.rst.inc +++ b/reference/constraints/map.rst.inc @@ -20,6 +20,7 @@ String Constraints * :doc:`Url ` * :doc:`Regex ` * :doc:`Ip ` +* :doc:`Uuid` Number Constraints ~~~~~~~~~~~~~~~~~~ diff --git a/reference/dic_tags.rst b/reference/dic_tags.rst index a5441a57e8e..49b39581d12 100644 --- a/reference/dic_tags.rst +++ b/reference/dic_tags.rst @@ -12,79 +12,47 @@ section of the Service Container chapter. Below is information about all of the tags available inside Symfony. There may also be tags in other bundles you use that aren't listed here. -+-----------------------------------+---------------------------------------------------------------------------+ -| Tag Name | Usage | -+===================================+===========================================================================+ -| `assetic.asset`_ | Register an asset to the current asset manager | -+-----------------------------------+---------------------------------------------------------------------------+ -| `assetic.factory_worker`_ | Add a factory worker | -+-----------------------------------+---------------------------------------------------------------------------+ -| `assetic.filter`_ | Register a filter | -+-----------------------------------+---------------------------------------------------------------------------+ -| `assetic.formula_loader`_ | Add a formula loader to the current asset manager | -+-----------------------------------+---------------------------------------------------------------------------+ -| `assetic.formula_resource`_ | Adds a resource to the current asset manager | -+-----------------------------------+---------------------------------------------------------------------------+ -| `assetic.templating.php`_ | Remove this service if PHP templating is disabled | -+-----------------------------------+---------------------------------------------------------------------------+ -| `assetic.templating.twig`_ | Remove this service if Twig templating is disabled | -+-----------------------------------+---------------------------------------------------------------------------+ -| `console.command`_ | Add a command | -+-----------------------------------+---------------------------------------------------------------------------+ -| `data_collector`_ | Create a class that collects custom data for the profiler | -+-----------------------------------+---------------------------------------------------------------------------+ -| `doctrine.event_listener`_ | Add a Doctrine event listener | -+-----------------------------------+---------------------------------------------------------------------------+ -| `doctrine.event_subscriber`_ | Add a Doctrine event subscriber | -+-----------------------------------+---------------------------------------------------------------------------+ -| `form.type`_ | Create a custom form field type | -+-----------------------------------+---------------------------------------------------------------------------+ -| `form.type_extension`_ | Create a custom "form extension" | -+-----------------------------------+---------------------------------------------------------------------------+ -| `form.type_guesser`_ | Add your own logic for "form type guessing" | -+-----------------------------------+---------------------------------------------------------------------------+ -| `kernel.cache_clearer`_ | Register your service to be called during the cache clearing process | -+-----------------------------------+---------------------------------------------------------------------------+ -| `kernel.cache_warmer`_ | Register your service to be called during the cache warming process | -+-----------------------------------+---------------------------------------------------------------------------+ -| `kernel.event_listener`_ | Listen to different events/hooks in Symfony | -+-----------------------------------+---------------------------------------------------------------------------+ -| `kernel.event_subscriber`_ | To subscribe to a set of different events/hooks in Symfony | -+-----------------------------------+---------------------------------------------------------------------------+ -| `kernel.fragment_renderer`_ | Add new HTTP content rendering strategies | -+-----------------------------------+---------------------------------------------------------------------------+ -| `monolog.logger`_ | Logging with a custom logging channel | -+-----------------------------------+---------------------------------------------------------------------------+ -| `monolog.processor`_ | Add a custom processor for logging | -+-----------------------------------+---------------------------------------------------------------------------+ -| `routing.loader`_ | Register a custom service that loads routes | -+-----------------------------------+---------------------------------------------------------------------------+ -| `security.voter`_ | Add a custom voter to Symfony's authorization logic | -+-----------------------------------+---------------------------------------------------------------------------+ -| `security.remember_me_aware`_ | To allow remember me authentication | -+-----------------------------------+---------------------------------------------------------------------------+ -| `serializer.encoder`_ | Register a new encoder in the ``serializer`` service | -+-----------------------------------+---------------------------------------------------------------------------+ -| `serializer.normalizer`_ | Register a new normalizer in the ``serializer`` service | -+-----------------------------------+---------------------------------------------------------------------------+ -| `swiftmailer.default.plugin`_ | Register a custom SwiftMailer Plugin | -+-----------------------------------+---------------------------------------------------------------------------+ -| `templating.helper`_ | Make your service available in PHP templates | -+-----------------------------------+---------------------------------------------------------------------------+ -| `translation.loader`_ | Register a custom service that loads translations | -+-----------------------------------+---------------------------------------------------------------------------+ -| `translation.extractor`_ | Register a custom service that extracts translation messages from a file | -+-----------------------------------+---------------------------------------------------------------------------+ -| `translation.dumper`_ | Register a custom service that dumps translation messages | -+-----------------------------------+---------------------------------------------------------------------------+ -| `twig.extension`_ | Register a custom Twig Extension | -+-----------------------------------+---------------------------------------------------------------------------+ -| `twig.loader`_ | Register a custom service that loads Twig templates | -+-----------------------------------+---------------------------------------------------------------------------+ -| `validator.constraint_validator`_ | Create your own custom validation constraint | -+-----------------------------------+---------------------------------------------------------------------------+ -| `validator.initializer`_ | Register a service that initializes objects before validation | -+-----------------------------------+---------------------------------------------------------------------------+ +======================================== ======================================================================== +Tag Name Usage +======================================== ======================================================================== +`assetic.asset`_ Register an asset to the current asset manager +`assetic.factory_worker`_ Add a factory worker +`assetic.filter`_ Register a filter +`assetic.formula_loader`_ Add a formula loader to the current asset manager +`assetic.formula_resource`_ Adds a resource to the current asset manager +`assetic.templating.php`_ Remove this service if PHP templating is disabled +`assetic.templating.twig`_ Remove this service if Twig templating is disabled +`console.command`_ Add a command +`data_collector`_ Create a class that collects custom data for the profiler +`doctrine.event_listener`_ Add a Doctrine event listener +`doctrine.event_subscriber`_ Add a Doctrine event subscriber +`form.type`_ Create a custom form field type +`form.type_extension`_ Create a custom "form extension" +`form.type_guesser`_ Add your own logic for "form type guessing" +`kernel.cache_clearer`_ Register your service to be called during the cache clearing process +`kernel.cache_warmer`_ Register your service to be called during the cache warming process +`kernel.event_listener`_ Listen to different events/hooks in Symfony +`kernel.event_subscriber`_ To subscribe to a set of different events/hooks in Symfony +`kernel.fragment_renderer`_ Add new HTTP content rendering strategies +`monolog.logger`_ Logging with a custom logging channel +`monolog.processor`_ Add a custom processor for logging +`routing.loader`_ Register a custom service that loads routes +`routing.expression_language_provider`_ Register a provider for expression language functions in routing +`security.expression_language_provider`_ Register a provider for expression language functions in security +`security.voter`_ Add a custom voter to Symfony's authorization logic +`security.remember_me_aware`_ To allow remember me authentication +`serializer.encoder`_ Register a new encoder in the ``serializer`` service +`serializer.normalizer`_ Register a new normalizer in the ``serializer`` service +`swiftmailer.default.plugin`_ Register a custom SwiftMailer Plugin +`templating.helper`_ Make your service available in PHP templates +`translation.loader`_ Register a custom service that loads translations +`translation.extractor`_ Register a custom service that extracts translation messages from a file +`translation.dumper`_ Register a custom service that dumps translation messages +`twig.extension`_ Register a custom Twig Extension +`twig.loader`_ Register a custom service that loads Twig templates +`validator.constraint_validator`_ Create your own custom validation constraint +`validator.initializer`_ Register a service that initializes objects before validation +======================================== ======================================================================== assetic.asset ------------- @@ -262,10 +230,6 @@ The tagged service will be removed from the container if console.command --------------- -.. versionadded:: 2.4 - Support for registering commands in the service container was introduced in - Symfony 2.4. - **Purpose**: Add a command to the application For details on registering your own commands in the service container, read @@ -551,9 +515,6 @@ points. For a full example of this listener, read the :doc:`/cookbook/service_container/event_listener` cookbook entry. -For another practical example of a kernel listener, see the cookbook -article: :doc:`/cookbook/request/mime_type`. - Core Event Listener Reference ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -632,7 +593,7 @@ kernel.terminate +-------------------------------------------------------------------------------------------+----------+ | Listener Class Name | Priority | +===========================================================================================+==========+ -| :class:`Symfony\\Bundle\\SwiftmailerBundle\\EventListener\\EmailSenderListener` | 0 | +| `EmailSenderListener`_ | 0 | +-------------------------------------------------------------------------------------------+----------+ .. _dic-tags-kernel-event-subscriber: @@ -919,6 +880,34 @@ of your configuration, and tag it with ``routing.loader``: For more information, see :doc:`/cookbook/routing/custom_route_loader`. +routing.expression_language_provider +------------------------------------ + +.. versionadded:: 2.6 + The ``routing.expression_language_provider`` tag was introduced in Symfony + 2.6. + +**Purpose**: Register a provider for expression language functions in routing + +This tag is used to automatically register +:ref:`expression function providers ` +for the routing expression component. Using these providers, you can add custom +functions to the routing expression language. + +security.expression_language_provider +------------------------------------- + +.. versionadded:: 2.6 + The ``security.expression_language_provider`` tag was introduced in Symfony + 2.6. + +**Purpose**: Register a provider for expression language functions in security + +This tag is used to automatically register :ref:`expression function providers +` for the security expression +component. Using these providers, you can add custom functions to the security +expression language. + security.remember_me_aware -------------------------- @@ -940,7 +929,7 @@ security.voter **Purpose**: To add a custom voter to Symfony's authorization logic -When you call ``isGranted`` on Symfony's security context, a system of "voters" +When you call ``isGranted`` on Symfony's authorization checker, a system of "voters" is used behind the scenes to determine if the user should have access. The ``security.voter`` tag allows you to add your own custom voter to that system. @@ -1402,5 +1391,7 @@ For an example, see the ``EntityInitializer`` class inside the Doctrine Bridge. .. _`Twig's documentation`: http://twig.sensiolabs.org/doc/advanced.html#creating-an-extension .. _`Twig official extension repository`: https://github.com/fabpot/Twig-extensions +.. _`KernelEvents`: https://github.com/symfony/symfony/blob/master/src/Symfony/Component/HttpKernel/KernelEvents.php .. _`SwiftMailer's Plugin Documentation`: http://swiftmailer.org/docs/plugins.html .. _`Twig Loader`: http://twig.sensiolabs.org/doc/api.html#loaders +.. _`EmailSenderListener`: https://github.com/symfony/SwiftmailerBundle/blob/master/EventListener/EmailSenderListener.php diff --git a/reference/forms/twig_reference.rst b/reference/forms/twig_reference.rst index 1db2fb2f217..68e694f1390 100644 --- a/reference/forms/twig_reference.rst +++ b/reference/forms/twig_reference.rst @@ -316,9 +316,6 @@ object: .. versionadded:: 2.3 The ``method`` and ``action`` variables were introduced in Symfony 2.3. -.. versionadded:: 2.4 - The ``submitted`` variable was introduced in Symfony 2.4. - +------------------------+-------------------------------------------------------------------------------------+ | Variable | Usage | +========================+=====================================================================================+ @@ -350,9 +347,11 @@ object: | ``required`` | If ``true``, a ``required`` attribute is added to the field to activate HTML5 | | | validation. Additionally, a ``required`` class is added to the label. | +------------------------+-------------------------------------------------------------------------------------+ -| ``max_length`` | Adds a ``maxlength`` HTML attribute to the element. | +| ``max_length`` | Adds a ``maxlength`` HTML attribute to the element. (deprecated as of 2.5, to be | +| | removed in 3.0, use ``attr["maxlength"]`` instead) | +------------------------+-------------------------------------------------------------------------------------+ -| ``pattern`` | Adds a ``pattern`` HTML attribute to the element. | +| ``pattern`` | Adds a ``pattern`` HTML attribute to the element. (deprecated as of 2.5, to be | +| | removed in 3.0, use ``attr["pattern"]`` instead) | +------------------------+-------------------------------------------------------------------------------------+ | ``label`` | The string label that will be rendered. | +------------------------+-------------------------------------------------------------------------------------+ diff --git a/reference/forms/types/birthday.rst b/reference/forms/types/birthday.rst index d77551c3b29..2926a22b369 100644 --- a/reference/forms/types/birthday.rst +++ b/reference/forms/types/birthday.rst @@ -25,7 +25,7 @@ option defaults to 120 years ago to the current year. | Inherited options | from the :doc:`date ` type: | | | | | | - `days`_ | -| | - `empty_value`_ | +| | - `placeholder`_ | | | - `format`_ | | | - `input`_ | | | - `model_timezone`_ | @@ -66,7 +66,7 @@ These options inherit from the :doc:`date ` type: .. include:: /reference/forms/types/options/days.rst.inc -.. include:: /reference/forms/types/options/empty_value.rst.inc +.. include:: /reference/forms/types/options/placeholder.rst.inc .. include:: /reference/forms/types/options/date_format.rst.inc diff --git a/reference/forms/types/choice.rst b/reference/forms/types/choice.rst index 9b2ef49971c..a35adba892e 100644 --- a/reference/forms/types/choice.rst +++ b/reference/forms/types/choice.rst @@ -15,7 +15,7 @@ option. +-------------+------------------------------------------------------------------------------+ | Options | - `choices`_ | | | - `choice_list`_ | -| | - `empty_value`_ | +| | - `placeholder`_ | | | - `expanded`_ | | | - `multiple`_ | | | - `preferred_choices`_ | @@ -104,7 +104,7 @@ is the item value and the array value is the item's label:: choice_list ~~~~~~~~~~~ -**type**: ``Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface`` +**type**: :class:`Symfony\\Component\\Form\\Extension\\Core\\ChoiceList\\ChoiceListInterface` This is one way of specifying the options to be used for this field. The ``choice_list`` option must be an instance of the ``ChoiceListInterface``. @@ -119,10 +119,10 @@ With this option you can also allow float values to be selected as data. // ... $builder->add('status', 'choice', array( - 'choice_list' => new ChoiceList(array(1, 0.5), array('Full', 'Half') + 'choice_list' => new ChoiceList(array(1, 0.5), array('Full', 'Half')) )); -.. include:: /reference/forms/types/options/empty_value.rst.inc +.. include:: /reference/forms/types/options/placeholder.rst.inc .. include:: /reference/forms/types/options/expanded.rst.inc @@ -204,13 +204,13 @@ Field Variables +------------------------+--------------+-------------------------------------------------------------------+ | separator | ``string`` | The separator to use between choice groups. | +------------------------+--------------+-------------------------------------------------------------------+ -| empty_value | ``mixed`` | The empty value if not already in the list, otherwise | +| placeholder | ``mixed`` | The empty value if not already in the list, otherwise | | | | ``null``. | +------------------------+--------------+-------------------------------------------------------------------+ | is_selected | ``callable`` | A callable which takes a ``ChoiceView`` and the selected value(s) | | | | and returns whether the choice is in the selected value(s). | +------------------------+--------------+-------------------------------------------------------------------+ -| empty_value_in_choices | ``Boolean`` | Whether the empty value is in the choice list. | +| placeholder_in_choices | ``Boolean`` | Whether the empty value is in the choice list. | +------------------------+--------------+-------------------------------------------------------------------+ .. tip:: diff --git a/reference/forms/types/collection.rst b/reference/forms/types/collection.rst index 0c86877850d..2d21cf57a3a 100644 --- a/reference/forms/types/collection.rst +++ b/reference/forms/types/collection.rst @@ -15,6 +15,7 @@ forms, which is useful when creating forms that expose one-to-many relationships +-------------+-----------------------------------------------------------------------------+ | Options | - `allow_add`_ | | | - `allow_delete`_ | +| | - `delete_empty`_ | | | - `options`_ | | | - `prototype`_ | | | - `prototype_name`_ | @@ -101,7 +102,7 @@ A much more flexible method would look like this: errors($emailField) ?> widget($emailField) ?> - +
    In both cases, no input fields would render unless your ``emails`` data array @@ -179,7 +180,9 @@ you need is the JavaScript: var emailCount = '{{ form.emails|length }}'; jQuery(document).ready(function() { - jQuery('#add-another-email').click(function() { + jQuery('#add-another-email').click(function(e) { + e.preventDefault(); + var emailList = jQuery('#email-fields-list'); // grab the prototype template @@ -192,9 +195,7 @@ you need is the JavaScript: // create a new list element and add it to the list var newLi = jQuery('
  • ').html(newWidget); - newLi.appendTo(jQuery('#email-fields-list')); - - return false; + newLi.appendTo(emailList); }); }) @@ -255,6 +256,19 @@ For more information, see :ref:`cookbook-form-collections-remove`. None of this is handled automatically. For more information, see :ref:`cookbook-form-collections-remove`. +delete_empty +~~~~~~~~~~~~ + +.. versionadded:: 2.5 + The ``delete_empty`` option was introduced in Symfony 2.5. + +**type**: ``Boolean`` **default**: ``false`` + +If you want to explicitly remove entirely empty collection entries from your +form you have to set this option to true. However, existing collection entries +will only be deleted if you have the allow_delete_ option enabled. Otherwise +the empty values will be kept. + options ~~~~~~~ @@ -376,9 +390,9 @@ error_bubbling Field Variables --------------- -============ =========== ======================================== -Variable Type Usage -============ =========== ======================================== -allow_add ``Boolean`` The value of the `allow_add`_ option. -allow_delete ``Boolean`` The value of the `allow_delete`_ option. -============ =========== ======================================== +============ =========== ======================================== +Variable Type Usage +============ =========== ======================================== +allow_add ``Boolean`` The value of the `allow_add`_ option. +allow_delete ``Boolean`` The value of the `allow_delete`_ option. +============ =========== ======================================== diff --git a/reference/forms/types/country.rst b/reference/forms/types/country.rst index c50ac519a48..fa19b1d31bd 100644 --- a/reference/forms/types/country.rst +++ b/reference/forms/types/country.rst @@ -27,7 +27,7 @@ you should just use the ``choice`` type directly. +-------------+-----------------------------------------------------------------------+ | Inherited | from the :doc:`choice ` type | | options | | -| | - `empty_value`_ | +| | - `placeholder`_ | | | - `error_bubbling`_ | | | - `error_mapping`_ | | | - `expanded`_ | @@ -66,7 +66,7 @@ Inherited Options These options inherit from the :doc:`choice ` type: -.. include:: /reference/forms/types/options/empty_value.rst.inc +.. include:: /reference/forms/types/options/placeholder.rst.inc .. include:: /reference/forms/types/options/error_bubbling.rst.inc diff --git a/reference/forms/types/currency.rst b/reference/forms/types/currency.rst index f009d726993..552b488d00a 100644 --- a/reference/forms/types/currency.rst +++ b/reference/forms/types/currency.rst @@ -21,7 +21,7 @@ should just use the ``choice`` type directly. +-------------+------------------------------------------------------------------------+ | Inherited | from the :doc:`choice ` type | | options | | -| | - `empty_value`_ | +| | - `placeholder`_ | | | - `error_bubbling`_ | | | - `expanded`_ | | | - `multiple`_ | @@ -58,7 +58,7 @@ Inherited Options These options inherit from the :doc:`choice` type: -.. include:: /reference/forms/types/options/empty_value.rst.inc +.. include:: /reference/forms/types/options/placeholder.rst.inc .. include:: /reference/forms/types/options/error_bubbling.rst.inc diff --git a/reference/forms/types/date.rst b/reference/forms/types/date.rst index 994ff6156b3..8197e7da328 100644 --- a/reference/forms/types/date.rst +++ b/reference/forms/types/date.rst @@ -20,8 +20,9 @@ day, and year) or three select boxes (see the `widget`_ option). | Rendered as | single text box or three select fields | +----------------------+-----------------------------------------------------------------------------+ | Options | - `days`_ | -| | - `empty_value`_ | +| | - `placeholder`_ | | | - `format`_ | +| | - `html5`_ | | | - `input`_ | | | - `model_timezone`_ | | | - `months`_ | @@ -82,29 +83,35 @@ Field Options .. include:: /reference/forms/types/options/days.rst.inc -empty_value +placeholder ~~~~~~~~~~~ +.. versionadded:: 2.6 + The ``placeholder`` option was introduced in Symfony 2.6 in favor of + ``empty_value``, which is available prior to 2.6. + **type**: ``string`` or ``array`` If your widget option is set to ``choice``, then this field will be represented -as a series of ``select`` boxes. The ``empty_value`` option can be used to +as a series of ``select`` boxes. The ``placeholder`` option can be used to add a "blank" entry to the top of each select box:: $builder->add('dueDate', 'date', array( - 'empty_value' => '', + 'placeholder' => '', )); Alternatively, you can specify a string to be displayed for the "blank" value:: $builder->add('dueDate', 'date', array( - 'empty_value' => array('year' => 'Year', 'month' => 'Month', 'day' => 'Day') + 'placeholder' => array('year' => 'Year', 'month' => 'Month', 'day' => 'Day') )); .. _reference-forms-type-date-format: .. include:: /reference/forms/types/options/date_format.rst.inc +.. include:: /reference/forms/types/options/html5.rst.inc + .. _form-reference-date-input: .. include:: /reference/forms/types/options/date_input.rst.inc diff --git a/reference/forms/types/datetime.rst b/reference/forms/types/datetime.rst index fc58493207f..e294d9ac464 100644 --- a/reference/forms/types/datetime.rst +++ b/reference/forms/types/datetime.rst @@ -18,9 +18,10 @@ data can be a ``DateTime`` object, a string, a timestamp or an array. | Options | - `date_format`_ | | | - `date_widget`_ | | | - `days`_ | -| | - `empty_value`_ | +| | - `placeholder`_ | | | - `format`_ | | | - `hours`_ | +| | - `html5`_ | | | - `input`_ | | | - `minutes`_ | | | - `model_timezone`_ | @@ -67,7 +68,7 @@ Defines the ``widget`` option for the :doc:`date ` .. include:: /reference/forms/types/options/days.rst.inc -.. include:: /reference/forms/types/options/empty_value.rst.inc +.. include:: /reference/forms/types/options/placeholder.rst.inc format ~~~~~~ @@ -82,6 +83,8 @@ field to be rendered as an ``input`` field with ``type="datetime"``. .. include:: /reference/forms/types/options/hours.rst.inc +.. include:: /reference/forms/types/options/html5.rst.inc + input ~~~~~ diff --git a/reference/forms/types/email.rst b/reference/forms/types/email.rst index 9a60ae4f32c..d6277f5bee9 100644 --- a/reference/forms/types/email.rst +++ b/reference/forms/types/email.rst @@ -18,7 +18,7 @@ The ``email`` field is a text field that is rendered using the HTML5 | | - `label`_ | | | - `label_attr`_ | | | - `mapped`_ | -| | - `max_length`_ | +| | - `max_length`_ (deprecated as of 2.5) | | | - `read_only`_ | | | - `required`_ | | | - `trim`_ | diff --git a/reference/forms/types/entity.rst b/reference/forms/types/entity.rst index dcada6d96eb..73dfae62f92 100644 --- a/reference/forms/types/entity.rst +++ b/reference/forms/types/entity.rst @@ -24,7 +24,7 @@ objects from the database. +-------------+------------------------------------------------------------------+ | Inherited | from the :doc:`choice ` type: | | options | | -| | - `empty_value`_ | +| | - `placeholder`_ | | | - `expanded`_ | | | - `multiple`_ | | | - `preferred_choices`_ | @@ -117,7 +117,7 @@ or the short alias name (as shown prior). em ~~ -**type**: ``string`` **default**: the default entity manager +**type**: ``string`` | ``Doctrine\Common\Persistence\ObjectManager`` **default**: the default entity manager If specified, the specified entity manager will be used to load the choices instead of the default entity manager. @@ -183,7 +183,7 @@ directly. choices ~~~~~~~ -**type**: array || ``\Traversable`` **default**: ``null`` +**type**: array | ``\Traversable`` **default**: ``null`` Instead of allowing the `class`_ and `query_builder`_ options to fetch the entities to include for you, you can pass the ``choices`` option directly. @@ -194,7 +194,7 @@ Inherited Options These options inherit from the :doc:`choice ` type: -.. include:: /reference/forms/types/options/empty_value.rst.inc +.. include:: /reference/forms/types/options/placeholder.rst.inc .. include:: /reference/forms/types/options/expanded.rst.inc diff --git a/reference/forms/types/file.rst b/reference/forms/types/file.rst index 3751bdff8b1..2449a4fdc92 100644 --- a/reference/forms/types/file.rst +++ b/reference/forms/types/file.rst @@ -9,6 +9,8 @@ The ``file`` type represents a file input in your form. +-------------+---------------------------------------------------------------------+ | Rendered as | ``input`` ``file`` field | +-------------+---------------------------------------------------------------------+ +| Options | - `multiple`_ | ++-------------+---------------------------------------------------------------------+ | Inherited | - `disabled`_ | | options | - `empty_data`_ | | | - `error_bubbling`_ | @@ -78,6 +80,19 @@ before using it directly. Read the :doc:`cookbook ` for an example of how to manage a file upload associated with a Doctrine entity. +Field Options +------------- + +multiple +~~~~~~~~ + +.. versionadded:: 2.5 + The ``multiple`` option was introduced in Symfony 2.5. + +**type**: ``Boolean`` **default**: ``false`` + +When set to true, the user will be able to upload multiple files at the same time. + Inherited Options ----------------- @@ -110,8 +125,8 @@ The default value is ``null``. Form Variables -------------- -======== ========== =============================================================================== -Variable Type Usage -======== ========== =============================================================================== -type ``string`` The type variable is set to ``file``, in order to render as a file input field. -======== ========== =============================================================================== +======== ========== =============================================================================== +Variable Type Usage +======== ========== =============================================================================== +type ``string`` The type variable is set to ``file``, in order to render as a file input field. +======== ========== =============================================================================== diff --git a/reference/forms/types/form.rst b/reference/forms/types/form.rst index 976f3c3e5d1..ae0f66edcc0 100644 --- a/reference/forms/types/form.rst +++ b/reference/forms/types/form.rst @@ -24,9 +24,9 @@ on all types for which ``form`` is the parent type. | | - `invalid_message_parameters`_ | | | - `label_attr`_ | | | - `mapped`_ | -| | - `max_length`_ | +| | - `max_length`_ (deprecated as of 2.5) | | | - `method`_ | -| | - `pattern`_ | +| | - `pattern`_ (deprecated as of 2.5) | | | - `post_max_size_message`_ | | | - `property_path`_ | | | - `read_only`_ | @@ -48,6 +48,8 @@ on all types for which ``form`` is the parent type. Field Options ------------- +.. _form-option-action: + .. include:: /reference/forms/types/options/action.rst.inc .. include:: /reference/forms/types/options/by_reference.rst.inc @@ -76,6 +78,8 @@ The actual default value of this option depends on other field options: .. include:: /reference/forms/types/options/empty_data.rst.inc :start-after: DEFAULT_PLACEHOLDER +.. _reference-form-option-error-bubbling: + .. include:: /reference/forms/types/options/error_bubbling.rst.inc .. include:: /reference/forms/types/options/error_mapping.rst.inc @@ -96,6 +100,8 @@ The actual default value of this option depends on other field options: .. include:: /reference/forms/types/options/max_length.rst.inc +.. _form-option-method: + .. include:: /reference/forms/types/options/method.rst.inc .. _reference-form-option-pattern: diff --git a/reference/forms/types/language.rst b/reference/forms/types/language.rst index 7e18fcbcf39..e4913e4d707 100644 --- a/reference/forms/types/language.rst +++ b/reference/forms/types/language.rst @@ -8,8 +8,8 @@ The ``language`` type is a subset of the ``ChoiceType`` that allows the user to select from a large list of languages. As an added bonus, the language names are displayed in the language of the user. -The "value" for each language is the *Unicode language identifier* -(e.g. ``fr`` or ``zh-Hant``). +The "value" for each language is the *Unicode language identifier* used in +the `International Components for Unicode`_ (e.g. ``fr`` or ``zh_Hant``). .. note:: @@ -28,7 +28,7 @@ you should just use the ``choice`` type directly. +-------------+------------------------------------------------------------------------+ | Inherited | from the :doc:`choice ` type | | options | | -| | - `empty_value`_ | +| | - `placeholder`_ | | | - `error_bubbling`_ | | | - `error_mapping`_ | | | - `expanded`_ | @@ -67,7 +67,7 @@ Inherited Options These options inherit from the :doc:`choice ` type: -.. include:: /reference/forms/types/options/empty_value.rst.inc +.. include:: /reference/forms/types/options/placeholder.rst.inc .. include:: /reference/forms/types/options/error_bubbling.rst.inc @@ -106,3 +106,5 @@ The actual default value of this option depends on other field options: .. include:: /reference/forms/types/options/read_only.rst.inc .. include:: /reference/forms/types/options/required.rst.inc + +.. _`International Components for Unicode`: http://site.icu-project.org diff --git a/reference/forms/types/locale.rst b/reference/forms/types/locale.rst index e8610495901..f6b33c0af39 100644 --- a/reference/forms/types/locale.rst +++ b/reference/forms/types/locale.rst @@ -30,7 +30,7 @@ you should just use the ``choice`` type directly. +-------------+------------------------------------------------------------------------+ | Inherited | from the :doc:`choice ` type | | options | | -| | - `empty_value`_ | +| | - `placeholder`_ | | | - `error_bubbling`_ | | | - `error_mapping`_ | | | - `expanded`_ | @@ -69,7 +69,7 @@ Inherited Options These options inherit from the :doc:`choice ` type: -.. include:: /reference/forms/types/options/empty_value.rst.inc +.. include:: /reference/forms/types/options/placeholder.rst.inc .. include:: /reference/forms/types/options/error_bubbling.rst.inc diff --git a/reference/forms/types/money.rst b/reference/forms/types/money.rst index dd466595c3b..6db6ead9c06 100644 --- a/reference/forms/types/money.rst +++ b/reference/forms/types/money.rst @@ -121,10 +121,10 @@ The default value is ``''`` (the empty string). Form Variables -------------- -============= ========== =============================================================== -Variable Type Usage -============= ========== =============================================================== -money_pattern ``string`` The format to use to display the money, including the currency. -============= ========== =============================================================== +============= ========== =============================================================== +Variable Type Usage +============= ========== =============================================================== +money_pattern ``string`` The format to use to display the money, including the currency. +============= ========== =============================================================== .. _`3 letter ISO 4217 code`: http://en.wikipedia.org/wiki/ISO_4217 diff --git a/reference/forms/types/options/_date_limitation.rst.inc b/reference/forms/types/options/_date_limitation.rst.inc index 8a39d6f6b80..4178e4dbc51 100644 --- a/reference/forms/types/options/_date_limitation.rst.inc +++ b/reference/forms/types/options/_date_limitation.rst.inc @@ -1,5 +1,5 @@ .. caution:: - + If ``timestamp`` is used, ``DateType`` is limited to dates between Fri, 13 Dec 1901 20:45:54 GMT and Tue, 19 Jan 2038 03:14:07 GMT on 32bit - systems. This is due to a `limitation in PHP itself `_. \ No newline at end of file + systems. This is due to a `limitation in PHP itself `_. diff --git a/reference/forms/types/options/_error_bubbling_body.rst.inc b/reference/forms/types/options/_error_bubbling_body.rst.inc index 5fd93614456..19c0b2ddef7 100644 --- a/reference/forms/types/options/_error_bubbling_body.rst.inc +++ b/reference/forms/types/options/_error_bubbling_body.rst.inc @@ -1,3 +1,3 @@ If ``true``, any errors for this field will be passed to the parent field or form. For example, if set to ``true`` on a normal field, any errors for -that field will be attached to the main form, not to the specific field. \ No newline at end of file +that field will be attached to the main form, not to the specific field. diff --git a/reference/forms/types/options/block_name.rst.inc b/reference/forms/types/options/block_name.rst.inc index 4f11ba54f52..6cfdeaf9a10 100644 --- a/reference/forms/types/options/block_name.rst.inc +++ b/reference/forms/types/options/block_name.rst.inc @@ -1,7 +1,8 @@ block_name ~~~~~~~~~~~ -**type**: ``string`` **default**: the form's name (see :ref:`Knowing which block to customize `) +**type**: ``string`` **default**: the form's name (see :ref:`Knowing which +block to customize `) Allows you to override the block name used to render the form type. Useful for example if you have multiple instances of the same form and you diff --git a/reference/forms/types/options/button_attr.rst.inc b/reference/forms/types/options/button_attr.rst.inc index 39a2f6c35f4..fe1d7fde82b 100644 --- a/reference/forms/types/options/button_attr.rst.inc +++ b/reference/forms/types/options/button_attr.rst.inc @@ -10,6 +10,3 @@ as a key. This can be useful when you need to set a custom class for the button: $builder->add('save', 'button', array( 'attr' => array('class' => 'save'), )); - - - diff --git a/reference/forms/types/options/by_reference.rst.inc b/reference/forms/types/options/by_reference.rst.inc index 8f7fffab0fe..3e7987addb9 100644 --- a/reference/forms/types/options/by_reference.rst.inc +++ b/reference/forms/types/options/by_reference.rst.inc @@ -42,4 +42,4 @@ call the setter on the parent object. Similarly, if you're using the :doc:`collection` form type where your underlying collection data is an object (like with Doctrine's ``ArrayCollection``), then ``by_reference`` must be set to ``false`` if you -need the setter (e.g. ``setAuthors()``) to be called. +need the adder and remover (e.g. ``addAuthor()`` and ``removeAuthor()``) to be called. diff --git a/reference/forms/types/options/cascade_validation.rst.inc b/reference/forms/types/options/cascade_validation.rst.inc index f110e9087b2..c41b605f9f6 100644 --- a/reference/forms/types/options/cascade_validation.rst.inc +++ b/reference/forms/types/options/cascade_validation.rst.inc @@ -8,8 +8,12 @@ For example, if you have a ``ProductType`` with an embedded ``CategoryType``, setting ``cascade_validation`` to ``true`` on ``ProductType`` will cause the data from ``CategoryType`` to also be validated. -Instead of using this option, you can also use the ``Valid`` constraint in -your model to force validation on a child object stored on a property. +.. tip:: -.. include:: /reference/forms/types/options/_error_bubbling_hint.rst.inc + Instead of using this option, it is recommended that you use the ``Valid`` + constraint in your model to force validation on a child object stored on + a property. This cascades only the validation but not the use of the + ``validation_group`` option on child forms. You can read more about this + in the section about :ref:`Embedding a Single Object `. +.. include:: /reference/forms/types/options/_error_bubbling_hint.rst.inc diff --git a/reference/forms/types/options/checkbox_compound.rst.inc b/reference/forms/types/options/checkbox_compound.rst.inc index a97204c5cf8..bf328936647 100644 --- a/reference/forms/types/options/checkbox_compound.rst.inc +++ b/reference/forms/types/options/checkbox_compound.rst.inc @@ -3,6 +3,5 @@ compound **type**: ``boolean`` **default**: ``false`` -This option specifies if a form is compound. As it's not the -case for checkbox, by default the value is overridden with the -``false`` value. +This option specifies if a form is compound. As it's not the case for checkbox, +by default the value is overridden with the ``false`` value. diff --git a/reference/forms/types/options/checkbox_empty_data.rst.inc b/reference/forms/types/options/checkbox_empty_data.rst.inc index 9e3c2e79427..62bd00c58d0 100644 --- a/reference/forms/types/options/checkbox_empty_data.rst.inc +++ b/reference/forms/types/options/checkbox_empty_data.rst.inc @@ -3,6 +3,6 @@ empty_data **type**: ``string`` **default**: ``mixed`` -This option determines what value the field will return when the ``empty_value`` +This option determines what value the field will return when the ``placeholder`` choice is selected. In the checkbox and the radio type, the value of ``empty_data`` is overriden by the value returned by the data transformer (see :doc:`/cookbook/form/data_transformers`). diff --git a/reference/forms/types/options/data.rst.inc b/reference/forms/types/options/data.rst.inc index ee92e82a2d3..9873740cf48 100644 --- a/reference/forms/types/options/data.rst.inc +++ b/reference/forms/types/options/data.rst.inc @@ -14,6 +14,6 @@ an individual field, you can set it in the data option:: .. note:: - The default values for form fields are taken directly from the - underlying data structure (e.g. an entity or an array). + The default values for form fields are taken directly from the + underlying data structure (e.g. an entity or an array). The ``data`` option overrides this default value. diff --git a/reference/forms/types/options/date_format.rst.inc b/reference/forms/types/options/date_format.rst.inc index d8a6c370825..b6db52d68dd 100644 --- a/reference/forms/types/options/date_format.rst.inc +++ b/reference/forms/types/options/date_format.rst.inc @@ -1,7 +1,8 @@ format ~~~~~~ -**type**: ``integer`` or ``string`` **default**: `IntlDateFormatter::MEDIUM`_ (or ``yyyy-MM-dd`` if `widget`_ is ``single_text``) +**type**: ``integer`` or ``string`` **default**: `IntlDateFormatter::MEDIUM`_ +(or ``yyyy-MM-dd`` if `widget`_ is ``single_text``) Option passed to the ``IntlDateFormatter`` class, used to transform user input into the proper format. This is critical when the `widget`_ option is diff --git a/reference/forms/types/options/date_input.rst.inc b/reference/forms/types/options/date_input.rst.inc index 89aa73bc432..43f50abbd9d 100644 --- a/reference/forms/types/options/date_input.rst.inc +++ b/reference/forms/types/options/date_input.rst.inc @@ -14,4 +14,4 @@ your underlying object. Valid values are: The value that comes back from the form will also be normalized back into this format. -.. include:: /reference/forms/types/options/_date_limitation.rst.inc \ No newline at end of file +.. include:: /reference/forms/types/options/_date_limitation.rst.inc diff --git a/reference/forms/types/options/disabled.rst.inc b/reference/forms/types/options/disabled.rst.inc index 130b7d7f6b3..151711a82c6 100644 --- a/reference/forms/types/options/disabled.rst.inc +++ b/reference/forms/types/options/disabled.rst.inc @@ -3,6 +3,5 @@ disabled **type**: ``boolean`` **default**: ``false`` -If you don't want a user to modify the value of a field, you can set -the disabled option to true. Any submitted value will be ignored. - +If you don't want a user to modify the value of a field, you can set the disabled +option to true. Any submitted value will be ignored. diff --git a/reference/forms/types/options/empty_data.rst.inc b/reference/forms/types/options/empty_data.rst.inc index 34ca9cb878c..3d5e3a21e85 100644 --- a/reference/forms/types/options/empty_data.rst.inc +++ b/reference/forms/types/options/empty_data.rst.inc @@ -23,7 +23,7 @@ selected, you can do it like this: 'f' => 'Female' ), 'required' => false, - 'empty_value' => 'Choose your gender', + 'placeholder' => 'Choose your gender', 'empty_data' => null )); diff --git a/reference/forms/types/options/error_bubbling.rst.inc b/reference/forms/types/options/error_bubbling.rst.inc index 21a53a887fa..dbe42833b17 100644 --- a/reference/forms/types/options/error_bubbling.rst.inc +++ b/reference/forms/types/options/error_bubbling.rst.inc @@ -3,4 +3,4 @@ error_bubbling **type**: ``Boolean`` **default**: ``false`` unless the form is ``compound`` -.. include:: /reference/forms/types/options/_error_bubbling_body.rst.inc \ No newline at end of file +.. include:: /reference/forms/types/options/_error_bubbling_body.rst.inc diff --git a/reference/forms/types/options/expanded.rst.inc b/reference/forms/types/options/expanded.rst.inc index 2543527300c..e41c28a5da4 100644 --- a/reference/forms/types/options/expanded.rst.inc +++ b/reference/forms/types/options/expanded.rst.inc @@ -4,4 +4,4 @@ expanded **type**: ``Boolean`` **default**: ``false`` If set to true, radio buttons or checkboxes will be rendered (depending -on the ``multiple`` value). If false, a select element will be rendered. \ No newline at end of file +on the ``multiple`` value). If false, a select element will be rendered. diff --git a/reference/forms/types/options/grouping.rst.inc b/reference/forms/types/options/grouping.rst.inc index e2e77178d5d..39aeddb6b2b 100644 --- a/reference/forms/types/options/grouping.rst.inc +++ b/reference/forms/types/options/grouping.rst.inc @@ -3,4 +3,8 @@ grouping **type**: ``integer`` **default**: ``false`` -This value is used internally as the ``NumberFormatter::GROUPING_USED`` value when using PHP's ``NumberFormatter`` class. Its documentation is non-existent, but it appears that if you set this to ``true``, numbers will be grouped with a comma or period (depending on your locale): ``12345.123`` would display as ``12,345.123``. \ No newline at end of file +This value is used internally as the ``NumberFormatter::GROUPING_USED`` value +when using PHP's ``NumberFormatter`` class. Its documentation is non-existent, +but it appears that if you set this to ``true``, numbers will be grouped with +a comma or period (depending on your locale): ``12345.123`` would display +as ``12,345.123``. diff --git a/reference/forms/types/options/hours.rst.inc b/reference/forms/types/options/hours.rst.inc index 51097ca25dc..2af89a163f6 100644 --- a/reference/forms/types/options/hours.rst.inc +++ b/reference/forms/types/options/hours.rst.inc @@ -4,4 +4,4 @@ hours **type**: ``array`` **default**: 0 to 23 List of hours available to the hours field type. This option is only relevant -when the ``widget`` option is set to ``choice``. \ No newline at end of file +when the ``widget`` option is set to ``choice``. diff --git a/reference/forms/types/options/html5.rst.inc b/reference/forms/types/options/html5.rst.inc new file mode 100644 index 00000000000..1022412f3b3 --- /dev/null +++ b/reference/forms/types/options/html5.rst.inc @@ -0,0 +1,13 @@ +html5 +~~~~~ + +.. versionadded:: 2.6 + The ``html5`` option was introduced in Symfony 2.6. + +**type**: ``boolean`` **default**: ``true`` + +If this is set to ``true`` (the default), it'll use the HTML5 type (date, time +or datetime) to render the field. When set to ``false``, it'll use the text type. + +This is useful when you want to use a custom JavaScript datapicker, which +often requires a text type instead of an HTML5 type. diff --git a/reference/forms/types/options/invalid_message.rst.inc b/reference/forms/types/options/invalid_message.rst.inc index 3fbfbe276e8..c5c05432492 100644 --- a/reference/forms/types/options/invalid_message.rst.inc +++ b/reference/forms/types/options/invalid_message.rst.inc @@ -13,4 +13,4 @@ number field. Normal (business logic) validation (such as when setting a minimum length for a field) should be set using validation messages with your validation rules -(:ref:`reference`). \ No newline at end of file +(:ref:`reference`). diff --git a/reference/forms/types/options/invalid_message_parameters.rst.inc b/reference/forms/types/options/invalid_message_parameters.rst.inc index 71ae5bee601..286fbc45cb6 100644 --- a/reference/forms/types/options/invalid_message_parameters.rst.inc +++ b/reference/forms/types/options/invalid_message_parameters.rst.inc @@ -11,4 +11,4 @@ to that option and including the variables in this option:: // ... 'invalid_message' => 'You entered an invalid value - it should include %num% letters', 'invalid_message_parameters' => array('%num%' => 6), - )); \ No newline at end of file + )); diff --git a/reference/forms/types/options/max_length.rst.inc b/reference/forms/types/options/max_length.rst.inc index d20d44e1199..c108451bed0 100644 --- a/reference/forms/types/options/max_length.rst.inc +++ b/reference/forms/types/options/max_length.rst.inc @@ -1,10 +1,17 @@ max_length ~~~~~~~~~~ +.. caution:: + + The ``max_length`` option was deprecated in Symfony 2.5 and will be removed + in Symfony 3.0. Use the ``attr`` option instead by setting it to an array + with a ``maxlength`` key. + **type**: ``integer`` **default**: ``null`` -If this option is not null, an attribute ``maxlength`` is added, which -is used by some browsers to limit the amount of text in a field. +If this option is not null, an attribute ``maxlength`` is added, which +is used by some browsers to limit the amount of text in a field. -This is just a browser validation, so data must still be validated +This is just a browser validation, so data must still be validated server-side. + diff --git a/reference/forms/types/options/model_timezone.rst.inc b/reference/forms/types/options/model_timezone.rst.inc index 4b0c6d36280..f06489ef65f 100644 --- a/reference/forms/types/options/model_timezone.rst.inc +++ b/reference/forms/types/options/model_timezone.rst.inc @@ -6,4 +6,4 @@ model_timezone Timezone that the input data is stored in. This must be one of the `PHP supported timezones`_. -.. _`PHP supported timezones`: http://php.net/manual/en/timezones.php \ No newline at end of file +.. _`PHP supported timezones`: http://php.net/manual/en/timezones.php diff --git a/reference/forms/types/options/multiple.rst.inc b/reference/forms/types/options/multiple.rst.inc index 7de60eda538..f5a61f28012 100644 --- a/reference/forms/types/options/multiple.rst.inc +++ b/reference/forms/types/options/multiple.rst.inc @@ -6,4 +6,4 @@ multiple If true, the user will be able to select multiple options (as opposed to choosing just one option). Depending on the value of the ``expanded`` option, this will render either a select tag or checkboxes if true and -a select tag or radio buttons if false. The returned value will be an array. \ No newline at end of file +a select tag or radio buttons if false. The returned value will be an array. diff --git a/reference/forms/types/options/pattern.rst.inc b/reference/forms/types/options/pattern.rst.inc index 2bb115b4483..a47b8aeb2a1 100644 --- a/reference/forms/types/options/pattern.rst.inc +++ b/reference/forms/types/options/pattern.rst.inc @@ -1,6 +1,12 @@ pattern ~~~~~~~ +.. caution:: + + The ``pattern`` option was deprecated in Symfony 2.5 and will be removed + in Symfony 3.0. Use the ``attr`` option instead by setting it to an array + with a ``pattern`` key. + **type**: ``string`` **default**: ``null`` This adds an HTML5 ``pattern`` attribute to restrict the field input by a diff --git a/reference/forms/types/options/empty_value.rst.inc b/reference/forms/types/options/placeholder.rst.inc similarity index 73% rename from reference/forms/types/options/empty_value.rst.inc rename to reference/forms/types/options/placeholder.rst.inc index 19adfa5effe..143f8d60c12 100644 --- a/reference/forms/types/options/empty_value.rst.inc +++ b/reference/forms/types/options/placeholder.rst.inc @@ -1,6 +1,10 @@ -empty_value +placeholder ~~~~~~~~~~~ +.. versionadded:: 2.6 + The ``placeholder`` option was introduced in Symfony 2.6 in favor of + ``empty_value``, which is available prior to 2.6. + .. versionadded:: 2.3 Since Symfony 2.3, empty values are also supported if the ``expanded`` option is set to true. @@ -14,16 +18,16 @@ will appear at the top of a select widget. This option only applies if the * Add an empty value with "Choose an option" as the text:: $builder->add('states', 'choice', array( - 'empty_value' => 'Choose an option', + 'placeholder' => 'Choose an option', )); * Guarantee that no "empty" value option is displayed:: $builder->add('states', 'choice', array( - 'empty_value' => false, + 'placeholder' => false, )); -If you leave the ``empty_value`` option unset, then a blank (with no text) +If you leave the ``placeholder`` option unset, then a blank (with no text) option will automatically be added if and only if the ``required`` option is false:: diff --git a/reference/forms/types/options/preferred_choices.rst.inc b/reference/forms/types/options/preferred_choices.rst.inc index a195fd1a940..96f8268546c 100644 --- a/reference/forms/types/options/preferred_choices.rst.inc +++ b/reference/forms/types/options/preferred_choices.rst.inc @@ -20,9 +20,9 @@ This can be customized when rendering the field: .. configuration-block:: .. code-block:: jinja - + {{ form_widget(form.foo_choices, { 'separator': '=====' }) }} .. code-block:: php - + widget($form['foo_choices'], array('separator' => '=====')) ?> diff --git a/reference/forms/types/options/seconds.rst.inc b/reference/forms/types/options/seconds.rst.inc index 4e8d5c6c9cd..7acbf39025c 100644 --- a/reference/forms/types/options/seconds.rst.inc +++ b/reference/forms/types/options/seconds.rst.inc @@ -4,4 +4,4 @@ seconds **type**: ``array`` **default**: 0 to 59 List of seconds available to the seconds field type. This option is only -relevant when the ``widget`` option is set to ``choice``. \ No newline at end of file +relevant when the ``widget`` option is set to ``choice``. diff --git a/reference/forms/types/options/select_how_rendered.rst.inc b/reference/forms/types/options/select_how_rendered.rst.inc index cb785e18f54..b131971ffff 100644 --- a/reference/forms/types/options/select_how_rendered.rst.inc +++ b/reference/forms/types/options/select_how_rendered.rst.inc @@ -4,14 +4,11 @@ Select Tag, Checkboxes or Radio Buttons This field may be rendered as one of several different HTML fields, depending on the ``expanded`` and ``multiple`` options: -+------------------------------------------+----------+----------+ -| element type | expanded | multiple | -+==========================================+==========+==========+ -| select tag | false | false | -+------------------------------------------+----------+----------+ -| select tag (with ``multiple`` attribute) | false | true | -+------------------------------------------+----------+----------+ -| radio buttons | true | false | -+------------------------------------------+----------+----------+ -| checkboxes | true | true | -+------------------------------------------+----------+----------+ +======================================== ========= ========= +Element Type Expanded Multiple +======================================== ========= ========= +select tag ``false`` ``false`` +select tag (with ``multiple`` attribute) ``false`` ``true`` +radio buttons ``true`` ``false`` +checkboxes ``true`` ``true`` +======================================== ========= ========= diff --git a/reference/forms/types/options/trim.rst.inc b/reference/forms/types/options/trim.rst.inc index 1665e5d4333..68ba656be94 100644 --- a/reference/forms/types/options/trim.rst.inc +++ b/reference/forms/types/options/trim.rst.inc @@ -6,4 +6,4 @@ trim If true, the whitespace of the submitted string value will be stripped via the ``trim()`` function when the data is bound. This guarantees that if a value is submitted with extra whitespace, it will be removed before -the value is merged back onto the underlying object. \ No newline at end of file +the value is merged back onto the underlying object. diff --git a/reference/forms/types/options/view_timezone.rst.inc b/reference/forms/types/options/view_timezone.rst.inc index 5cffeeb5747..d2ab65adcbf 100644 --- a/reference/forms/types/options/view_timezone.rst.inc +++ b/reference/forms/types/options/view_timezone.rst.inc @@ -6,4 +6,4 @@ view_timezone Timezone for how the data should be shown to the user (and therefore also the data that the user submits). This must be one of the `PHP supported timezones`_. -.. _`PHP supported timezones`: http://php.net/manual/en/timezones.php \ No newline at end of file +.. _`PHP supported timezones`: http://php.net/manual/en/timezones.php diff --git a/reference/forms/types/options/with_minutes.rst.inc b/reference/forms/types/options/with_minutes.rst.inc index 48e6cdddcaf..60ad9b94515 100644 --- a/reference/forms/types/options/with_minutes.rst.inc +++ b/reference/forms/types/options/with_minutes.rst.inc @@ -1,9 +1,6 @@ with_minutes ~~~~~~~~~~~~ -.. versionadded:: 2.2 - The ``with_minutes`` option was introduced in Symfony 2.2. - **type**: ``Boolean`` **default**: ``true`` Whether or not to include minutes in the input. This will result in an additional diff --git a/reference/forms/types/options/with_seconds.rst.inc b/reference/forms/types/options/with_seconds.rst.inc index 684a748fb3e..cc9b0b12105 100644 --- a/reference/forms/types/options/with_seconds.rst.inc +++ b/reference/forms/types/options/with_seconds.rst.inc @@ -4,4 +4,4 @@ with_seconds **type**: ``Boolean`` **default**: ``false`` Whether or not to include seconds in the input. This will result in an additional -input to capture seconds. \ No newline at end of file +input to capture seconds. diff --git a/reference/forms/types/options/years.rst.inc b/reference/forms/types/options/years.rst.inc index 8a6e73a83a4..3c4655697f6 100644 --- a/reference/forms/types/options/years.rst.inc +++ b/reference/forms/types/options/years.rst.inc @@ -1,7 +1,8 @@ years ~~~~~ -**type**: ``array`` **default**: five years before to five years after the current year +**type**: ``array`` **default**: five years before to five years after the +current year List of years available to the year field type. This option is only relevant when the ``widget`` option is set to ``choice``. diff --git a/reference/forms/types/password.rst b/reference/forms/types/password.rst index eefe8eb7c31..a7d605883ad 100644 --- a/reference/forms/types/password.rst +++ b/reference/forms/types/password.rst @@ -18,7 +18,7 @@ The ``password`` field renders an input password text box. | | - `label`_ | | | - `label_attr`_ | | | - `mapped`_ | -| | - `max_length`_ | +| | - `max_length`_ (deprecated as of 2.5) | | | - `read_only`_ | | | - `required`_ | | | - `trim`_ | diff --git a/reference/forms/types/search.rst b/reference/forms/types/search.rst index 955c52cadaf..c9c9890d490 100644 --- a/reference/forms/types/search.rst +++ b/reference/forms/types/search.rst @@ -19,7 +19,7 @@ Read about the input search field at `DiveIntoHTML5.info`_ | | - `label`_ | | | - `label_attr`_ | | | - `mapped`_ | -| | - `max_length`_ | +| | - `max_length`_ (deprecated as of 2.5) | | | - `read_only`_ | | | - `required`_ | | | - `trim`_ | diff --git a/reference/forms/types/submit.rst b/reference/forms/types/submit.rst index 72faa779ae1..63385eb328c 100644 --- a/reference/forms/types/submit.rst +++ b/reference/forms/types/submit.rst @@ -17,6 +17,7 @@ A submit button. | | - `label`_ | | | - `label_attr`_ | | | - `translation_domain`_ | +| | - `validation_groups`_ | +----------------------+----------------------------------------------------------------------+ | Parent type | :doc:`button` | +----------------------+----------------------------------------------------------------------+ @@ -45,11 +46,38 @@ Inherited Options .. include:: /reference/forms/types/options/button_translation_domain.rst.inc +validation_groups +~~~~~~~~~~~~~~~~~ + +**type**: ``array`` **default**: ``null`` + +When your form contains multiple submit buttons, you can change the validation +group based on the button which was used to submit the form. Imagine a registration +form wizard with buttons to go to the previous or the next step:: + + $form = $this->createFormBuilder($user) + ->add('previousStep', 'submit', array( + 'validation_groups' => false, + )) + ->add('nextStep', 'submit', array( + 'validation_groups' => array('Registration'), + )) + ->getForm(); + +The special ``false`` ensures that no validation is performed when the previous +step button is clicked. When the second button is clicked, all constraints +from the "Registration" are validated. + +.. seealso:: + + You can read more about this in :ref:`the Form chapter ` + of the book. + Form Variables -------------- -======== =========== ============================================================== -Variable Type Usage -======== =========== ============================================================== -clicked ``Boolean`` Whether the button is clicked or not. -======== =========== ============================================================== +======== =========== ============================================================== +Variable Type Usage +======== =========== ============================================================== +clicked ``Boolean`` Whether the button is clicked or not. +======== =========== ============================================================== diff --git a/reference/forms/types/text.rst b/reference/forms/types/text.rst index f9fbb152c22..5fb4f07ae34 100644 --- a/reference/forms/types/text.rst +++ b/reference/forms/types/text.rst @@ -17,7 +17,7 @@ The text field represents the most basic input text field. | | - `label`_ | | | - `label_attr`_ | | | - `mapped`_ | -| | - `max_length`_ | +| | - `max_length`_ (deprecated as of 2.5) | | | - `read_only`_ | | | - `required`_ | | | - `trim`_ | diff --git a/reference/forms/types/textarea.rst b/reference/forms/types/textarea.rst index 1352d9e2e6f..a75ffbb0f27 100644 --- a/reference/forms/types/textarea.rst +++ b/reference/forms/types/textarea.rst @@ -18,7 +18,7 @@ Renders a ``textarea`` HTML element. | | - `label`_ | | | - `label_attr`_ | | | - `mapped`_ | -| | - `max_length`_ | +| | - `max_length`_ (deprecated as of 2.5) | | | - `read_only`_ | | | - `required`_ | | | - `trim`_ | diff --git a/reference/forms/types/time.rst b/reference/forms/types/time.rst index 71b3af1cdb5..ae95ab4ae94 100644 --- a/reference/forms/types/time.rst +++ b/reference/forms/types/time.rst @@ -15,8 +15,9 @@ as a ``DateTime`` object, a string, a timestamp or an array. +----------------------+-----------------------------------------------------------------------------+ | Rendered as | can be various tags (see below) | +----------------------+-----------------------------------------------------------------------------+ -| Options | - `empty_value`_ | +| Options | - `placeholder`_ | | | - `hours`_ | +| | - `html5`_ | | | - `input`_ | | | - `minutes`_ | | | - `model_timezone`_ | @@ -77,10 +78,12 @@ values. Field Options ------------- -.. include:: /reference/forms/types/options/empty_value.rst.inc +.. include:: /reference/forms/types/options/placeholder.rst.inc .. include:: /reference/forms/types/options/hours.rst.inc +.. include:: /reference/forms/types/options/html5.rst.inc + input ~~~~~ diff --git a/reference/forms/types/timezone.rst b/reference/forms/types/timezone.rst index 175345b0330..e7e99266c55 100644 --- a/reference/forms/types/timezone.rst +++ b/reference/forms/types/timezone.rst @@ -23,7 +23,7 @@ you should just use the ``choice`` type directly. +-------------+------------------------------------------------------------------------+ | Inherited | from the :doc:`choice ` type | | options | | -| | - `empty_value`_ | +| | - `placeholder`_ | | | - `expanded`_ | | | - `multiple`_ | | | - `preferred_choices`_ | @@ -62,7 +62,7 @@ Inherited Options These options inherit from the :doc:`choice ` type: -.. include:: /reference/forms/types/options/empty_value.rst.inc +.. include:: /reference/forms/types/options/placeholder.rst.inc .. include:: /reference/forms/types/options/expanded.rst.inc diff --git a/reference/forms/types/url.rst b/reference/forms/types/url.rst index 1df99a06d30..ace61dd0588 100644 --- a/reference/forms/types/url.rst +++ b/reference/forms/types/url.rst @@ -21,7 +21,7 @@ have a protocol. | | - `label`_ | | | - `label_attr`_ | | | - `mapped`_ | -| | - `max_length`_ | +| | - `max_length`_ (deprecated as of 2.5) | | | - `read_only`_ | | | - `required`_ | | | - `trim`_ | diff --git a/reference/forms/types/variables/check_or_radio_table.rst.inc b/reference/forms/types/variables/check_or_radio_table.rst.inc index b6ef7527640..ae137a3f200 100644 --- a/reference/forms/types/variables/check_or_radio_table.rst.inc +++ b/reference/forms/types/variables/check_or_radio_table.rst.inc @@ -1,5 +1,5 @@ -======== ============ ============================================ -Variable Type Usage -======== ============ ============================================ -checked ``Boolean`` Whether or not the current input is checked. -======== ============ ============================================ +======== ============ ============================================ +Variable Type Usage +======== ============ ============================================ +checked ``Boolean`` Whether or not the current input is checked. +======== ============ ============================================ diff --git a/reference/twig_reference.rst b/reference/twig_reference.rst index f70d7f5c6fc..e58eda4822a 100644 --- a/reference/twig_reference.rst +++ b/reference/twig_reference.rst @@ -7,183 +7,681 @@ Symfony Twig Extensions ======================= Twig is the default template engine for Symfony. By itself, it already contains -a lot of built-in functions, filters, tags and tests (`http://twig.sensiolabs.org/documentation`_ -then scroll to the bottom). +a lot of built-in functions, filters, tags and tests (learn more about them +from the `Twig Reference`_). -Symfony adds more custom extension on top of Twig to integrate some components -into the Twig templates. Below is information about all the custom functions, -filters, tags and tests that are added when using the Symfony Core Framework. +Symfony adds more custom extensions on top of Twig to integrate some components +into the Twig templates. You can find more information about the custom +:ref:`functions `, :ref:`filters `, +:ref:`tags ` and :ref:`tests ` +that are added when using the Symfony Core Framework. There may also be tags in bundles you use that aren't listed here. +.. _reference-twig-functions: + Functions --------- -.. versionadded:: 2.2 - The ``render`` and ``controller`` functions were introduced in Symfony - 2.2. Prior, the ``{% render %}`` tag was used and had a different signature. - -.. versionadded:: 2.4 - The ``expression`` function was introduced in Symfony 2.4. - -+----------------------------------------------------+--------------------------------------------------------------------------------------------+ -| Function Syntax | Usage | -+====================================================+============================================================================================+ -| ``render(uri, options = {})`` | This will render the fragment for the given controller or URL | -| ``render(controller('B:C:a', {params}))`` | For more information, see :ref:`templating-embedding-controller`. | -| ``render(path('route', {params}))`` | | -| ``render(url('route', {params}))`` | | -+----------------------------------------------------+--------------------------------------------------------------------------------------------+ -| ``render_esi(controller('B:C:a', {params}))`` | This will generate an ESI tag when possible or fallback to the ``render`` | -| ``render_esi(url('route', {params}))`` | behavior otherwise. For more information, see :ref:`templating-embedding-controller`. | -| ``render_esi(path('route', {params}))`` | | -+----------------------------------------------------+--------------------------------------------------------------------------------------------+ -| ``render_hinclude(controller(...))`` | This will generates an Hinclude tag for the given controller or URL. | -| ``render_hinclude(url('route', {params}))`` | For more information, see :ref:`templating-embedding-controller`. | -| ``render_hinclude(path('route', {params}))`` | | -+----------------------------------------------------+--------------------------------------------------------------------------------------------+ -| ``controller(attributes = {}, query = {})`` | Used along with the ``render`` tag to refer to the controller that you want to render. | -+----------------------------------------------------+--------------------------------------------------------------------------------------------+ -| ``asset(path, packageName = null)`` | Get the public path of the asset, more information in | -| | ":ref:`book-templating-assets`". | -+----------------------------------------------------+--------------------------------------------------------------------------------------------+ -| ``asset_version(packageName = null)`` | Get the current version of the package, more information in | -| | ":ref:`book-templating-assets`". | -+----------------------------------------------------+--------------------------------------------------------------------------------------------+ -| ``form(view, variables = {})`` | This will render the HTML of a complete form, more information in | -| | in :ref:`the Twig Form reference`. | -+----------------------------------------------------+--------------------------------------------------------------------------------------------+ -| ``form_start(view, variables = {})`` | This will render the HTML start tag of a form, more information in | -| | in :ref:`the Twig Form reference`. | -+----------------------------------------------------+--------------------------------------------------------------------------------------------+ -| ``form_end(view, variables = {})`` | This will render the HTML end tag of a form together with all fields that | -| | have not been rendered yet, more information | -| | in :ref:`the Twig Form reference`. | -+----------------------------------------------------+--------------------------------------------------------------------------------------------+ -| ``form_enctype(view)`` | This will render the required ``enctype="multipart/form-data"`` attribute | -| | if the form contains at least one file upload field, more information in | -| | in :ref:`the Twig Form reference `. | -+----------------------------------------------------+--------------------------------------------------------------------------------------------+ -| ``form_widget(view, variables = {})`` | This will render a complete form or a specific HTML widget of a field, | -| | more information in :ref:`the Twig Form reference `. | -+----------------------------------------------------+--------------------------------------------------------------------------------------------+ -| ``form_errors(view)`` | This will render any errors for the given field or the "global" errors, | -| | more information in :ref:`the Twig Form reference `. | -+----------------------------------------------------+--------------------------------------------------------------------------------------------+ -| ``form_label(view, label = null, variables = {})`` | This will render the label for the given field, more information in | -| | :ref:`the Twig Form reference `. | -+----------------------------------------------------+--------------------------------------------------------------------------------------------+ -| ``form_row(view, variables = {})`` | This will render the row (the field's label, errors and widget) of the given | -| | field, more information in :ref:`the Twig Form reference `. | -+----------------------------------------------------+--------------------------------------------------------------------------------------------+ -| ``form_rest(view, variables = {})`` | This will render all fields that have not yet been rendered, more | -| | information in :ref:`the Twig Form reference `. | -+----------------------------------------------------+--------------------------------------------------------------------------------------------+ -| ``csrf_token(intention)`` | This will render a CSRF token. Use this function if you want CSRF protection without | -| | creating a form | -+----------------------------------------------------+--------------------------------------------------------------------------------------------+ -| ``is_granted(role, object = null, field = null)`` | This will return ``true`` if the current user has the required role, more | -| | information in ":ref:`book-security-template`" | -+----------------------------------------------------+--------------------------------------------------------------------------------------------+ -| ``logout_path(key)`` | This will generate the relative logout URL for the given firewall | -+----------------------------------------------------+--------------------------------------------------------------------------------------------+ -| ``logout_url(key)`` | Equal to ``logout_path(...)`` but this will generate an absolute URL | -+----------------------------------------------------+--------------------------------------------------------------------------------------------+ -| ``path(name, parameters = {})`` | Get a relative URL for the given route, more information in | -| | ":ref:`book-templating-pages`". | -+----------------------------------------------------+--------------------------------------------------------------------------------------------+ -| ``url(name, parameters = {})`` | Equal to ``path(...)`` but it generates an absolute URL | -+----------------------------------------------------+--------------------------------------------------------------------------------------------+ -| ``expression(expression)`` | Creates an :class:`Symfony\\Component\\ExpressionLanguage\\Expression` in Twig. See | -| | ":ref:`Template Expressions `". | -+----------------------------------------------------+--------------------------------------------------------------------------------------------+ +.. _reference-twig-function-render: + +render +~~~~~~ + +.. code-block:: jinja + + {{ render(uri, options) }} + +``uri`` + **type**: ``string`` | ``ControllerReference`` +``options`` + **type**: ``array`` **default**: ``[]`` + +Renders the fragment for the given controller (using the `controller`_ function) +or URI. For more information, see :ref:`templating-embedding-controller`. + +The render strategy can be specified in the ``strategy`` key of the options. + +.. tip:: + + The URI can be generated by other functions, like `path`_ and `url`_. + +render_esi +~~~~~~~~~~ + +.. code-block:: jinja + + {{ render_esi(uri, options) }} + +``uri`` + **type**: ``string`` | ``ControllerReference`` +``options`` + **type**: ``array`` **default**: ``[]`` + +Generates an ESI tag when possible or falls back to the behaviour of +`render`_ function instead. For more information, see +:ref:`templating-embedding-controller`. + +.. tip:: + + The URI can be generated by other functions, like `path`_ and `url`_. + +.. tip:: + + The ``render_esi()`` function is an example of the shortcut functions + of ``render``. It automatically sets the strategy based on what's given + in the function name, e.g. ``render_hinclude()`` will use the hinclude.js + strategy. This works for all ``render_*()`` functions. + +controller +~~~~~~~~~~ + +.. code-block:: jinja + + {{ controller(controller, attributes, query) }} + +``controller`` + **type**: ``string`` +``attributes`` + **type**: ``array`` **default**: ``[]`` +``query`` + **type**: ``array`` **default**: ``[]`` + +Returns an instance of ``ControllerReference`` to be used with functions like +:ref:`render() ` and `render_esi() `. + +asset +~~~~~ + +.. code-block:: jinja + + {{ asset(path, packageName, absolute = false, version = null) }} + +``path`` + **type**: ``string`` +``packageName`` + **type**: ``string``|``null`` **default**: ``null`` +``absolute`` + **type**: ``boolean`` **default**: ``false`` +``version`` + **type**: ``string`` **default** ``null`` + +Returns a public path to ``path``, which takes into account the base path set +for the package and the URL path. More information in +:ref:`book-templating-assets`. For asset versioning, see :ref:`ref-framework-assets-version`. + +asset_version +~~~~~~~~~~~~~ + +.. code-block:: jinja + + {{ asset_version(packageName) }} + +``packageName`` + **type**: ``string``|``null`` **default**: ``null`` + +Returns the current version of the package, more information in +:ref:`book-templating-assets`. + +form +~~~~ + +.. code-block:: jinja + + {{ form(view, variables) }} + +``view`` + **type**: ``FormView`` +``variables`` + **type**: ``array`` **default**: ``[]`` + +Renders the HTML of a complete form, more information in +:ref:`the Twig Form reference `. + +form_start +~~~~~~~~~~ + +.. code-block:: jinja + + {{ form_start(view, variables) }} + +``view`` + **type**: ``FormView`` +``variables`` + **type**: ``array`` **default**: ``[]`` + +Renders the HTML start tag of a form, more information in +:ref:`the Twig Form reference `. + +form_end +~~~~~~~~ + +.. code-block:: jinja + + {{ form_end(view, variables) }} + +``view`` + **type**: ``FormView`` +``variables`` + **type**: ``array`` **default**: ``[]`` + +Renders the HTML end tag of a form together with all fields that have not been +rendered yet, more information in :ref:`the Twig Form reference `. + +form_enctype +~~~~~~~~~~~~ + +.. code-block:: jinja + + {{ form_enctype(view) }} + +``view`` + **type**: ``FormView`` + +Renders the required ``enctype="multipart/form-data"`` attribute if the form +contains at least one file upload field, more information in +:ref:`the Twig Form reference `. + +form_widget +~~~~~~~~~~~ + +.. code-block:: jinja + + {{ form_widget(view, variables) }} + +``view`` + **type**: ``FormView`` +``variables`` + **type**: ``array`` **default**: ``[]`` + +Renders a complete form or a specific HTML widget of a field, more information +in :ref:`the Twig Form reference `. + +form_errors +~~~~~~~~~~~ + +.. code-block:: jinja + + {{ form_errors(view) }} + +``view`` + **type**: ``FormView`` + +Renders any errors for the given field or the global errors, more information +in :ref:`the Twig Form reference `. + +form_label +~~~~~~~~~~ + +.. code-block:: jinja + + {{ form_label(view, label, variables) }} + +``view`` + **type**: ``FormView`` +``label`` + **type**: ``string`` **default**: ``null`` +``variables`` + **type**: ``array`` **default**: ``[]`` + +Renders the label for the given field, mre information in +:ref:`the Twig Form reference `. + +form_row +~~~~~~~~ + +.. code-block:: jinja + + {{ form_row(view, variables) }} + +``view`` + **type**: ``FormView`` +``variables`` + **type**: ``array`` **default**: ``[]`` + +Renders the row (the field's label, errors and widget) of the given field, more +information in :ref:`the Twig Form reference `. + +form_rest +~~~~~~~~~ + +.. code-block:: jinja + + {{ form_rest(view, variables) }} + +``view`` + **type**: ``FormView`` +``variables`` + **type**: ``array`` **default**: ``[]`` + +Renders all fields that have not yet been rendered, more information in +:ref:`the Twig Form reference `. + +csrf_token +~~~~~~~~~~ + +.. code-block:: jinja + + {{ csrf_token(intention) }} + +``intention`` + **type**: ``string`` + +Renders a CSRF token. Use this function if you want CSRF protection without +creating a form. + +is_granted +~~~~~~~~~~ + +.. code-block:: jinja + + {{ is_granted(role, object, field) }} + +``role`` + **type**: ``string`` +``object`` + **type**: ``object`` +``field`` + **type**: ``string`` + +Returns ``true`` if the current user has the required role. Optionally, an +object can be pasted to be used by the voter. More information can be found in +:ref:`book-security-template`. + +.. note:: + + You can also pass in the field to use ACE for a specific field. Read more + about this in :ref:`cookbook-security-acl-field_scope`. + + +logout_path +~~~~~~~~~~~ + +.. code-block:: jinja + + {{ logout_path(key) }} + +``key`` + **type**: ``string`` + +Generates a relative logout URL for the given firewall. + +logout_url +~~~~~~~~~~ + +.. code-block:: jinja + + {{ logout_url(key) }} + +``key`` + **type**: ``string`` + +Equal to the `logout_path`_ function, but it'll generate an absolute URL +instead of a relative one. + +path +~~~~ + +.. code-block:: jinja + + {{ path(name, parameters, relative) }} + +``name`` + **type**: ``string`` +``parameters`` + **type**: ``array`` **default**: ``[]`` +``relative`` + **type**: ``boolean`` **default**: ``false`` + +Returns the relative URL (without the scheme and host) for the given route. If +``relative`` is enabled, it'll create a path relative to the current path. More +information in :ref:`book-templating-pages`. + +url +~~~ + +.. code-block:: jinja + + {{ url(name, parameters, schemeRelative) }} + +``name`` + **type**: ``string`` +``parameters`` + **type**: ``array`` **default**: ``[]`` +``schemeRelative`` + **type**: ``boolean`` **default**: ``false`` + +Returns the absolute URL (with scheme and host) for the given route. If +``schemeRelative`` is enabled, it'll create a scheme-relative URL. More +information in :ref:`book-templating-pages`. + +expression +~~~~~~~~~~ + +Creates an :class:`Symfony\\Component\\ExpressionLanguage\\Expression` in +Twig. See ":ref:`Template Expressions `". + +.. _reference-twig-filters: Filters ------- -+---------------------------------------------------------------------------------+-------------------------------------------------------------------+ -| Filter Syntax | Usage | -+=================================================================================+===================================================================+ -| ``text|humanize`` | Makes a technical name human readable (replaces underscores by | -| | spaces and capitalizes the string). | -+---------------------------------------------------------------------------------+-------------------------------------------------------------------+ -| ``text|trans(arguments = {}, domain = 'messages', locale = null)`` | This will translate the text into the current language, more | -| | information in | -| | :ref:`Translation Filters `. | -+---------------------------------------------------------------------------------+-------------------------------------------------------------------+ -| ``text|transchoice(count, arguments = {}, domain = 'messages', locale = null)`` | This will translate the text with pluralization, more information | -| | in :ref:`Translation Filters `. | -+---------------------------------------------------------------------------------+-------------------------------------------------------------------+ -| ``variable|yaml_encode(inline = 0)`` | This will transform the variable text into a YAML syntax. | -+---------------------------------------------------------------------------------+-------------------------------------------------------------------+ -| ``variable|yaml_dump`` | This will render a YAML syntax with their type. | -+---------------------------------------------------------------------------------+-------------------------------------------------------------------+ -| ``classname|abbr_class`` | This will render an ``abbr`` element with the short name of a | -| | PHP class. | -+---------------------------------------------------------------------------------+-------------------------------------------------------------------+ -| ``methodname|abbr_method`` | This will render a PHP method inside a ``abbr`` element | -| | (e.g. ``Symfony\Component\HttpFoundation\Response::getContent`` | -+---------------------------------------------------------------------------------+-------------------------------------------------------------------+ -| ``arguments|format_args`` | This will render a string with the arguments of a function and | -| | their types. | -+---------------------------------------------------------------------------------+-------------------------------------------------------------------+ -| ``arguments|format_args_as_text`` | Equal to ``[...]|format_args``, but it strips the tags. | -+---------------------------------------------------------------------------------+-------------------------------------------------------------------+ -| ``path|file_excerpt(line)`` | This will render an excerpt of a code file around the given line. | -+---------------------------------------------------------------------------------+-------------------------------------------------------------------+ -| ``path|format_file(line, text = null)`` | This will render a file path in a link. | -+---------------------------------------------------------------------------------+-------------------------------------------------------------------+ -| ``exceptionMessage|format_file_from_text`` | Equal to ``format_file`` except it parsed the default PHP error | -| | string into a file path (i.e. 'in foo.php on line 45') | -+---------------------------------------------------------------------------------+-------------------------------------------------------------------+ -| ``path|file_link(line)`` | This will render a path to the correct file (and line number) | -+---------------------------------------------------------------------------------+-------------------------------------------------------------------+ +humanize +~~~~~~~~ + +.. code-block:: jinja + + {{ text|humanize }} + +``text`` + **type**: ``string`` + +Makes a technical name human readable (i.e. replaces underscores by spaces and +capitalizes the string). + +trans +~~~~~ + +.. code-block:: jinja + + {{ message|trans(arguments, domain, locale) }} + +``message`` + **type**: ``string`` +``arguments`` + **type**: ``array`` **default**: ``[]`` +``domain`` + **type**: ``string`` **default**: ``null`` +``locale`` + **type**: ``string`` **default**: ``null`` + +Translates the text into the current language. More information in +:ref:`Translation Filters `. + +transchoice +~~~~~~~~~~~ + +.. code-block:: jinja + + {{ message|transchoice(count, arguments, domain, locale) }} + +``message`` + **type**: ``string`` +``count`` + **type**: ``integer`` +``arguments`` + **type**: ``array`` **default**: ``[]`` +``domain`` + **type**: ``string`` **default**: ``null`` +``locale`` + **type**: ``string`` **default**: ``null`` + +Translates the text with pluralization support. More information in +:ref:`Translation Filters `. + +yaml_encode +~~~~~~~~~~~ + +.. code-block:: jinja + + {{ input|yaml_encode(inline, dumpObjects) }} + +``input`` + **type**: ``mixed`` +``inline`` + **type**: ``integer`` **default**: ``0`` +``dumpObjects`` + **type**: ``boolean`` **default**: ``false`` + +Transforms the input into YAML syntax. See :ref:`components-yaml-dump` for more +information. + +yaml_dump +~~~~~~~~~ + +.. code-block:: jinja + + {{ value|yaml_dump(inline, dumpObjects) }} + +``value`` + **type**: ``mixed`` +``inline`` + **type**: ``integer`` **default**: ``0`` +``dumpObjects`` + **type**: ``boolean`` **default**: ``false`` + +Does the same as `yaml_encode() `_, but includes the type in the output. + +abbr_class +~~~~~~~~~~ + +.. code-block:: jinja + + {{ class|abbr_class }} + +``class`` + **type**: ``string`` + +Generates an ```` element with the short name of a PHP class (the FQCN +will be shown in a tooltip when a user hovers over de element). + +abbr_method +~~~~~~~~~~~ + +.. code-block:: jinja + + {{ method|abbr_method }} + +``method`` + **type**: ``string`` + +Generates an ```` element using the ``FQCN::method()`` syntax. If ``method`` +is ``Closure``, ``Closure`` will be used instead and if ``method`` doesn't have a +class name, it's shown as a function (``method()``). + +format_args +~~~~~~~~~~~ + +.. code-block:: jinja + + {{ args|format_args }} + +``args`` + **type**: ``array`` + +Generates a string with the arguments and their types (within ```` elements). + +format_args_as_text +~~~~~~~~~~~~~~~~~~~ + +.. code-block:: jinja + + {{ args|format_args_as_text }} + +``args`` + **type**: ``array`` + +Equal to the `format_args`_ filter, but without using tags. + +file_excerpt +~~~~~~~~~~~~ + +.. code-block:: jinja + + {{ file|file_excerpt(line) }} + +``file`` + **type**: ``string`` +``line`` + **type**: ``integer`` + +Generates an excerpt of 7 lines around the given ``line``. + +format_file +~~~~~~~~~~~ + +.. code-block:: jinja + + {{ file|format_file(line, text) }} + +``file`` + **type**: ``string`` +``line`` + **type**: ``integer`` +``text`` + **type**: ``string`` **default**: ``null`` + +Generates the file path inside an ```` element. If the path is inside the +kernel root directory, the kernel root directory path is replaced by +``kernel.root_dir`` (showing the full path in a tooltip on hover). + +format_file_from_text +~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: jinja + + {{ text|format_file_from_text }} + +``text`` + **type**: ``string`` + +Uses `|format_file ` to improve the output of default PHP errors. + +file_link +~~~~~~~~~ + +.. code-block:: jinja + + {{ file|file_link(line) }} + +``line`` + **type**: ``integer`` + +Generates a link to the provided file (and optionally line number) using a +preconfigured scheme. + +.. _reference-twig-tags: Tags ---- -.. versionadded:: 2.4 - The stopwatch tag was introduced in Symfony 2.4. - -+---------------------------------------------------+--------------------------------------------------------------------+ -| Tag Syntax | Usage | -+===================================================+====================================================================+ -| ``{% form_theme form 'file' %}`` | This will look inside the given file for overridden form blocks, | -| | more information in :doc:`/cookbook/form/form_customization`. | -+---------------------------------------------------+--------------------------------------------------------------------+ -| ``{% trans with {variables} %}...{% endtrans %}`` | This will translate and render the text, more information in | -| | :ref:`book-translation-tags` | -+---------------------------------------------------+--------------------------------------------------------------------+ -| ``{% transchoice count with {variables} %}`` | This will translate and render the text with pluralization, more | -| ... | information in :ref:`book-translation-tags` | -| ``{% endtranschoice %}`` | | -+---------------------------------------------------+--------------------------------------------------------------------+ -| ``{% trans_default_domain language %}`` | This will set the default domain for message catalogues in the | -| | current template | -+---------------------------------------------------+--------------------------------------------------------------------+ -| ``{% stopwatch 'name' %}...{% endstopwatch %}`` | This will time the run time of the code inside it and put that on | -| | the timeline of the WebProfilerBundle. | -+---------------------------------------------------+--------------------------------------------------------------------+ +form_theme +~~~~~~~~~~ + +.. code-block:: jinja + + {% form_theme form resources %} + +``form`` + **type**: ``FormView`` +``resources`` + **type**: ``array``|``string`` + +Sets the resources to override the form theme for the given form view instance. +You can use ``_self`` as resources to set it to the current resource. More +information in :doc:`/cookbook/form/form_customization`. + +trans +~~~~~ + +.. code-block:: jinja + + {% trans with vars from domain into locale %}{% endtrans %} + +``vars`` + **type**: ``array`` **default**: ``[]`` +``domain`` + **type**: ``string`` **default**: ``string`` +``locale`` + **type**: ``string`` **default**: ``string`` + +Renders the translation of the content. More information in :ref:`book-translation-tags`. + +transchoice +~~~~~~~~~~~ + +.. code-block:: jinja + + {% transchoice count with vars from domain into locale %}{% endtranschoice %} + +``count`` + **type**: ``integer`` +``vars`` + **type**: ``array`` **default**: ``[]`` +``domain`` + **type**: ``string`` **default**: ``null`` +``locale`` + **type**: ``string`` **default**: ``null`` + +Renders the translation of the content with pluralization support, more +information in :ref:`book-translation-tags`. + +trans_default_domain +~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: jinja + + {% trans_default_domain domain %} + +``domain`` + **type**: ``string`` + +This will set the default domain in the current template. + +stopwatch +~~~~~~~~~ + +.. code-block:: jinja + + {% stopwatch 'name' %}...{% endstopwatch %} + +This will time the run time of the code inside it and put that on the timeline +of the WebProfilerBundle. + +.. _reference-twig-tests: Tests ----- -+---------------------------------------------------+------------------------------------------------------------------------------+ -| Test Syntax | Usage | -+===================================================+==============================================================================+ -| ``selectedchoice(choice, selectedValue)`` | This will return ``true`` if the choice is selected for the given form value | -+---------------------------------------------------+------------------------------------------------------------------------------+ +selectedchoice +~~~~~~~~~~~~~~ + +.. code-block:: jinja + + {% if choice is selectedchoice(selectedValue) %} + +``choice`` + **type**: ``ChoiceView`` +``selectedValue`` + **type**: ``string`` + +Checks if ``selectedValue`` was checked for the provided choice field. Using +this test is the most effective way. Global Variables ---------------- -+-------------------------------------------------------+------------------------------------------------------------------------------------+ -| Variable | Usage | -+=======================================================+====================================================================================+ -| ``app`` *Attributes*: ``app.user``, ``app.request``, | The ``app`` variable is available everywhere, and gives you quick | -| ``app.session``, ``app.environment``, ``app.debug``, | access to many commonly needed objects. The ``app`` variable is | -| ``app.security`` | instance of :class:`Symfony\\Bundle\\FrameworkBundle\\Templating\\GlobalVariables` | -+-------------------------------------------------------+------------------------------------------------------------------------------------+ +.. _reference-twig-global-app: + +app +~~~ + +The ``app`` variable is available everywhere and gives access to many commonly +needed objects and values. It is an instance of +:class:`Symfony\\Bundle\\FrameworkBundle\\Templating\\GlobalVariables`. + +The available attributes are: + +* ``app.user`` +* ``app.request`` +* ``app.session`` +* ``app.environment`` +* ``app.debug`` +* ``app.security`` + +.. versionadded:: 2.6 + The ``app.security`` global is deprecated as of 2.6. The user is already available + as ``app.user`` and ``is_granted()`` is registered as function. Symfony Standard Edition Extensions ----------------------------------- @@ -197,5 +695,5 @@ Those bundles can have other Twig extensions: ``{% image %}`` tags. You can read more about them in :doc:`the Assetic Documentation `. +.. _`Twig Reference`: http://twig.sensiolabs.org/documentation#reference .. _`the official Twig Extensions documentation`: http://twig.sensiolabs.org/doc/extensions/index.html -.. _`http://twig.sensiolabs.org/documentation`: http://twig.sensiolabs.org/documentation