Symfony2

Web application framework




DC PHP, Aug 27th 2014
© David Buchmann, Liip AG

About David Buchmann

Twitter: @dbu

Outline

Symfony2

Composer

Dependency management reloaded

You should too!

Dependeny Injection

Dependeny Injection

class MyClass
{
    public function do()
    {
        global $variable;
        echo $variable;
    }
}
            

NO!

Dependeny Injection

class MyClass
{
  public function do()
  {
    echo Registry::instance()->get('variable');
  }
}
            

Meh. A bit better but not good.

Dependeny Injection

class MyClass
{
    private $variable;
    
    public function __construct($variable)
    {
        $this->variable = $variable;
    }
    public function do()
    {
        echo $this->variable;
    }
}
            

This is dependency injection!

Dependeny Injection

Symfony2 Dependeny Injection

Core Components:

HttpFoundation

HttpFoundation

HTTP object oriented

The Request object

// /test-path?name=test
$request = Request::createFromGlobals();
$request->query->get('name'); // test
$request->getLanguages(); // ordered by priority
$request->getContent(); // body of request
Request::create('/test-path', 'GET', array('name' => 'test'));
            

Creating a response

$response = new Response('Success', Response::HTTP_OK, 
    array('content-type' => 'text/plain'));
$response->headers->setCookie('foo' => 'bar');
$response->setMaxAge(3600); // let client cache for 1 hour
$response->send();
            

Special types of responses

$response = new RedirectResponse('http://php.net');
$response = new Response('Not Found', Response::HTTP_NOT_FOUND);

$path = '/path/to/file.txt';
$response = new BinaryFileResponse($file);
// overwrite the filename
$response->setContentDisposition(
    ResponseHeaderBag::DISPOSITION_ATTACHMENT,
    'filename.txt'
);
            

Core Components:

Routing

Routing

URL to PHP method

Routes

Request Matching

$route = new Route('/articles/{slug}', 
    array('controller' => 'MyClass'));
$routes = new RouteCollection();
$routes->add('article', $route);
// convenient when using HttpFoundation:
$context = RequestContext::fromRequest(
    Request::createFromGlobals());

$matcher = new UrlMatcher($routes, $context);
$parameters = $matcher->match('/articles/my');
// array('slug'=>'my', 'controller'=>'MyClass', 
//       '_route'=>'article')
//
// Your application knows how to call MyClass
            

Generate URL

$generator = new UrlGenerator($routes, $context);
$generator->generate('article', array(
    'slug' => 'my',
));
// /article/my

$generator->generate('article', array(
    'slug' => 'my'), true);
// http://example.com/article/my
            

Load routes from file

article:
    path: /article/{slug}
    defaults: { _controller: 'MyClass' }
    requirements:
        slug: [a-zA-Z\-]
            
// uses the optional Config component
$locator = new FileLocator(array(__DIR__));
$loader = new YamlFileLoader($locator);
$collection = $loader->load('routes.yml');
            

Core Components:

Twig

Twig

Templating language

Twig in 3 bullet points

Example

<!DOCTYPE html>
<html>
  <head>
    <title>Welcome to Washington DC PHP!</title>
  </head>
  <body>
    <h1>Hello: {{ name }}</h1>
  </body>
</html>
            

Control and data access

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

Inheritance

        {# base.html.twig #}
        <html>
            <head>
                {% block title %}Liip AG{% endblock %}
            </head>
            <body>
                {% block body %}{% endblock %}
            </body>
        </html>
    
{% extends 'base.html.twig' %}
{% block title %}
    {{ content.title }} - {{ parent() }}
{% endblock %}
{% block body %}{{ content.text }}{% endblock %}
            

Extension points

Symfony2 full stack framework

Setting up a project

$ curl -s https://getcomposer.org/installer | php
$ php composer.phar create-project symfony/framework-standard-edition my-project 2.5.*
            

Configure your web server to point to web subfolder of my-project

Directory Layout

Develop

Create your first bundle

$ php app/console generate:bundle --namespace=DC/HelloBundle --format=yml
# simply accept all defaults, they are fine
            
// app/AppKernel.php
public function registerBundles()
{
    $bundles = array(
        // ...
        new DC\HelloBundle\DCHelloBundle(),
    );
    return $bundles;
}
            

Also links routing file from app/config/routing.yml

Bundle Directory Layout

Controller

Controller

namespace DC\HelloBundle\Controller;

use Symfony\Component\HttpFoundation\Response;

class HelloController
{
    public function greetAction($name)
    {
        return new Response("Hello $name");
    }
}
            

Routing: Map request to controller

View

Symfony2 twig integration

$this->render(
    'DCHelloBundle:Hello:greet.html.twig',
    array('name' => $name)
);



Model

<?php
namespace DC\HelloBundle\Entity;
class Page
{
    private $mainText;
    public function __construct($text)
    {
        $this->mainText = $text;
    }
    public function getMainText()
    {
        return $this->mainText;
    }
    public function setMainText($text)
    {
        $this->mainText = $text;
    }
}
            

Use it

Controller

$page = new \DC\HelloBundle\Entity\Page('test');
return $this->render(
    'DCHelloBundle:Default:index.html.twig',
    array('name' => $name, 'page' => $page)
);
            

Template

<p>{{ page.mainText }}</p>
            

Doctrine ORM

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 */
class Page
{
    /**
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @ORM\Column(name="main_text", type="string")
     */
    private $mainText;
    ...
            

Console commands to manage database

$ app/console doctrine:schema:update --dump-sql
CREATE TABLE Page (id INTEGER NOT NULL, main_text VARCHAR(255) NOT NULL, PRIMARY KEY(id))

$ app/console doctrine:schema:update --force
Updating database schema...
Database schema updated successfully! "1" queries were executed
            

Insert Doctrine Objects

$page = new Page('DCPHP in Washington');

$em = $this->getDoctrine()->getManager();
$em->persist($page);
$em->flush();
        

Read Doctrine Objects

$em = $this->getDoctrine()->getManager();
$repository = $em->getRepository('DCHelloBundle:Page')
$page = $repository->find($id);
if (! $page) {
    throw new NotFoundHttpException("no page $id");
}
// render as before
}
        

Doctrine shell commands

Reuse: There is a bundle for that!

Find existing code

Integrate KnpMarkdownBundle

php composer.phar require "knplabs/knp-markdown-bundle:1.2.*"
            

Add to app/AppKernel.php

$bundles = array(
    // ...
    new Knp\Bundle\MarkdownBundle\KnpMarkdownBundle(),
);
            

Use it in template

{{ page.mainText|markdown }}
            

Using git

Lots of interesting components

Thank you!



Questions / Input / Feedback ?



@dbu