WIP - 2/3 parts of the quick tour finished

This commit is contained in:
Ryan Weaver
2017-12-15 23:22:57 -05:00
parent 13d112be53
commit b0d46e5eca
11 changed files with 384 additions and 1163 deletions

View File

@@ -381,3 +381,5 @@
/testing/simulating_authentication /testing/http_authentication
/validation/group_service_resolver /form/validation_group_service_resolver
/request/load_balancer_reverse_proxy /deployment/proxies
/quick_tour/the_controller /quick_tour/the_big_picture
/quick_tour/the_view /quick_tour/flex_recipes

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

249
quick_tour/flex_recipes.rst Normal file
View File

@@ -0,0 +1,249 @@
Flex: Compose your Application
==============================
After reading the first part of this tutorial, you have decided that Symfony was
worth another 10 minutes. Great choice! In this second part, you'll learn about
Symfony Flex: the amazing tool that makes adding new features as simple as running
one command. It's also the reason why Symfony is perfect for a small micro-service
or a huge application. Curious? Perfect!
Symfony: Start Micro!
---------------------
Unless you're building a pure API (more on that soon!), you'll probably want to
render HTML. To do that, you'll use `Twig`_. Twig is a flexible, fast, and secure
template engine for PHP. It makes your templates more readable and concise; it also
makes them more friendly for web designers.
Is Twig already installed in our application? Actually, not yet! And that's great!
When you start a new Symfony project, it's *small*: only the most critical dependencies
are included in your ``composer.json`` file:
.. code-block:: text
"require": {
"...",
"symfony/console": "^4.1",
"symfony/flex": "^1.0",
"symfony/framework-bundle": "^4.1",
"symfony/yaml": "^4.1"
}
This makes Symfony different than any other PHP framework! Instead of starting with
a *bulky* app with *every* possible feature you might ever need, a Symfony app is
small, simple and *fast*. And you're in total control of what you add.
Flex Recipes and Aliases
------------------------
So how can we install and configure Twig? Just run one command:
.. code-block:: terminal
$ composer require twig
Two *very* interesting things happen behind the scenes thanks to Symfony Flex: a
Composer plugin that is already installed in our project.
First, ``twig`` is not the name of a Composer package: it's a Flex *alias* that
points to ``symfony/twig-bundle``. Flex resolves that alias for Composer.
And second, Flex installs a *recipe* for ``symfony/twig-bundle``. What's a recipe?
It's a way for a library to automatically configure itself by adding and modifying
files. Thanks to recipes, adding features is seamless and automated: install a package
and you're done!
You an find a full list of recipes and aliases by going to `https://symfony.sh`_.
What did this recipe do? In addition to automatically enabling the bundle in
``config/bundles.php``, it added 3 things:
``config/packages/twig.yaml``
A configuration file that sets up Twig with sensible defaults.
``config/routes/dev/twig.yaml``
A route that helps you debug your error pages: it's added only in the ``dev``
environment.
``templates/``
This is the directory where template files will live. The recipe also added
a ``base.html.twig`` layout file.
Twig: Rendering a Template
--------------------------
Thanks to Flex, after one command, you can start using Twig immediately::
// src/Controller/DefaultController.php
// ...
+ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
-class DefaultController
+class DefaultController extends AbstractController
{
/**
* @Route("/hello/{name}")
*/
public function index($name)
{
- return new Response("Hello $name!");
+ return $this->render('default/index.html.twig', [
+ 'name' => $name,
+ ]);
}
By extending ``AbstractController``, you now have access to a number of shortcut
methods and tools, like ``render()``. Create the new template:
.. code-block:: twig
{# templates/default/index.html.twig #}
<h1>Hello {{ name }}</h1>
That's it! The ``{{ name }}`` syntax will print the ``name`` variable that's passed
in from the controller. If you're new to Twig, welcome! You'll learn more about
its syntax and power later.
But, right now, the page *only* contains the ``h1`` tag. To give it an HTML layout,
extend ``base.html.twig``:
.. code-block:: twig
{# templates/default/index.html.twig #}
{% extends 'base.html.twig' %}
{% block body %}
<h1>Hello {{ name }}</h1>
{% endblock %}
This is called template inheritance: our page now inherits the HTML structure from
``base.html.twig``.
Profiler: Debugging Paradise
----------------------------
One of the *coolest* features of Symfony isn't even installed yet! Let's fix that:
.. code-block:: terminal
$ composer require profiler
Yes! This is another alias! And Flex *also* installs another recipe, which automates
the configuration of Symfony's WebProfilerBundle. What's the result? Refresh!
See that black bar on the bottom? That's the web debug toolbar, and it's your new
best friend. By hovering over each icon, you can get information about what controller
was executed, performance information, cache hits & misses and a lot more. Click
any icon to go into the *profiler* where you have even *more* detailed debugging
and performance data!
Oh, and as you install more libraries, you'll get more tools (like a web debug toolbar
icon that shows database queries).
Using the profiler is easy because it configured *itself* thanks to the recipe.
What else can we install this easily?
Rich API Support
----------------
Are you building an API? You can already return JSON easily from any controller::
/**
* @Route("/api/hello/{name}")
*/
public function apiExample($name)
{
return $this->json([
'name' => $name,
'symfony' => 'rocks',
]);
}
But for a *truly* rich API, try installing `Api Platform`_:
.. code-block:: terminal
$ composer require api
This is an alias to ``api-platform/api-pack``, which has dependencies on several
other packages, like Symfony's Validator and Security components, as well as the Doctrine
ORM. In fact, Flex installed *5* recipes!
But like usual, we can immediately start using the new library. Want to create a
rich API for a ``product`` table? Create a ``Product`` entity and give it the
``@ApiResource()`` annotation::
// src/Entity/Product.php
// ...
use ApiPlatform\Core\Annotation\ApiResource;
/**
* @ORM\Entity()
* @ApiResource()
*/
class Product
{
/**
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\Column(type="string")
*/
private $name;
/**
* @ORM\Column(type="string")
*/
private $price;
// ...
}
Done! You now have endpoints to list, add, update and delete products! Don't believe
me? List your routes by running:
.. code-block:: terminal
$ php bin/console debug:router
.. code-block:: text
------------------------------ -------- -------------------------------------
Name Method Path
------------------------------ -------- -------------------------------------
api_products_get_collection GET /api/products.{_format}
api_products_post_collection POST /api/products.{_format}
api_products_get_item GET /api/products/{id}.{_format}
api_products_put_item PUT /api/products/{id}.{_format}
api_products_delete_item DELETE /api/products/{id}.{_format}
...
------------------------------ -------- -------------------------------------
Easily Remove Recipes
---------------------
Not convinced yet? No problem: remove the library:
.. code-block:: terminal
$ composer remove api
Flex will *uninstall* the recipes: removing files and un-doing changes to put your
app back in its original state. Experiment without worry.
More Feature, Architecture and Speed
------------------------------------
I hope you're as excited about Flex as I am! But we still have *one* more chapter,
and it's the most important yet. I want to show you how Symfony empowers you to quickly
build features *without* sacrificing code quality or performance. It's all about
the service container, and it's Symfony's super power.
.. _`https://symfony.sh`: https://symfony.sh
.. _`Api Platform`: https://api-platform.com/

View File

@@ -5,6 +5,5 @@ The Quick Tour
:maxdepth: 1
the_big_picture
the_view
the_controller
flex_recipes
the_architecture

View File

@@ -1,322 +1,4 @@
The Architecture
================
You are my hero! Who would have thought that you would still be here after
the first three parts? Your efforts will be well rewarded soon. The first
three parts didn't look too deeply at the architecture of the framework.
Because it makes Symfony stand apart from the framework crowd, let's dive
into the architecture now.
Understanding the Directory Structure
-------------------------------------
The directory structure of a Symfony application is rather flexible, but the
recommended structure is as follows:
``config/``
The application configuration
``templates/``
The application's views
``translations/``
The home of translation files
``bin/``
Executable files (e.g. ``bin/console``).
``src/``
The project's PHP code.
``tests/``
Automatic tests (e.g. Unit tests).
``var/``
Generated files (cache, logs, etc.).
``vendor/``
The third-party dependencies.
``public/``
The web root directory.
The ``public/`` 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 front controller (the
file that handles all requests to your application) lives, such as the
production controller shown here::
// public/index.php
require_once __DIR__.'/../var/bootstrap.php.cache';
require_once __DIR__.'/../src/Kernel.php';
use Symfony\Component\HttpFoundation\Request;
$kernel = new Kernel('prod', false);
$request = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();
The controller first bootstraps the application using a kernel class (``Kernel``
in this case). Then, it creates the ``Request`` object using the PHP's global
variables and passes it to the kernel. The last step is to send the response
contents returned by the kernel back to the user.
.. _the-app-dir:
The ``app/`` Directory
~~~~~~~~~~~~~~~~~~~~~~
The ``AppKernel`` class is the main entry point of the application
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, 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
are stored under the ``vendor/`` directory, but this is just a convention.
You can store them wherever you want, globally on your server or locally
in your projects.
Understanding the Bundle System
-------------------------------
This section introduces one of the greatest and most powerful features of
Symfony: The bundle system.
A bundle is kind of like a plugin in other software. So why is it
called a *bundle* and not a *plugin*? This is because *everything* is a
bundle in 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::
// src/Kernel.php
public function registerBundles()
{
$bundles = array(
new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
new Symfony\Bundle\SecurityBundle\SecurityBundle(),
new Symfony\Bundle\TwigBundle\TwigBundle(),
new Symfony\Bundle\MonologBundle\MonologBundle(),
new Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle(),
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 Symfony\Bundle\WebProfilerBundle\WebProfilerBundle();
$bundles[] = new Sensio\Bundle\DistributionBundle\SensioDistributionBundle();
$bundles[] = new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle();
}
return $bundles;
}
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 this sample of the default Symfony configuration:
.. code-block:: yaml
# app/config/config.yml
imports:
- { resource: parameters.yml }
- { resource: security.yml }
- { resource: services.yml }
framework:
#esi: ~
#translator: { fallbacks: ['%locale%'] }
secret: '%secret%'
router:
resource: '%kernel.project_dir%/app/config/routing.yml'
strict_requirements: '%kernel.debug%'
form: true
csrf_protection: true
validation: { enable_annotations: true }
templating: { engines: ['twig'] }
default_locale: '%locale%'
trusted_proxies: ~
session: ~
# Twig Configuration
twig:
debug: '%kernel.debug%'
strict_variables: '%kernel.debug%'
# Swift Mailer Configuration
swiftmailer:
transport: '%mailer_transport%'
host: '%mailer_host%'
username: '%mailer_user%'
password: '%mailer_password%'
spool: { type: memory }
# ...
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 environment can override the default configuration by providing a
specific configuration file. For example, the ``dev`` environment loads
the ``config_dev.yml`` file, which loads the main configuration (i.e.
``config.yml``) and then modifies it to add some debugging tools:
.. code-block:: yaml
# app/config/config_dev.yml
imports:
- { resource: config.yml }
framework:
router: { resource: '%kernel.project_dir%/app/config/routing_dev.yml' }
profiler: { only_exceptions: false }
web_profiler:
toolbar: true
intercept_redirects: false
# ...
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.
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
``@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 actions using the format
``BUNDLE_NAME:CONTROLLER_NAME:ACTION_NAME``. 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 </bundles/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
applications, store them locally or globally, your choice.
.. _using-vendors:
Using Vendors
-------------
Odds are that your application will depend on third-party libraries. Those
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 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 ``var/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:: terminal
$ php bin/console cache:clear --env=prod
When developing a web application, things can go wrong in many ways. The
log files in the ``var/log/`` directory tell you everything about the requests
and help you fix the problem quickly.
Using the Command Line Interface
--------------------------------
Each application comes with a command line interface tool (``bin/console``)
that helps you maintain your application. It provides commands that boost
your productivity by automating tedious and repetitive tasks.
Run it without any arguments to learn more about its capabilities:
.. code-block:: terminal
$ php bin/console
The ``--help`` option helps you discover the usage of a command:
.. code-block:: terminal
$ php bin/console debug:router --help
Final Thoughts
--------------
Call me crazy, but after reading this part, you should be comfortable with
moving things around and making Symfony work for you. Everything in Symfony
is designed to get out of your way. So, feel free to rename and move directories
around as you see fit.
And that's all for the quick tour. From testing to sending emails, you still
need to learn a lot to become a Symfony master. Ready to dig into these
topics now? Look no further - go to the official :doc:`/index` and
pick any topic you want.
.. _`Composer`: https://getcomposer.org
TODO

View File

@@ -1,267 +1,182 @@
The Big Picture
===============
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.
Start using Symfony in 10 minutes! Really! That's all you need to understand the
most important concepts and start building a real project!
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.
Symfony. If not, welcome to a whole new way of developing web applications. Symfony
*embraces* best practices, keeps backwards compatbility (Yes! Upgrading is always
safe & easy!) and offers long-term support.
.. _installing-symfony2:
Installing Symfony
------------------
Downloading Symfony
-------------------
Before continuing reading this chapter, make sure to have installed both PHP
and Symfony as explained in the :doc:`/setup` article.
First, make sure you've installed `Composer`_ and have PHP 7.1.3 or higher.
Understanding the Fundamentals
------------------------------
Ready? In a terminal, run:
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.
.. code-block:: terminal
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 ``Homepage`` HTML page).
$ composer create-project symfony/skeleton quick_tour
The code to execute is defined as methods of PHP classes. The methods are
called **actions** and the classes **controllers**, but in practice most
developers use **controllers** to refer to both of them. 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**.
This creates a new ``quick_tour/`` directory with, at first, just *one* file:
``composer.json``. But when the command finishes, you'll find a small, but
powerful Symfony application:
When you go to ``http://localhost:8000/app/example``, Symfony will execute
the controller in ``src/Controller/DefaultController.php`` and
render the ``templates/default/index.html.twig`` template.
.. code-block:: text
In the following sections you'll learn in detail the inner workings of Symfony
controllers, routes and templates.
symfony-project/
├─ .env
├─ .env.dist
├─ bin/console
├─ composer.json
├─ composer.lock
├─ config/
├─ public/index.php
├─ src/
├─ symfony.lock
├─ var/
└─ vendor/
Actions and Controllers
~~~~~~~~~~~~~~~~~~~~~~~
Where did these files come from? They came from recipes installed by Symfony Flex.
But don't worry about that yet: we'll talk about it soon.
Open the ``src/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)::
Can we already load the project in a browser? Of course! You can setup
:doc:`Nginx or Apache <setup/web_server_configuration>` and configure their document
root to be the ``public/`` directory. But, for development, Symfony has its own server.
Install it with:
.. code-block:: terminal
$ composer require server
$ php bin/console server:start
Try your new app by going to ``http://localhost:8000`` in a browser!
.. image:: /_images/quick_tour/no_routes_page.png
:align: center
:class: with-browser
Fundamentals: Route, Controller, Response
-----------------------------------------
Our project only has about 15 files, but it's ready to become an sleek API, a robust
web app, or a microservice. Symfony starts small, but scales with you.
But before we go too far, let's dig into the fundamentals by building our first page.
Start in ``config/routes.yaml``: this is where *we* can define the URL to our new
page. Uncomment the example that already lives in the file:
.. code-block:: yaml
index:
path: /
controller: 'App\Controller\DefaultController::index'
This is called a *route*: it defines the URL to your page (``/``) and the "controller":
the *function* that will be called whenever anyone goes to this URL. That function
doesn't exist yet, so let's create it!
In ``src/Controller``, create a new ``DefaultController`` class and an ``index``
method inside::
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Response;
class DefaultController extends Controller
class DefaultController
{
/**
* @Route("/", name="homepage")
*/
public function index()
{
return $this->render('default/index.html.twig', [
// ...
]);
return new Response('Hello!');
}
}
In Symfony applications, **controllers** are usually PHP classes whose names
are suffixed with the ``Controller`` word. In this example, the controller
is called ``DefaultController``.
That's it! Try going to the homepage: ``http://localhost:8000/``. Symfony sees
that the URL matches our route and then executes the new ``index()`` method.
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 ``DefaultController`` has only one
action called ``index()``.
A controller is just a normal function with *one* rule: it must return a Symfony
``Response`` object. But that response can contain anything: simple text, JSON or
a full HTML page.
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.
But the routing system is *much* more powerful. So let's make the route more interesting:
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 to welcome
you to Symfony.
.. code-block:: diff
Routing
~~~~~~~
# config/routes.yaml
index:
- path: /
+ path: /hello/{name}
controller: 'App\Controller\DefaultController::index'
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/Controller/DefaultController.php`` file and take a look
at the three lines of code above the ``index()`` method::
The URL to this page has changed: it is *now* ``/hello/*``: the ``{name}`` acts
like a wildcard that matches anything. And it gets better! Update the controller too:
.. code-block:: diff
// src/Controller/DefaultController.php
namespace App\Controller;
// ...
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Routing\Annotation\Route;
class DefaultController extends Controller
- public function index()
+ public function index($name)
{
/**
* @Route("/", name="homepage")
*/
public function index()
{
return $this->render('default/index.html.twig', [
// ...
]);
}
- return new Response('Hello!');
+ return new Response("Hello $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 ``/*``.
Try the page out by going to ``http://localhost:8000/hello/Symfony``. You should
see: Hello Symfony! The value of the ``{name}`` in the URL is available as a ``$name``
argument in your controller.
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.
But this can be even simpler! So let's install annotations support:
Considering all this, the ``@Route("/", name="homepage")`` annotation creates a
new route called ``homepage`` which makes Symfony execute the ``index`` action
of the ``DefaultController`` when the user browses the ``/`` path of the application.
.. code-block:: terminal
.. tip::
$ composer require annotations
In addition to PHP annotations, routes can be configured in YAML, XML
or PHP files, as explained in the :doc:`/routing` guide. This flexibility
is one of the main features of Symfony, a framework that never imposes a
particular configuration format on you.
Templates
~~~~~~~~~
The only content of the ``index`` action is this PHP instruction::
return $this->render('default/index.html.twig', [
// ...
]);
The ``$this->render()`` method is a convenient shortcut to render a template.
Symfony provides some useful shortcuts to any controller extending from
the base Symfony :class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller`
class.
By default, application templates are stored in the ``templates/``
directory. Therefore, the ``default/index.html.twig`` template corresponds
to the ``templates/default/index.html.twig``. Open that file and
you'll see the following code:
.. code-block:: html+twig
{# templates/default/index.html.twig #}
{% extends 'base.html.twig' %}
{% block body %}
<h1>Welcome to Symfony</h1>
{# ... #}
{% endblock %}
This template is created with `Twig`_, a template engine created for modern PHP
applications. The :doc:`second part of this tutorial </quick_tour/the_view>`
explains how templates work in Symfony.
.. _quick-tour-big-picture-environments:
Working with Environments
-------------------------
Now that you have a better understanding of how Symfony works, take a closer
look at the bottom of any Symfony rendered page. If you've installed the profiler
with ``composer require profiler``, you should notice a small bar with the Symfony
logo. This is the "web debug toolbar" and it is a Symfony developer's best friend!
.. image:: /_images/quick_tour/web_debug_toolbar.png
:align: center
:class: with-browser
But what you see initially is only the tip of the iceberg; click on any
of the bar sections to open the profiler and get much more detailed information
about the request, the query parameters, security details and database queries:
.. image:: /_images/quick_tour/profiler.png
:align: center
:class: with-browser
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:
What is an Environment?
~~~~~~~~~~~~~~~~~~~~~~~
An environment represents a group of configurations that's used to run 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).
The environment is determined by the ``APP_ENV`` environment variable, which is
set in the ``.env`` file while developing. By default, this value is set to ``dev``.
This means that, right now, hen 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, open your ``.env`` file and
set ``APP_ENV`` to ``prod`` and ``APP_DEBUG`` to ``0``.
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 any configuration files from the ``config/packages/dev`` directory. When you
access the ``prod`` environment, Symfony loads files from the ``config/packages/prod``
directory.
Typically, the environments share a large amount of configuration options.
For that reason, any configuration files stored directly in ``config/packages``
are loaded in *all* environments. Then, configuration files in the ``dev/`` sub-directory
can *override* that config.
Now, comment-out the YAML route by adding the ``#`` character:
.. code-block:: yaml
# config/packages/routing.yaml
framework:
router:
strict_requirements: ~
# config/routes.yaml
# index:
# path: /hello/{name}
# controller: 'App\Controller\DefaultController::index'
.. code-block:: yaml
Instead, add the route *right above* the controller method:
# config/packages/dev/routing.yaml
framework:
router:
strict_requirements: true
.. code-block:: diff
In this example, the ``strict_requirements`` is overridden and set to ``true``
*only* in the ``dev`` environment.
// src/Controller/DefaultController.php
// ...
+ use Symfony\Component\Routing\Annotation\Route;
+ /**
+ * @Route("/hello/{name}")
+ */
public function index($name)
This works just like before! But by using annotations, the route and controller
live right next to each other. Need another page? Just add another route and method
in ``DefaultController``::
For more details on environments, see
:ref:`the "Environments" section <page-creation-environments>` of the
Configuration guide.
// src/Controller/DefaultController.php
// ...
/**
* @Route("/simplicity")
*/
public function simple()
{
return new Response('Simple! Easy! Great!');
}
Final Thoughts
--------------
Routing can do *even* more, but we'll save that for another time! Right now, our
app needs more features! Like a template engine, logging, debugging tools and more.
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 <the_view>`".
.. _`Twig`: http://twig.sensiolabs.org/
Keep reading with :doc:`/quick_tour/flex_architecture`.

View File

@@ -1,342 +0,0 @@
The Controller
==============
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()``
controller shortcut method to return a rendered template as result. In case
you need it, you can also create a raw ``Response`` object to return any
text content::
// src/Controller/DefaultController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class DefaultController extends Controller
{
/**
* @Route("/", name="homepage")
*/
public function index()
{
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/Controller/DefaultController.php`` file and add
a new method called ``helloAction()`` with the following content::
// src/Controller/DefaultController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Routing\Annotation\Route;
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 ``templates/default/hello.html.twig`` template
with the following content:
.. code-block:: html+twig
{# templates/default/hello.html.twig #}
{% extends 'base.html.twig' %}
{% block body %}
<h1>Hi {{ name }}! Welcome to Symfony!</h1>
{% 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
-------------
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 thanks to a special variable called ``_format``
which stores the format requested by the user.
Tweak the ``hello`` route by adding a new ``_format`` variable with ``html``
as its default value::
// src/Controller/DefaultController.php
use Symfony\Component\Routing\Annotation\Route;
// ...
/**
* @Route("/hello/{name}.{_format}", defaults={"_format"="html"}, name="hello")
*/
public function helloAction($name, $_format)
{
return $this->render('default/hello.'.$_format.'.twig', array(
'name' => $name
));
}
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
<!-- templates/default/hello.xml.twig -->
<hello>
<name>{{ name }}</name>
</hello>
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.
To restrict the formats supported by a given action, use the ``requirements``
option of the ``@Route()`` annotation::
// src/Controller/DefaultController.php
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Component\Routing\Annotation\Route;
// ...
/**
* @Route("/hello/{name}.{_format}",
* defaults = {"_format"="html"},
* requirements = { "_format" = "html|xml|json" },
* name = "hello"
* )
*/
public function helloAction($name, $_format)
{
return $this->render('default/hello.'.$_format.'.twig', array(
'name' => $name
));
}
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:
Redirecting
-----------
If you want to redirect the user to another page, use the ``redirectToRoute()``
method::
// src/Controller/DefaultController.php
class DefaultController extends Controller
{
/**
* @Route("/", name="homepage")
*/
public function indexAction()
{
return $this->redirectToRoute('hello', array('name' => 'Fabien'));
}
}
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.
Displaying Error Pages
----------------------
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::
// src/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::
// src/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
------------------------------------
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/Controller/DefaultController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
class DefaultController extends Controller
{
/**
* @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 special
``app.request`` variable automatically provided by Symfony:
.. code-block:: html+twig
{{ app.request.query.get('page') }}
{{ app.request.request.get('page') }}
Persisting Data in the Session
------------------------------
Even if the HTTP protocol is stateless, Symfony provides a nice session
object that represents the client (be it a real person using a browser,
a bot, or a web service). Between two requests, Symfony stores the attributes
in a cookie by using native PHP sessions.
Storing and retrieving information from the session can be easily achieved
from any controller::
use Symfony\Component\HttpFoundation\Session\Session;
public function indexAction(Session $session)
{
// store an attribute for reuse during a later user request
$session->set('foo', 'bar');
// get the value of a session attribute
$foo = $session->get('foo');
// use a default value if the attribute doesn't exist
$foo = $session->get('foo', 'default_value');
}
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)::
public function indexAction()
{
// ...
// store a message for the very next request
$this->addFlash('notice', 'Congratulations, your action succeeded!');
}
And you can display the flash message in the template like this:
.. code-block:: html+twig
{% for message in app.flashes('notice') %}
<div class="flash-notice">
{{ message }}
</div>
{% endfor %}
Final Thoughts
--------------
That's all there is to it and I'm not even sure you have spent the full
10 minutes. Let's move onto something *very* interesting: the fact that everything
in Symfony can be extended or replaced. That's the topic of the :doc:`next part of this tutorial <the_architecture>`.

View File

@@ -1,284 +0,0 @@
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 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 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, 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 evaluating an expression;
``{% ... %}``
Controls the logic of the template; it is used for example to execute
``for`` loops and ``if`` statements.
``{# ... #}``
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:
.. code-block:: html+twig
<!DOCTYPE html>
<html>
<head>
<title>{{ page_title }}</title>
</head>
<body>
<h1>{{ page_title }}</h1>
<ul id="navigation">
{% for item in navigation %}
<li><a href="{{ item.url }}">{{ item.label }}</a></li>
{% endfor %}
</ul>
</body>
</html>
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('default/index.html.twig', array(
'variable_name' => 'variable_value',
));
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 passed by the controller depending
on its type:
.. code-block:: twig
{# 1. Simple variables #}
{# $this->render('template.html.twig', array(
'name' => 'Fabien')
) #}
{{ name }}
{# 2. Arrays #}
{# $this->render('template.html.twig', array(
'user' => array('name' => 'Fabien'))
) #}
{{ user.name }}
{# alternative syntax for arrays #}
{{ user['name'] }}
{# 3. Objects #}
{# $this->render('template.html.twig', array(
'user' => new User('Fabien'))
) #}
{{ user.name }}
{{ user.getName }}
{# alternative syntax for objects #}
{{ user.name() }}
{{ user.getName() }}
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 template that contains all the common elements of your site and
defines "blocks" of contents that child templates can override.
The ``index.html.twig`` template uses the ``extends`` tag to indicate that
it inherits from the ``base.html.twig`` template:
.. code-block:: html+twig
{# templates/default/index.html.twig #}
{% extends 'base.html.twig' %}
{% block body %}
<h1>Welcome to Symfony!</h1>
{% endblock %}
Open the ``templates/base.html.twig`` file that corresponds to
the ``base.html.twig`` template and you'll find the following Twig code:
.. code-block:: html+twig
{# templates/base.html.twig #}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>{% block title %}Welcome!{% endblock %}</title>
{% block stylesheets %}{% endblock %}
<link rel="icon" type="image/x-icon" href="{{ asset('favicon.ico') }}" />
</head>
<body>
{% block body %}{% endblock %}
{% block javascripts %}{% endblock %}
</body>
</html>
The ``{% block %}`` tags tell the template engine that a child template
may 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 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:: twig
<h1>{{ article.title|capitalize }}</h1>
<p>{{ article.content|striptags|slice(0, 255) }} ...</p>
<p>Tags: {{ article.tags|sort|join(", ") }}</p>
<p>Activate your account before {{ 'next Monday'|date('M j, Y') }}</p>
Don't forget to check out the official `Twig documentation`_ to learn everything
about filters, functions and tags.
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.
Imagine that we want to display ads on some pages of our application. First,
create a ``banner.html.twig`` template:
.. code-block:: twig
{# templates/ads/banner.html.twig #}
<div id="ad-banner">
...
</div>
To display this ad on any page, include the ``banner.html.twig`` template
using the ``include()`` function:
.. code-block:: html+twig
{# templates/default/index.html.twig #}
{% extends 'base.html.twig' %}
{% block body %}
<h1>Welcome to Symfony!</h1>
{{ include('ads/banner.html.twig') }}
{% endblock %}
Embedding other Controllers
---------------------------
And what if you want to embed the result of another controller in a template?
That's very useful when working with Ajax, or when the embedded template
needs some variable not available in the main template.
Suppose you've created a ``topArticles()`` controller method to display
the most popular articles of your website. If you want to "render" the result
of that method (usually some HTML content) inside the ``index`` template,
use the ``render()`` function:
.. code-block:: twig
{# templates/index.html.twig #}
{{ render(controller('App\\DefaultController::topArticles')) }}
Here, the ``render()`` and ``controller()`` functions use the special
``App\\DefaultController::topArticles`` syntax to refer to the ``topArticles()``
action of the ``Defaultcontroller``::
// src/Controller/DefaultController.php
class DefaultController extends Controller
{
public function topArticles()
{
// look for the most popular articles in the database
$articles = ...;
return $this->render('default/top_articles.html.twig', array(
'articles' => $articles,
));
}
// ...
}
Creating Links between Pages
----------------------------
Creating links between pages is a must for web applications. Instead of
hardcoding URLs in templates, the ``path()`` function knows how to generate
URLs based on the routing configuration. That way, all your URLs
can be easily updated by just changing the configuration:
.. code-block:: html+twig
<a href="{{ path('homepage') }}">Return to homepage</a>
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:
``<a href="{{ url('homepage') }}">Visit our website</a>``.
Including Assets: Images, JavaScripts and Stylesheets
-----------------------------------------------------
What would the Internet be without images, JavaScripts and stylesheets?
Symfony provides the ``asset()`` function to deal with them easily:
.. code-block:: twig
<link href="{{ asset('css/blog.css') }}" rel="stylesheet" type="text/css" />
<img src="{{ asset('images/logo.png') }}" />
The ``asset()`` function looks for the web assets inside the ``public/`` directory.
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.
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
the basics is easy and you will soon learn that this simplicity is hidden
under a very flexible architecture.
But I'm getting ahead of myself. First, you need to learn more about the
controller and that's exactly the topic of the :doc:`next part of this tutorial
<the_controller>`. Ready for another 10 minutes with Symfony?
.. _`Twig`: http://twig.sensiolabs.org/
.. _`Twig documentation`: http://twig.sensiolabs.org/documentation