The not so New PHP

BADCamp 2014

About me


Jesus Manuel Olivas


Drupal 8 Solutions Engineer

BlinkReaction

Reach me at:
@jmolivas | @drupodcast

Topics

  • What is Symfony
  • The HTTP Lifecycle
  • Request & Response
  • Namespaces
  • Routing
  • Controller
  • Service
  • Service Container
  • Dependency Injection
  • Extras

Disclaimer

If you do Symfony:

This talk may have a lot of Drupal-isms

If you do Drupal:

This talk may have a lot of Symfony-isms.

If you do Silex:

Then you are a cool developer like



I shamelessly borrowed concepts
from his DrupalCon Austin presentation

Chapter 0


  • What is Symfony

What is Symfony

Symfony is a group of decoupled components and other standalone PHP libraries.


Then, based on these components, Symfony is also a full-stack web framework.

Chapter 1


  • The HTTP Lifecycle

  • Request & Response

  • PHP Classes

  • Namespaces

The HTTP Lifecycle

In every web application, in any language, you always have one goal, read and understand the incoming HTTP request in order to create and return the appropriate HTTP response.

The Request


 GET /foo HTTP/1.1
 Host: yourdomain.com
 Accept: text/html
 User-Agent: Mozilla/5.0 (Macintosh)

The Response


 HTTP/1.1 200 OK
 Date: Tue, 04 Jun 2011 21:05:05 GMT
 Server: lighttpd/1.4.19
 Content-Type: text/html

 <html>
   <h1>Foo!</h1>
 </html>

The Symfony Application Flow

PHP Classes & Namespaces


PHP Classes

By default, a PHP class is identified by its name, which must be unique within your project.

class Product {
  private $foo = 'bar';
  public function __construct() {
    ...
  }
}

Referencing a non-namespaced class


$product = new \Product(); // Always works :)

$product = new Product();  // Sometimes works :(

PHP Namespaces

Are a way of encapsulating items. Bringing the ability to organize and group related classes and to avoid name collision.


namespace BADCamp\FirstBundle\Entity;

class Product {
// ...
}

Referencing a namespaced class


// reference by its fully-qualified name
$product = new BADCamp\FirstBundle\Entity\Product();

// importing the namespace to file
use BADCamp\FirstBundle\Entity\Product;

$product = new Product();

Chapter 2


  • Routing

  • Controller

Routing


A route is a map from a URL path to a controller.

Controller


A controller is a PHP method or call-back that takes information from the HTTP request and constructs and returns an HTTP response.

Symfony

src/BADCamp/FirstBundle/resources/config/routing.yml

hello_name:
    path:     /hello/{name}
    defaults: { _controller: FirstBundle:Hello:index }

src/BADCamp/FirstBundle/Controller/HelloController.php

namespace BADCamp\FirstBundle\Controller;

use Symfony\Component\HttpFoundation\Response;

class HelloController {

  public function indexAction($name) {
    return new Response('Hello '.$name);
  }
}

Silex

web/index.php


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

$app = new Silex\Application();

$app->get('/hello/{name}', function($name) {
    return 'Hello '.$name;
});

$app->run();

Silex

web/index.php

$app->get(
  '/hello-controller/{name}',
  'BADCamp\Controller\HelloController::indexAction'
);

src/BADCamp/Controller/HelloController.php

namespace BADCamp\Controller;

use Symfony\Component\HttpFoundation\Response;

class HelloController {
  public function indexAction($name) {
    return new Response('Hello '.$name);
  }
}

Drupal

badcamp.routing.yml


badcamp.hello_controller:
  path: 'hello/{name}'
  defaults:
    _content: '\Drupal\BADCamp\Controller\HelloController::index'

src/Controller/HelloController.php


namespace Drupal\badcamp\Controller;

class HelloController {

  public function index($name)
  {
    return 'Hello '.$name;
  }
}

Chapter 3


  • Service

  • Service container

  • Dependency Injection

Service


PHP object that performs some sort of "global" task.

Service container


A Service Container (or dependency injection container) is simply a PHP object that manages the instantiation of services.

Dependency Injection


Dependency Injection is where components are given their dependencies through their constructors, methods, or directly into fields.

Dependency Injection 101

Dependency Injection code


$redRanger = new PowerRangers\MightyMorphin\Ranger();
$redranger->setName('Jason Lee Scott');

$yellowRanger = new PowerRangers\MightyMorphin\Ranger();
$yellowRanger->setName('Trini Kwan');

$mastodonDinozord = new PowerRangers\MightyMorphin\MastodonDinozord($redRanger);
$tigerDinozord = new PowerRangers\MightyMorphin\TigerDinozord($yellowRanger);

// Initiate Megazord sequence!

$megaZord = new PowerRangers\MightyMorphin\MegaZord();
$megaZord->setMastodonDinozord($mastodonDinozord);
$megaZord->setTigerDinozord($tigerDinozord);
$megaZord->setMode($megaZord::MODE_BATTLE);

Symfony

src/BADCamp/FirstBundle/Controller/HelloTemplatingController.php
use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class HelloTemplatingController extends Controller {

  public function indexAction($name) {
    $templating = $this->get('templating');
    $content = $templating->render(
        'FirstBundle:Hello:index.html.twig',
        array('name' => $name)
    );
    return new Response($content);
  }
}
src/BADCamp/FirstBundle/resources/views/Hello/index.html.twig
Hello {{name}}

Symfony/Bundle/FrameworkBundle/Controller/Controller.php

Symfony Controller as a Service 1/2

app/config/config.yml

imports:
  - { resource: "@FirstBundle/Resources/config/services.yml" }

src/BADCamp/FirstBundle/resources/config/services.yml


services:
 badcamp.service.controller:
  class:
   BADCamp\FirstBundle\Controller\HelloTemplatingServiceController
  arguments: ["@templating"]

src/BADCamp/FirstBundle/resources/config/routing.yml


hello_templating_service_name:
  path:     /hello-templating-service/{name}
  defaults: { _controller: badcamp.service.controller:indexAction }

Symfony Controller as a Service 2/2

@FirstBundle/Controller/HelloTemplatingServiceController.php

use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;

class HelloTemplatingServiceController {

  private $templating;

  public function __construct(EngineInterface $templating) {
    $this->templating = $templating;
  }

  public function indexAction($name) {
    $content = $this->templating->render(
      'FirstBundle:Hello:index.html.twig',
      array('name' => $name)
    );
    return new Response($content);
  }
}

Silex Service Provider 1/2

composer.json

"twig/twig": ">=1.8,<2.0-dev"

index.php


$app->register(new Silex\Provider\TwigServiceProvider(), array(
    'twig.path' => __DIR__.'/../templates',
));

$app->get(
  '/hello-controllertwig/{name}',
  'BADCamp\Controller\HelloTwigController::indexAction'
);

Silex Service Provider 2/2

src/BADCamp/Controller/HelloTwigController.php


use Silex\Application;

class HelloTwigController {

  public function indexAction(Application $app, $name) {
    $twig = $app['twig'];
    return $twig->render('hello.html.twig', [
      'name' => $name,
    ]);
  }

}

templates/hello.html.twig

Hello {{name}}

Drupal

src/Controller/HelloEntityManagerController.php

use Drupal\Core\Controller\ControllerBase;

class HelloEntityManagerController extends ControllerBase {

  public function index($id)
  {
    // Load a node using the Node ID
    // node_load($nid, $reset) DO NOT use this one is deprecated
    // $nodeStorage = \Drupal::entityManager()->getStorage('node');

    $nodeStorage = $this->entityManager()->getStorage('node');
    $node = $nodeStorage->load($id);

    return 'Hello node '. $node->title->value ;
  }
}

Drupal DI Service 1/2

src/Controller/HelloEntityManagerServiceController.php


use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Entity\EntityManagerInterface;

class HelloEntityManagerServiceController implements ContainerInjectionInterface {

  protected $entityManager;

  public function __construct(EntityManagerInterface $entityManager) {
    $this->entityManager = $entityManager;
  }

Drupal DI Service 2/2

src/Controller/HelloEntityManagerServiceController.php


  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('entity.manager')
    );
  }

  public function index($id) {

    $nodeStorage = $this->entityManager->getStorage('node');
    $node = $nodeStorage->load($id);

    return 'Hello node '. $node->title->value ;
  }
}

Chapter 4


  • Extras

WebProfiler

Collects information about each request made to your application and allows you to visualize it in the browser.

Symfony

https://www.drupal.org/project/webprofiler

Silex

https://github.com/silexphp/Silex-WebProfiler

Drupal

https://www.drupal.org/project/webprofiler

Console

The Console component allows you to create command-line commands.

Symfony Commands


$app/console assets:install

$app/console cache:clear

$app/console route:debug

$app/console container:debug

$app/console server:run

$app/console generate:bundle
$app/console generate:controller

Symfony 3rd Party Bundle Commands


$app/console doctrine:database:create
$app/console doctrine:database:drop

$app/console doctrine:schema:create
$app/console doctrine:schema:drop
$app/console doctrine:schema:update

$app/console doctrine:generate:entity
$app/console doctrine:generate:crud
$app/console doctrine:generate:form

$app/console doctrine:migrations:diff
$app/console doctrine:migrations:execute
$app/console doctrine:migrations:migrate
$app/console doctrine:migrations:status

Silex


$composer require knplabs/console-service-provider:dev-master


$ app/console hello Drupal

https://github.com/KnpLabs/ConsoleServiceProvider

Silex Command

src/BADCamp/Command/HelloCommand.php

namespace BADCamp\Command;

use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputArgument;

class HelloCommand extends Knp\Command\Command {
  protected function configure() {
      $this
        ->setName("hello")
        ->setDescription("A hello command!")
        ->addArgument('name', InputArgument::OPTIONAL,'What is your Name ?');
  }
  protected function execute(InputInterface $input, OutputInterface $output) {
      $output->writeln("Hello " . ($input->getArgument('name')?:"Silex"));
  }
}

Silex console file

app/console

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

$app = new Silex\Application();

set_time_limit(0);

$app->register(new Knp\Provider\ConsoleServiceProvider(), array(
    'console.name' => 'ConsoleApp',
    'console.version' => '1.0.0',
    'console.project_directory' => __DIR__ . '/..'
));

$console = $app["console"];
$console->add(new \BADCamp\Command\HelloCommand());
$console->run();

Drupal


$ COMPOSER_BIN_DIR=bin composer require  --dev drupal/console:@stable

$ ./bin/console list

$ ./bin/console --shell

Do you have a Drupal user? https://www.drupal.org/project/console


Do you have a Github account ? https://github.com/hechoendrupal/DrupalAppConsole

Drupal Console Commands


 $bin/console container:debug
 $bin/console config:debug

 $bin/console router:debug
 $bin/console router:rebuild

 $bin/console generate:module
 $bin/console generate:controller
 $bin/console generate:form
 $bin/console generate:service
 $bin/console generate:plugin:block
 $bin/console generate:plugin:imageeffect
 $bin/console generate:entity:config
 $bin/console generate:entity:content
 $bin/console generate:command

Console shell mode

LadyBug


Ladybug provides an easy and extensible var_dump / print_r replacement for PHP 5.3+ projects.

https://github.com/raulfraile/ladybug

Symfony

https://github.com/raulfraile/LadybugBundle

Silex

https://github.com/bangpound/ladybug-silex-provider

Drupal

https://www.drupal.org/project/ld

Thanks


Questions & feedback

http://bit.ly/new-php-badcamp