00:00:00

PHP Extended

Notes

Let's Do Professional Development Now!

Notes

Who Is Speaking?

Notes

William DURAND

PhD / CTO TailorDev

Graduated from IUT, ISIMA, Blaise Pascal University. Worked at:

Open-Source evangelist:

twitter.com/couac  |  github.com/willdurand  |  williamdurand.fr

Notes

Notes

Notes

Agenda

  • Symfony
    • Controllers
    • Templating
    • Dependency Injection
    • Command Line
    • Forms
    • Validation
    • Translation
    • HTTP Cache
  • Stack PHP
  • Hack

Notes

A Framework To Simplify Developments

A framework helps you work better by structuring developments, and faster by reusing generic modules.

A framework facilitates long-term maintenance and scalability by complying with standard development rules.

Compliance with development standards also simplifies integrating and interfacing the application with the rest of the information system.

In other words, it works as a tool to make the development process easier and more productive.

Most of the time, a framework implements many kinds of design patterns.

Read more: Symfony explained to a Developer.

Notes

Notes

What Is Symfony?

First of all:

Symfony is a reusable set of standalone, decoupled, and cohesive PHP components that solve common web development problems.

Then, based on these components:

Symfony is also a full-stack web framework.

Fabien Potencier, http://fabien.potencier.org/article/49/what-is-symfony2.

Notes

Is Symfony A MVC Framework?

Notes

NO!

Notes

Why You Should Use Symfony

Symfony is built on powerful concepts:

  • Separation of Concerns;
  • Pragmatism;
  • Best Practices.

It has been written by ~1502 developers.

Open Source, MIT licensed.

Notes

The Symfony Components

The Components implement common features needed to develop websites.

They are the foundation of the Symfony full-stack framework, but they can also be used standalone even if you don't use the framework as they don't have any mandatory dependencies.

There are ~30 components, including:

BrowserKit              EventDispatcher     OptionsResolver     Templating
ClassLoader             ExpressionLanguage  Process             Translation
Config                  Filesystem          PropertyAccess      VarDumper
Console                 Finder              PropertyInfo        Yaml
CssSelector             Form                Routing
Debug                   HttpFoundation      Security
DependencyInjection     HttpKernel          Serializer
DomCrawler              Intl                Stopwatch

Notes

Getting Ready With Components

Say you want to play with YAML files, start by requiring the symfony/yaml component into your composer.json file:

{
    "require": {
        "symfony/yaml": "~3.0"
    }
}

Install it by running php composer.phar install, and use it:

require __DIR__ . '/vendor/autoload.php';

use Symfony\Component\Yaml\Yaml;

$yaml = Yaml::parse('/path/to/file.yml');

http://symfony.com/doc/current/components/yaml/introduction.html

Notes

Full-Stack Framework

The Symfony Framework accomplishes two distinct tasks:

  • Provides a selection of components;
  • Provides sensible configuration and a "glue" library that ties all of these pieces together.

The goal of the framework is to integrate many independent tools in order to provide a consistent experience for the developer. Even the framework itself is a Symfony bundle (i.e. a plugin) that can be configured or replaced entirely.

Symfony provides a powerful set of tools for rapidly developing web applications without imposing on your application.

http://symfony.com/doc/current/book/index.html

Notes

Overall Architecture

Notes

The Symfony Request

use Symfony\Component\HttpFoundation\Request;

$request = Request::createFromGlobals();

// the URI being requested (e.g. /about) minus any query parameters
$request->getPathInfo();

// the HTTP verb
$request->getMethod();

// GET variables
$request->query->get('foo');

// POST variables
$request->request->get('bar');

// SERVER variables
$request->server->get('HTTP_HOST');

// retrieve an HTTP request header, with normalized, lowercase keys
$request->headers->get('host');

Notes

The Symfony Response

use Symfony\Component\HttpFoundation\Response;

$response = new Response();

$response->setContent(<<<HTML
<html>
    <body>
        <h1>Hello world!</h1>
    </body>
</html>
HTML
);

$response->setStatusCode(200);

$response->headers->set('Content-Type', 'text/html');

// prints the HTTP headers followed by the content
$response->send();

Notes

The Simplest Front Controller Ever

// index.php
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

$request = Request::createFromGlobals();
$path    = $request->getPathInfo();

if (in_array($path, ['', '/'])) {
    $response = new Response('Welcome to the homepage.');
} elseif ('/hello' === $path) {
    $response = new Response('hello, World!');
} else {
    $response = new Response('Page not found.', 404);
}

$response->send();

Notes

The Symfony Application Flow

It's all about transforming a Request into a Response:

Notes

Routing Definition

The routing system determines which PHP function should be executed based on information from the request and routing configuration you've created.

# app/config/routing.yml
hello:
    path:  /hello
    defaults: { _controller: AppBundle:Main:hello }

The AppBundle:Main:hello string is a short syntax that points to a specific PHP method named helloAction() inside a class called MainController.

This example uses YAML to define the routing configuration. Routing configuration can also be written in other formats such as XML or PHP.

Notes

Your First Controller

In Symfony, a method in a controller is called an action. The convention is to suffix each method with Action.

Also, each controller should be suffixed with Controller.

// src/AppBundle/Controller/MainController.php
namespace AppBundle\Controller;

use Symfony\Component\HttpFoundation\Response;

class MainController
{
    public function helloAction()
    {
        return new Response('<h1>Hello, World!</h1>');
    }
}

Notes

A Symfony Project (1/2)

Recommended structure of a Symfony (3.x) project:

path/to/project/
    app/
        config/
        Resources/
            views/
    bin/
        console
    src/
        ...
    tests/
        ...
    var/
        cache/
        logs/
        sessions/
    vendor/
        ...
    web/
        app.php
        ...

Notes

A Symfony Project (2/2)

Each directory has its own purpose (and set of files):

  • app/ contains the application kernel, views, and the configuration;
  • src/ contains your bundles;
  • tests/ contains your tests;
  • var/ contains files that change often (like in Unix systems);
  • vendor/ contains your dependencies;
  • web/ contains your front controllers and your assets.

Notes

Application Kernel

This is the central part of your application:

// app/AppKernel.php
use Symfony\Component\HttpKernel\Kernel;

class AppKernel extends Kernel
{
    public function registerBundles()
    {
        $bundles = [
            new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
            // ...
        ];

        if (in_array($this->getEnvironment(), ['dev', 'test'])) {
            $bundles[] = // dev bundle;
        }

        return $bundles;
    }

    // ...
}

Notes

Application Configuration

An application consists of a collection of "bundles" representing all of 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 either config.yml, config.xml or config.php depending on which format you prefer.

Symfony is all about configuring everything, and you can do pretty much everything you want. That's why people agreed on some conventions, but then again, a convention is just A way to do things, not THE way to do them.

Notes

YAML Configuration

Example:

# app/config/config.yml
imports:
    - { resource: parameters.yml }
    - { resource: security.yml }

framework:
    secret: '%secret%'
    router: { resource: '%kernel.root_dir%/config/routing.yml' }
    # ...

# Twig Configuration
twig:
    debug:            '%kernel.debug%'
    strict_variables: '%kernel.debug%'

# ...

Notes

XML Configuration

Example:

<!-- app/config/config.xml -->
<imports>
    <import resource="parameters.yml"/>
    <import resource="security.yml"/>
</imports>

<framework:config secret="%secret%">
    <framework:router resource="%kernel.root_dir%/config/routing.xml"/>
    <!-- ... -->
</framework:config>

<!-- Twig Configuration -->
<twig:config debug="%kernel.debug%" strict-variables="%kernel.debug%"/>

<!-- ... -->

Notes

PHP Configuration

Example:

$this->import('parameters.yml');
$this->import('security.yml');

$container->loadFromExtension('framework', [
    'secret' => '%secret%',
    'router' => [
        'resource' => '%kernel.root_dir%/config/routing.php'
    ],
    // ...
]);

// Twig Configuration
$container->loadFromExtension('twig', [
    'debug'            => '%kernel.debug%',
    'strict_variables' => '%kernel.debug%',
]);

// ...

Notes

The Rules (Well... My Rules)

The main configuration MUST be written in YAML:

# app/config/config.yml
# ...
twig:
    debug:            '%kernel.debug%'
    strict_variables: '%kernel.debug%'

The routing definition MUST be written in YAML:

# app/config/routing.yml
hello:
    path:  /hello
    defaults: { _controller: AppBundle:Main:hello }

The DI Container configuration MUST be written in XML:

<services>
    <service id="acme_demo.controllers.main"
        class="AppBundle\MainController" />
</services>

Notes

Environments

An application can run in various environments. The different environments share the same PHP code, but use different configuration.

A Symfony project generally uses three environments: dev, test and prod.

// web/app.php

// ...
$kernel = new AppKernel('prod', false);

The AppKernel class is responsible for actually loading the configuration file of your choice:

// app/AppKernel.php
public function registerContainerConfiguration(LoaderInterface $loader)
{
    $loader->load(
        __DIR__ . '/config/config_' . $this->getEnvironment() . '.yml'
    );
}

Notes

What Is A Bundle?

A Bundle is a directory containing a set of files (PHP files, stylesheets, JavaScripts, images, ...) that implement a single feature (a blog, a forum, etc).

It should be reusable, so that you don't reinvent the wheel each time you need a common feature. In Symfony, (almost) everything lives inside a bundle.

In order to use a bundle in your application, you need to register it in the AppKernel, using the registerBundles() method:

public function registerBundles()
{
    $bundles = array(
        // ...

        new My\AwesomeBundle\MyAwesomeBundle(),
    );

    // ...
}

Notes

Bundle: Directory Structure

Recommended structure for a bundle:

XXX/...
    DemoBundle/
        DemoBundle.php
        Controller/
        Resources/
            config/
            doc/
                index.rst
            translations/
            views/
            public/
        Tests/
        LICENSE

The DemoBundle class is mandatory, and both LICENSE and Resources/doc/index.rst files should be present.

The XXX directory(ies) reflects the namespace structure of the bundle.

Notes

Bundle: Where To Put Your Classes?

Type Directory
Commands Command/
Controllers Controller/
Service Container Extensions DependencyInjection/
Event Listeners EventListener/
Configuration Resources/config/
Web Resources Resources/public/
Translation files Resources/translations/
Templates Resources/views/
Unit and Functional Tests Tests/

Notes

Creating a Bundle

A bundle has to extend the Symfony\Component\HttpKernel\Bundle\Bundle class:

// src/Acme/MyFirstBundle/AcmeMyFirstBundle.php
namespace Acme\MyFirstBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;

class AcmeMyFirstBundle extends Bundle
{
}

Then, you can register your bundle:

// app/AppKernel.php
public function registerBundles()
{
    $bundles = array(
        new Acme\MyFirstBundle\AcmeMyFirstBundle(),
    );

    return $bundles;
}

Notes

The Web Directory

The web root directory is the home of all public and static files including images, stylesheets, and JavaScript files. It is also where each front controller lives:

// web/app.php
require_once __DIR__.'/../app/bootstrap.php.cache';
require_once __DIR__.'/../app/AppKernel.php';

use Symfony\Component\HttpFoundation\Request;

$kernel   = new AppKernel('prod', false);
$request  = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();

The front controller file (app.php in this example) is the actual PHP file that's executed when using a Symfony application and its job is to use a Kernel class, AppKernel, to bootstrap the application, for a given environment.

Notes

Summary

Creating a page is a three-step process involving a route, a controller, and (optionally) a template.

Each project contains just a few main directories: web/ (web assets and the front controllers), app/ (configuration), src/ (your bundles), and vendor/ (third-party code).

Each feature in Symfony (including the Symfony framework core) is organized into a bundle, which is a structured set of files for that feature.

The configuration for each bundle lives in the Resources/config directory of the bundle and can be specified in YAML, XML or PHP.

The global application configuration lives in the app/config/ directory.

Each environment is accessible via a different front controller (e.g. app.php and app_dev.php) and loads a different configuration file.

Notes

Read The Best Practices!

Notes

Controllers

Notes

Request, Controller, Response

A controller is a PHP function you create that takes information from the HTTP request and constructs and returns an HTTP response.

Every request handled by a Symfony project goes through the same lifecycle:

  1. Each request is handled by a single front controller file (e.g. app.php or app_dev.php) that bootstraps the application;
  2. The Router reads information from the request (e.g. the URI), finds a route that matches that information, and reads the _controller parameter from the route;
  3. The controller from the matched route is executed and the code inside the controller creates and returns a Response object;
  4. The HTTP headers and content of the Response object are sent back to the client.

Notes

The Simplest Page Ever

Routing Definition

# app/config/routing.yml
homepage:
    path:  /
    defaults: { _controller: AppBundle:Hello:index }

Controller Implementation

// src/AppBundle/Controller/HelloController.php
namespace AppBundle\Controller;

use Symfony\Component\HttpFoundation\Response;

class HelloController
{
    public function indexAction()
    {
        return new Response('Home, Sweet Home!');
    }
}

Notes

Controller Naming Pattern

Every route must have a _controller parameter, which dictates which controller should be executed when that route is matched.

This parameter uses a simple string pattern called the logical controller name. The pattern has three parts, each separated by a colon: bundle:controller:action.

For example, a _controller value of AcmeBlogBundle:Blog:show means:

  • Bundle: AcmeBlogBundle;
  • Controller Class: BlogController;
  • Method Name: showAction.

Notice that Symfony adds the string Controller to the class name (Blog => BlogController) and Action to the method name (show => showAction).

Notes

Route Params as Controller Args

Routing Definition

# src/AppBundle/Resources/config/routing.yml
app.hello_hello:
    path:  /hello/{name}
    defaults: { _controller: AppBundle:Hello:hello }
    requirements:
        _method: GET

Controller Implementation

// src/AppBundle/Controller/HelloController.php

class HelloController
{
    // ...

    public function helloAction($name)
    {
        return new Response(sprintf('Home, Sweet %s!', $name));
    }
}

Notes

The Request as a Controller Argument

For convenience, you can also have Symfony pass you the Request object as an argument to your controller:

use Symfony\Component\HttpFoundation\Request;

class HelloController
{
    // ...

    public function updateAction(Request $request)
    {
        // do something useful with $request
    }
}

This is useful when you are working with forms.

Notes

The Base Controller Class

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:

use Symfony\Bundle\FrameworkBundle\Controller\Controller

class HelloController extends Controller
{
    // ...
}

Redirecting

$this->redirect($this->generateUrl('homepage'));

Rendering Templates

return $this->render(
    'hello/hello.html.twig', array('name' => $name)
);

Notes

The Response

The only requirement for a controller is to return a Response object.

Create a simple Response with a 200 status code:

use Symfony\Component\HttpFoundation\Response;

$response = new Response('Hello, ' . $name, 200);

Create a JSON response with a 200 status code:

$response = new Response(json_encode(array('name' => $name)));
$response->headers->set('Content-Type', 'application/json');

Or:

use Symfony\Component\HttpFoundation\JsonResponse;

$response = new JsonResponse(array('name' => $name));

Notes

Routing

Notes

Basic Route Configuration

The Symfony router lets you define URLs that you map to different areas of your application.

A route is a map from a URL path to a controller. Each route is named, and maps a path to a _controller:

# app/config/routing.yml
homepage:
    path:  /
    defaults: { _controller: AppBundle:Hello:index }

This route matches the homepage (/) and maps it to the AppBundle:Hello:index controller.

http://symfony.com/doc/master/book/routing.html

Notes

Routing with Placeholders (1/2)

Required Placeholders

blog:
    path:      /blog/{page}
    defaults:  { _controller: AcmeBlogBundle:Blog:index }

The path will match anything that looks like /blog/*.

Even better, the value matching the {page} placeholder will be available inside your controller.

/blog will not match.

Notes

Routing with Placeholders (2/2)

Optional Placeholders

blog:
    path:      /blog/{page}
    defaults:  { _controller: AcmeBlogBundle:Blog:index, page: 1 }

By adding page to the defaults key, {page} is no longer required.

/blog will match this route and the value of the page parameter will be set to 1. /blog/2 will also match, giving the page parameter a value of 2.

Notes

Requirements

blog:
    path:      /blog/{page}
    defaults:  { _controller: AcmeBlogBundle:Blog:index, page: 1 }
    requirements:
        page:  \d+

The \d+ requirement is a regular expression that says that the value of the {page} parameter must be a digit (i.e. a number).

HTTP Method Requirements

# src/AppBundle/Resources/config/routing.yml
app.hello_hello:
    path:  /hello/{name}
    defaults: { _controller: AppBundle:Hello:hello }
    methods:  [ GET ]
    # methods:  [ GET, POST ]

Notes

Including External Routing Resources

All routes are loaded via a single configuration file, most of the time it will be app/config/routing.yml.

In order to respect the "bundle" principle, the routing configuration should be located in the bundle itself, and you should just require it:

# app/config/routing.yml
appa:
    resource: '@AppBundle/Resources/config/routing.yml'

Prefixing Imported Routes

# app/config/routing.yml
app:
    resource: '@AppBundle/Resources/config/routing.yml'
    prefix:   /demo

The string /demo now be prepended to the path of each route loaded from the new routing resource.

Notes

Generating URLs

The Router is able to generate both relative and absolute URLs.

$router = $this->get('router');

Relative URLs

$router->generate('app.hello_hello', [ 'name' => 'will' ]);
// /hello/will

Absolute URLs

$router->generate('app.hello_hello', [ 'name' => 'will' ], true);
// http://example.com/hello/will

Query String

$router->generate('app.hello_hello', [
    'name' => 'will', 'some' => 'thing'
]);
// /hello/will?some=thing

Notes

Templating

Notes

Notes

Why Twig?

Fast, Secure, Flexible.

Before

<ul id="navigation">
    <?php foreach ($navigation as $item): ?>
        <li>
            <a href="<?php echo $item->getHref() ?>">
                <?php echo $item->getCaption() ?>
            </a>
        </li>
    <?php endforeach; ?>
</ul>

After

<ul id="navigation">
    {% for item in navigation %}
        <li><a href="{{ item.href }}">{{ item.caption }}</a></li>
    {% endfor %}
</ul>

Notes

Getting Familiar With Twig

Delimiters

  • {{ ... }}: prints a variable or the result of an expression;
  • {% ... %}: controls the logic of the template; it is used to execute for loops and if statements, for example;
  • {# ... #}: comments.

http://twig.sensiolabs.org/

Notes

Accessing Variables

{# array('name' => 'Fabien') #}
{{ name }}

{# array('user' => array('name' => 'Fabien')) #}
{{ user.name }}

{# force array lookup #}
{{ user['name'] }}

{# array('user' => new User('Fabien')) #}
{{ user.name }}
{{ user.getName }}

{# force method name lookup #}
{{ user.name() }}
{{ user.getName() }}

{# pass arguments to a method #}
{{ user.date('Y-m-d') }}

Notes

Control Structure

Conditions

{% if user.isSuperAdmin() %}
    ...
{% elseif user.isMember() %}
    ...
{% else %}
    ...
{% endif %}

Loops

<ul>
    {% for user in users if user.active %}
        <li>{{ user.username }}</li>
    {% else %}
        <li>No users found</li>
    {% endfor %}
</ul>

Notes

Filters

Filters are used to modify Twig variables.

You can use inline filters by using the | symbol:

{{ 'hello'|upper }}

But you can also use the block syntax:

{% filter upper %}
    hello
{% endfilter %}

Filters can be parametrized:

{{ post.createdAt|date('Y-m-d') }}

http://twig.sensiolabs.org/doc/filters/index.html

Notes

Including Other Templates

The include tag is useful to include a template and return the rendered content of that template into the current one:

{% include 'sidebar.html' %}

Example

Given the following template:

{% for user in users %}
    {% include "render_user.html" %}
{% endfor %}

with render_user.html:

<p>{{ user.username }}</p>

<p>William D.</p>
<p>Julien M.</p>

Notes

Template Inheritance (1/2)

Let's define a base template, base.html, which defines a simple HTML skeleton:

{# app/Resources/views/base.html.twig #}
<!DOCTYPE html>
<html>
    <head>
        <title>{% block title %}Test Application{% endblock %}</title>
    </head>
    <body>
        <div id="sidebar">
            {% block sidebar %}
            <ul>
                <li><a href="/">Home</a></li>
                <li><a href="/blog">Blog</a></li>
            </ul>
            {% endblock %}
        </div>

        <div id="content">
            {% block body %}{% endblock %}
        </div>
    </body>
</html>

Notes

Template Inheritance (2/2)

The key to template inheritance is the {% extends %} tag.

A child template might look like this:

{# app/Resources/views/Blog/index.html.twig #}
{% extends 'base.html.twig' %}

{% block title %}My cool blog posts{% endblock %}

{% block body %}
    {% for entry in blog_entries %}
        <h2>{{ entry.title }}</h2>
        <p>{{ entry.body }}</p>
    {% endfor %}
{% endblock %}

If you need to get the content of a block from the parent template, you can use the {{ parent() }} function.

Notes

Template Naming and Locations (1/2)

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), templates specific to your app as well as templates that override bundle templates;
  • path/to/bundle/Resources/views/: Each (public) bundle houses its templates in its Resources/views directory (and subdirectories).

Symfony uses a bundle:controller:template string syntax for templates.

You can skip the controller string: bundle::template. The template file would live in Resources/views/.

You can also skip the bundle string. It refers to an application-wide base template or layout. This means that the template is not located in any bundle, but instead in the root app/Resources/views/ directory.

Notes

Template Naming and Locations (2/2)

Example

AcmeBlogBundle:Blog:index.html.twig
  • AcmeBlogBundle: (bundle) the template lives inside the AcmeBlogBundle (e.g. src/Acme/BlogBundle);
  • Blog: (controller) 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.

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

Notes

Overriding Bundle Templates

Once you use a third-party bundle, you'll likely need to override and customize one or more of its templates.

When the FooBarBundle:Bar:index.html.twig is rendered, Symfony actually looks in two different locations for the template:

  • app/Resources/FooBarBundle/views/Bar/index.html.twig;
  • src/Foo/BarBundle/Resources/views/Bar/index.html.twig.

In order to override the bundle template, copy the index.html.twig template from the bundle to: app/Resources/FooBarBundle/views/Bar/index.html.twig.

Notes

Overriding Core Templates

The core TwigBundle contains a number of different templates that can be overridden by copying each from the Resources/views/ directory of the TwigBundle to the app/Resources/TwigBundle/views/ directory.

Notes

Twig Into Symfony

Notes

Rendering A Template

Using The Base Controller

public function listAction()
{
    // ...

    return $this->render('blog/index.html.twig', array(
        'posts' => $posts,
    ));
}

Using the Templating Service

$engine  = $this->container->get('templating');
$content = $engine->render('blog/index.html.twig', array(
    'posts' => $posts,
));

return new Response($content);

Notes

Linking to Pages

Assuming the following routing definition:

homepage:
    path:     /
    defaults: { _controller: AppBundle:Hello:index }

acme_blog.post_show:
    path:     /posts/{slug}
    defaults: { _controller: AcmeBlogBundle:Post:show }

You can create a relative URL using path():

<a href="{{ path('homepage') }}">Home</a>

You can create an absolute URL using url():

<a href="{{ url('homepage') }}">Home</a>

The second argument is used to pass parameters:

<a href="{{ path('acme_blog.post_show', {'slug': 'my-super-slug'}) }}">

Notes

Linking to Assets

<script src={{ asset('js/script.js') }}></script>

<link href="{{ asset('css/style.css') }}" rel="stylesheet">

<img src="{{ asset('images/logo.png') }}" alt="Symfony!" />

Cache Busting

Cache busting is the process of forcing browsers or proxy servers to update their cache, for instance, JavaScript and CSS files or images.

# app/config/config.yml
framework:
    # ...
    templating: { engines: ['twig'], assets_version: v2 }

The asset_version parameter is used to bust the cache on assets by globally adding a query parameter to all rendered asset paths:

/images/logo.png?v2

Notes

Linking To Pages In JavaScript

The FOSJsRoutingBundle allows you to expose your routing in your JavaScript code. That means you'll be able to generate URL with given parameters like you can do with the Router component provided by Symfony.

# app/config/routing.yml
my_route_to_expose:
    path:  /foo/{id}/bar
    defaults: { _controller: FooBarBundle:Foo:bar }
    options:
        expose: true

According to the routing definition above, you can write the following JavaScript code to generate URLs:

Routing.generate('my_route_to_expose', { id: 10 });
// /foo/10/bar

Routing.generate('my_route_to_expose', { id: 10 }, true);
// http://example.org/foo/10/bar

Notes

Global Template Variables

  • 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.

Notes

Service Container

Notes

What Is A Service?

A Service is a generic term for any PHP object that performs a specific task.

A service is usually used globally, such as a database connection object or an object that delivers email messages.

In Symfony, services are often configured and retrieved from the service container.

An application that has many decoupled services is said to follow a Service-Oriented Architecture (SOA).

Notes

What Is A Service Container?

A Service Container, also known as a Dependency Injection Container (DIC), is a special object that manages the instantiation of services inside an application.

The service container takes care of lazily instantiating and injecting dependent services.

Notes

Creating A Service

class Foo
{
    private $bar;
    private $debug;

    public function __construct(Bar $bar = null, $debug = false)
    {
        $this->bar   = $bar;
        $this->debug = $debug;
    }
}

The service definition for the class described above is:

<services>
    <service id="foo" class="My\Bundle\Foo" />
</services>

This service is now available in the container, and you can access it by asking the service from the container:

$foo = $this->container->get('foo');

Notes

Service Parameters

The service definition described before is not flexible enough. For instance, $debug argument is never configured.

Parameters make defining services more organized and flexible:

<parameters>
    <parameter key="my_bundle.foo.class">My\Bundle\Foo</parameter>
</parameters>

<services>
    <service id="foo" class="%my_bundle.foo.class%">
        <argument></argument> <!-- null -->
        <argument>%kernel.debug%</argument>
    </service>
</services>

In the definition above, kernel.debug is a parameter defined by the framework itself. The foo service is now parametrized.

Also, it becomes easy to change the implementation of this service by simply overriding the my_bundle.foo.class parameter.

Notes

Injecting Services

As you may noticed, the Foo class takes an instance of Bar as first argument. You can inject this instance in your foo service by referencing the bar service:

<parameters>
    <parameter key="my_bundle.foo.class">My\Bundle\Foo</parameter>
    <parameter key="my_bundle.bar.class">My\Bundle\Bar</parameter>
</parameters>

<services>
    <service id="bar" class="%my_bundle.bar.class%" />

    <service id="foo" class="%my_bundle.foo.class%">
        <argument type="service" id="bar" />
        <argument>%kernel.debug%</argument>
    </service>
</services>

Optional Dependencies: Setter Injection

<call method="setBar">
    <argument type="service" id="bar" />
</call>

Notes

Importing Configuration Resources

The imports Way

# app/config/config.yml
imports:
    - { resource: "@AcmeDemoBundle/Resources/config/services.xml" }

Container Extensions

A service container extension is a PHP class to accomplish two things:

  • import all service container resources needed to configure the services for the bundle;
  • provide semantic, straightforward configuration so that the bundle can be configured without interacting with the flat parameters of the bundle's service container configuration.

Notes

Creating an Extension Class

An extension class should live in the DependencyInjection directory of your bundle and its name should be constructed by replacing the Bundle suffix of the Bundle class name with Extension.

// Acme/DemoBundle/DependencyInjection/AcmeDemoExtension.php
namespace Acme\DemoBundle\DependencyInjection;

use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;

class AcmeDemoExtension extends Extension
{
    public function load(array $configs, ContainerBuilder $container)
    {
        $loader = new XmlFileLoader($container, new FileLocator(
            __DIR__ . '/../Resources/config'
        ));
        $loader->load('services.xml');
    }
}

Notes

Dealing With Configuration (1/2)

The presence of the previous class means that you can now define an acme_demo configuration namespace in any configuration file:

# app/config/config.yml
acme_demo: ~

Take the following configuration:

acme_demo:
    foo: fooValue
    bar: barValue

The array passed to your load() method will look like this:

array(
    array(
        'foo' => 'fooValue',
        'bar' => 'barValue',
    )
)

Notes

Dealing With Configuration (2/2)

The $configs argument is an array of arrays, not just a single flat array of the configuration values.

It's your job to decide how these configurations should be merged together.

You might, for example, have later values override previous values or somehow merge them together:

public function load(array $configs, ContainerBuilder $container)
{
    $config = array();
    foreach ($configs as $subConfig) {
        $config = array_merge($config, $subConfig);
    }

    // ... now use the flat $config array
}

Notes

The Configuration Class (1/2)

Definition

// src/Acme/DemoBundle/DependencyInjection/Configuration.php
namespace Acme\DemoBundle\DependencyInjection;

use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;

class Configuration implements ConfigurationInterface
{
    public function getConfigTreeBuilder()
    {
        $treeBuilder = new TreeBuilder();
        $rootNode    = $treeBuilder->root('acme_demo');

        $rootNode
            ->children()
                ->scalarNode('my_type')->defaultValue('bar')->end()
            ->end();

        return $treeBuilder;
    }
}

Notes

The Configuration Class (2/2)

Usage

public function load(array $configs, ContainerBuilder $container)
{
    $configuration = new Configuration();

    $config = $this->processConfiguration($configuration, $configs);

    // ...
}

The processConfiguration() method uses the configuration tree you've defined in the Configuration class to validate, normalize and merge all of the configuration arrays together.

Read more on How to expose a Semantic Configuration for a Bundle: http://symfony.com/doc/master/cookbook/bundles/extension.html.

Notes

More On The Service Container

Tags

In the service container, a tag implies that the service is meant to be used for a specific purpose.

<service id="my_bundle.twig.foo" class="My\Bundle\Twig\FooExtension">
    <tag name="twig.extension" />
</service>

Twig finds all services tagged with twig.extension and automatically registers them as extensions.

Debugging Services

$ php bin/console debug:container

$ php bin/console debug:container foo

http://symfony.com/doc/master/book/service_container.html

Notes

Symfony Commands

Notes

Built-in Commands (1/2)

$ php bin/console

Global Options

You can get help information:

$ php bin/console help cmd
$ php bin/console cmd --help
$ php bin/console cmd -h

You can get more verbose messages:

$ php bin/console cmd --verbose
$ php bin/console cmd -v [-vv] [-vvv]

You can suppress output:

$ php bin/console cmd --quiet
$ php bin/console cmd -q

Notes

Built-in Commands (2/2)

assets
  assets:install          Installs bundles web assets under a public
                          web directory
cache
  cache:clear             Clears the cache
  cache:warmup            Warms up an empty cache
config
  config:dump-reference   Dumps default configuration for an extension
container
  container:debug         Displays current services for an application
debug
  debug:container         Displays current services for an application
  debug:router            Displays current routes for an application
router
  router:match            Helps debug routes by simulating a path info
                          match
server
  server:run              Runs PHP built-in web server
translation
  translation:update      Updates the translation file
lint
  lint:twig               Lints a template and outputs encountered
                          errors

Notes

Creating Commands

Create a Command directory inside your bundle and create a php file suffixed with Command.php for each command that you want to provide:

// src/AppBundle/Command/GreetCommand.php
namespace AppBundle\Command;

use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class GreetCommand extends ContainerAwareCommand
{
    protected function configure()
    {
        $this->setName('demo:greet');
    }

    protected function execute(
        InputInterface $input,
        OutputInterface $output
    ) {
        // code ...
    }
}

Notes

Command Arguments

Arguments are the strings, separated by spaces, that come after the command name itself. They are ordered, and can be optional or required.

protected function configure()
{
    $this
        // ...
        ->addArgument(
            'name',
            InputArgument::REQUIRED,
            'Who do you want to greet?'
        )
        ->addArgument(
            'last_name',
            InputArgument::OPTIONAL,
            'Your last name?'
        );
}

Usage

$input->getArgument('last_name');

Notes

Command Options (1/2)

Unlike arguments, options are not ordered, always optional, and can be setup to accept a value or simply as a boolean flag without a value.

protected function configure()
{
    $this
        // ...
        ->addOption(
            'yell',
            null,
            InputOption::VALUE_NONE,
            'If set, the task will yell in uppercase letters'
        );
}

Usage

// php bin/console demo:greet --yell

if ($input->getOption('yell')) {
    // ...
}

Notes

Command Options (2/2)

protected function configure()
{
    $this
        // ...
        ->addOption(
            'iterations',
            null,
            InputOption::VALUE_REQUIRED,
            'How many times should the message be printed?',
            1
        );
}

Usage

// php bin/console demo:greet --iterations=10

for ($i = 0; $i < $input->getOption('iterations'); $i++) {
}

Notes

More On Commands

Getting Services from the Service Container

protected function execute(
    InputInterface $input,
    OutputInterface $output
) {
    $translator = $this->getContainer()->get('translator');
    // ...
}

Calling an existing Command

$command   = $this->getApplication()->find('demo:greet');
$arguments = array(
    'command' => 'demo:greet',
    'name'    => 'Fabien',
    'yell'    => true,
);

$returnCode = $command->run(new ArrayInput($arguments), $output);

http://symfony.com/doc/master/cookbook/console/index.html

Notes

Forms

Notes

Building Your First Form

public function newAction(Request $request)
{
    $form = $this->createFormBuilder()
        ->add('name')
        ->add('bio', 'textarea')
        ->add('birthday', 'date')
        ->getForm();

    return $this->render('default/new.html.twig', [
        'form' => $form->createView(),
    ]);
}

In order to display the Form, you need to pass a special view object to the View layer. It's achieved through the createView() method.

Notes

Rendering The Form


{# src/AppBundle/Resources/views/Default/new.html.twig #}
<form action="{{ path('acme_demo.default_new') }}" method="post">
    {{ form_widget(form) }}

    <input type="submit" />
</form>

Notes

Handling Forms: The Right Way

  1. When initially loading the page in a browser, the request method is GET and the form is simply created and rendered;

  2. When the user submits the form (i.e. the method is POST) with invalid data, the form is bound and then rendered, this time displaying all validation errors;

  3. When the user submits the form with valid data, the form is bound and you have the opportunity to perform some actions before redirecting the user to some other page (e.g. a "success" page).

Redirecting a user after a successful form submission prevents the user from being able to hit "refresh" and re-post the data.

Notes

Handling Form Submissions

public function newAction(Request $request)
{
    $form = $this->createFormBuilder()
        ->add('name')
        ->add('bio', 'textarea')
        ->add('birthday', 'date')
        ->getForm();

    if ($form->handleRequest($request)->isValid()) {
        $data = $form->getData();
        // do something ...

        return $this->redirect($this->generateUrl('success'));
    }

    // ...
}

Notes

Built-in Form Types

Everything is a Type!

Notes

Creating A Custom Type (Form Class)

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class PersonType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('name')
            ->add('bio', 'textarea')
            ->add('birthday', 'date');
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults([
            'data_class' => My\Person::class,
        ]);
    }

    public function getName()
    {
        return 'person';
    }
}

Notes

Dealing With Objects

public function newAction(Request $request)
{
    $person = new Person();
    $form   = $this->createForm(PersonType::class, $person);

    if ($form->handleRequest($request)->isValid()) {
        $person->save(); // insert a new `person`

        return $this->redirect($this->generateUrl('success'));
    }

    // ...
}

Placing the form logic into its own class means that the form can be easily reused elsewhere in your project.

This is the best way to create forms, but the choice is up to you!

Notes

The processForm() Method (1/2)

Saving or updating an object is pretty much the same thing. In order to avoid code duplication, you can use a processForm() method that can be used in both the newAction() and the updateAction():

/**
 * Create a new Person
 */
public function newAction(Request $request)
{
    return $this->processForm($request, new Person());
}

/**
 * Update an existing Person
 */
public function updateAction(Request $request, $id)
{
    $person = ...; // get a `Person` by its $id

    return $this->processForm($request, $person);
}

Notes

The processForm() Method (2/2)

/**
 * @param Request $request
 * @param Person  $person
 *
 * @return Response
 */
private function processForm(Request $request, Person $person)
{
    $form = $this->createForm(PersonType::class, $person);

    if ($form->handleRequest($request)->isValid()) {
        $person->save();

        return $this->redirect($this->generateUrl('success'));
    }

    return $this->render('default/new.html.twig', [
        'form' => $form->createView(),
    ]);
}

Notes

Cross-Site Request Forgery Protection

CSRF is a method by which a malicious user attempts to make your legitimate users unknowingly submit data that they don't intend to submit. Fortunately, CSRF attacks can be prevented by using a CSRF token inside your forms.

CSRF protection works by adding a hidden field to your form, called _token by default that contains a value that only you and your user knows.

This ensures that the user is submitting the given data. Symfony automatically validates the presence and accuracy of this token.

The _token field is a hidden field and will be automatically rendered if you include the form_rest() function in your template, which ensures that all un-rendered fields are output.

Notes

Rendering a Form in a Template (1/2)

<form action="" method="post" {{ form_enctype(form) }}>
    {{ form_errors(form) }}

    {{ form_row(form.name) }}

    {{ form_row(form.bio) }}

    {{ form_row(form.birthday) }}

    {{ form_rest(form) }}

    <input type="submit" />
</form>

Read more: http://symfony.com/doc/master/book/forms.html#rendering-a-form-in-a-template.

Notes

Rendering a Form in a Template (2/2)

  • form_enctype(form): if at least one field is a file upload field, this renders the obligatory enctype="multipart/form-data";

  • form_errors(form): renders any errors global to the whole form (field-specific errors are displayed next to each field);

  • form_row(form.name): renders the label, any errors, and the HTML form widget for the given field inside, by default, a div element;

  • form_rest(form): renders any fields that have not yet been rendered. It's usually a good idea to place a call to this helper at the bottom of each form. This helper is also useful for taking advantage of the automatic CSRF Protection.

Notes

Validation

Notes

About Form Validation

In the previous section, you learned how a form can be submitted with valid or invalid data. In Symfony, validation is applied to the underlying object.

In other words, the question isn't whether the "form" is valid, but whether the object is valid after the form has applied the submitted data to it.

Calling $form->isValid() is a shortcut that asks the object whether it has valid data using a Validation layer.

Validation is done by adding a set of rules (called constraints) to a class.

Notes

The Validator Component

This component is based on the JSR303 Bean Validation specification.

Example

Given the following class:

namespace AppBundle\Entity;

class Author
{
    public $name;
}

You can configure a set of constraints on it:

# src/AppBundle/Resources/config/validation.yml
AppBundle\Entity\Author:
    properties:
        name:
            - NotBlank: ~

Notes

Using the validator Service

$author = new Author();
// ... do something to the $author object

$validator = $this->get('validator');
$errors    = $validator->validate($author);

if (count($errors) > 0) {
    // Ooops, errors!
} else {
    // Everything is ok :-)
}

If the $name property is empty, you will see the following error message:

AppBundle\Author.name:
    This value should not be blank

Most of the time, you won't interact directly with the validator service or need to worry about printing out the errors. You will rather use validation indirectly when handling submitted form data.

Notes

Constraints