High Performance Web
Apps con PHP
e Symfony 2

di Giorgio Cefaro ed Eugenio Pombi
Giorgio Cefaro




@giorrrgio
giorgiocefaro.com
Eugenio Pombi




@euxpom
nerd2business.net
Symfony 2

First, Symfony2 is a reusable set of standalone,
decoupled, and cohesive PHP components that solve
common web development problems. Then, based on
these components, Symfony2 is also a full-stack web
framework.
                                     Fabien Potencier
Request > Response
http://symfony.com/download
get the git repo
git clone git@bitbucket.org:eux/pugx_book.git

git tag -l

git checkout {nomeTag}

git stash

                            nerd2business.net
http://pugx-book.localhost/config.php
tag cap1


git checkout cap1
parameters-dist.yml
parameters:
  database_driver: pdo_mysql
  database_host: 127.0.0.1
  database_port: ~
  database_name: pugx_book
  database_user: root
  database_password: ~

  mailer_transport: smtp
  mailer_host:     127.0.0.1
  mailer_user:     ~
  mailer_password: ~

  locale:      en
  secret:      ThisTokenIsNotSoSecretChangeIt
composer
curl -s https://getcomposer.org/installer | php

composer.json
composer.lock

./composer.phar update
./composer.phar install

minimum-stability
virtual host
<VirtualHost *:80>
 ServerName pugx-book.localhost
 DocumentRoot "/PATH TO PROJECT/pugx_book/web"
 DirectoryIndex index.php
 <Directory "/PATH TO PROJECT/pugx_book/web">
    AllowOverride All
    Require all granted
 </Directory>
</VirtualHost>

PATH TO PUGX PROJECT
/etc/hosts


127.0.0.1   pugx-book.localhost
struttura
bundle
tag cap2


git checkout cap2
il nostro bundle
   php app/console generate:bundle
         --namespace=PUGX/BookBundle
         --format=yml
Bundle namespace [PUGX/BookBundle]:
Bundle name [PUGXBookBundle]:
Target directory [/home/USERNAME/PATH/pugx_book/src]:
Configuration format (yml, xml, php, or annotation) [yml]:

Do you want to generate the whole directory structure [no]? yes
DefaultControllerTest.php

DefaultControllerTest.php

src/PUGX/BookBundle/Tests/Controller/
     DefaultControllerTest.php



phpunit -c app/
front controller
/web/app.php
/web/app_dev.php

/app/config/config.yml
/app/config/config_dev.yml
/app/config/config_prod.yml
/app/config/config_test.yml
routing
/app/config/routing.yml

pugx_book:
   resource: "@PUGXBookBundle/Resources/config/routing.yml"
   prefix:   /




/src/PUGX/BookBundle/Resources/config/routing.yml

pugx_book_homepage:
    pattern: /
    defaults: { _controller: PUGXBookBundle:Default:index }
controller

In genere costituito da una classe che raggruppa una serie
di azioni definite attraverso metodi pubblici.



Il nostro primo controller:
src/PUGX/BookBundle/Controller/DefaultController.php
twig
return $this->render('PUGXBookBundle:Default:index.html.twig');


PUGXBookBundle: <NomeVendor><NomeBundle>
Default:         <NomeController>
index.html.twig: <NomeTemplate>



//src/PUGX/BookBundle/Resources/views/Default/index.html.twig
Hello world!



http://symfony.com/it/doc/2.1/book/templating.html
yay! live coding
//src/PUGX/BookBundle/Tests/Controller/DefaultControllerTest.php

$this->assertTrue($client->getResponse()->isSuccessful());
$this->assertRegExp("/Welcome/i", $crawler->filter('h1')->text());
$this->assertTrue($crawler->filter('p')->count() > 0);

//nav bar
$this->assertTrue($crawler->filter('.navbar')->count() > 0);
$this->assertEquals(1, $crawler->filter('.navbar > li')->count());
$this->assertRegExp("/home/i", $crawler->filter('.navbar > li:nth-
child(1)')->text());
PHPUnit asserts

verifichiamo che la risposta sia valida
$this->assertTrue($client->getResponse()->isSuccessful());


ci assicuriamo che l’elemento h1 contenga la parola "Welcome":
$this->assertRegExp("/Welcome/i", $crawler->filter('h1')->text());


$crawler è un'istanza del componente DomCrawler di Sf2, permette di manipolare
documenti XML e HTML attraverso xpath e css selector:
http://symfony.com/doc/2.1/components/dom_crawler.html
PHPUnit asserts
ci assicuriamo che ci sia almeno un elemento p nella pagina di risposta:
$this->assertTrue($crawler->filter('p')->count() > 0);


verifichiamo che ci sia un oggetto del DOM che abbia la classe css navbar:
$this->assertTrue($crawler->filter('.navbar')->count() > 0);


verifichiamo che la navbar contenga un solo elemento li:
$this->assertEquals(1, $crawler->filter('.navbar > li')->count());


infine verifichiamo che il primo elemento li di navbar contenga il testo home
$this->assertRegExp("/home/i", $crawler->filter('.navbar > li:nth-
child(1)')->text());
DefaultControllerTest.php

DefaultControllerTest.php

src/PUGX/BookBundle/Tests/Controller/
     DefaultControllerTest.php



phpunit -c app/
DefaultControllerTest.php

DefaultControllerTest.php

TEST ROSSI
There was 1 error:



1) PUGXBookBundleTestsControllerDefaultControllerTest::testIndex
InvalidArgumentException: The current node list is empty.


/home/eux/Documents/www/symfony2/pugx_book/vendor/symfony/symfony/src/Symfony/Component/DomCrawler/Cra
wler.php:468
/home/eux/Documents/www/symfony2/pugx_book/src/PUGX/BookBundle/Tests/Controller/DefaultControllerTest.php:
16



FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
make it GREEN
//src/PUGX/BookBundle/Resources/views/Default/index.html.twig
<!DOCTYPE html>
<html>
 <head>
   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
   <title>PUGX Book</title>
 </head>
 <body>
   <div id="sidebar">
      <ul class="navbar">
         <li><a href="/">Home</a></li>
      </ul>
   </div>
   <div id="content">
      <h1>Welcome on PUGX Book</h1>
        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus ultrices, nisi quis porta
fermentum, magna ligula suscipit metus, quis blandit leo urna non diam. Sed non dui dui, quis porttitor
massa. Phasellus convallis porta leo, sed vehicula eros ultrices sit amet.</p>
   </div>
  </body>
</html>
DefaultControllerTest.php

DefaultControllerTest.php

src/PUGX/BookBundle/Tests/Controller/
     DefaultControllerTest.php



phpunit -c app/
DefaultControllerTest.php

DefaultControllerTest.php

TEST VERDI
PHPUnit 3.6.11 by Sebastian Bergmann.
Configuration read from /home/giorgio/Progetti/codemotion/pugx_book/app/phpunit.xml.dist


.


Time: 11 seconds, Memory: 17.50Mb


OK (1 test, 6 assertions)
tag cap3


git stash

git checkout cap3
DefaultControllerTest.php

un elenco di libri


Vogliamo aggiungere una pagina che contiene
una lista di libri, ognuno con informazioni
relative all'autore e alla data di pubblicazione.
DefaultControllerTest.php

Il test


Scriviamo innanzi tutto il test testBooks() in
src/PUGX/BookBundle/Tests/Controller/DefaultControllerTest.
php
DefaultControllerTest.php

l'entità Book


La struttura dati che rappresenta il nostro libro
è l'entità Book, definita in:
src/PUGX/BookBundle/Entity/Book.php
DefaultControllerTest.php

la nuova rotta


Aggiungiamo una nuova rotta che risponderà
all'url /books
DefaultControllerTest.php

la nuova action


Aggiungiamo un metodo booksAction al
DefaultController in cui ci limiteremo a creare
tre instanze di Book, passandole al twig
DefaultControllerTest.php

il nuovo template


Creiamo un nuovo twig in
src/PUGX/BookBundle/Resources/views/Default/books.html.twig
e stampiamo la lista di libri in una tabella

Ereditarietà dei template
tag cap4


git checkout cap4
Doctrine - ORM
     Object Relational Mapping




        class Book
        {
          public $title;
          public $author;
          public $publicationDate
        }
Doctrine DBAL
                Application

              Doctrine DBAL

                  PDO




 PostgreSQL       MySQL       SQLite
Book entity
/**
 * @ORMEntity
 * @ORMTable(name="book")
 */
class Book
{ [...] }


src/PUGX/BookBundle/Entity/Book.php

http://symfony.com/it/doc/2.1/book/doctrine.html#book-doctrine-field-types
parameters.yml
/app/config/parameters.yml

database_driver:  pdo_mysql
database_host:   127.0.0.1
database_port:    ~
database_name:    pugx_book
database_user:     root
database_password: ~
Doctrine commands
php app/console doctrine:database:create

php app/console doctrine:database:drop --force

php app/console doctrine:schema:create
Doctrine migrations
directory:
/app/DoctrineMigrations

table:
migration_versions

php app/console doctrine:migrations:diff
php app/console doctrine:migrations:migrate
php app/console doctrine:migrations:execute --up NNN
php app/console doctrine:migrations:execute --down NNN
Doctrine fixtures
src/PUGX/BookBundle/DataFixtures/ORM/LoadBookData.php


php app/console doctrine:fixtures:load
DefaultControllerTest.php
/src/PUGX/BookBundle/Tests/Controller/DefaultControllerTest.php

Controlliamo che i libri siano in ordine alfabetico
Query con doctrine
/src/PUGX/BookBundle/Controller/DefaultController.php

public function booksAction()
{
  $em = $this->getDoctrine()->getManager();
  $books = $em->getRepository('PUGXBookBundle:Book')->findBy(
      array(),
      array('title' => 'ASC')
  );
  return $this->render('PUGXBookBundle:Default:books.html.twig',
     array('books' => $books)
  );
}
DefaultControllerTest.php
/src/PUGX/BookBundle/Tests/Controller/DefaultControllerTest.php

Aggiungiamo il test per una pagina di dettaglio (o 404)
rotta - action - template
/src/PUGX/BookBundle/Resources/config/routing.yml

/src/PUGX/BookBundle/Controller/DefaultController.php

/src/PUGX/BookBundle/Resources/views/Default/bookDeta
il.html.twig
tag cap5


git checkout cap5
doctrine - approfondimenti
Author è un campo testuale
Un autore è associato a più libri
Spostiamo l'autore in una entità separata.


php app/console doctrine:generate:entity --
entity="PUGXBookBundle:Author" --fields="name:
string(255) surname:string(255) bio:text"
Author entity
/**
 * @ORMEntity
 * @ORMTable(name="author")
 */
class Author
{ [...] }


src/PUGX/BookBundle/Entity/Author.php

http://symfony.com/it/doc/2.1/book/doctrine.html#book-doctrine-field-types
doctrine - associazioni

Per definire l'associazione tra Book e Author
utilizziamo le annotazioni di Doctrine
OneToMany(lato Author) e ManyToOne(lato
Book)
doctrine - associazioni
in src/PUGX/BookBundle/Entity/Author.php
/**
* @ORMOneToMany(targetEntity="PUGXBookBundleEntityBook",
mappedBy="author")
*/
private $books;


aggiungendo i metodi addBook, getBooks,
setBooks e removeBook
doctrine - associazioni
in src/PUGX/BookBundle/Entity/Book.php
/**
* @ORMManyToOne(targetEntity="PUGXBookBundleEntityAuthor",
inversedBy="books")
**/
protected $author;


aggiungendo i metodi setAuthor e getAuthor
doctrine - associazioni
Osservazione

Doctrine nasconde la logica dell'associazione a
livello database, in cui l'associazione è definita
da un campo author_id della tabella book e una
foreign key.
La parte inversa dell'associazione è ricostruita
automaticamente da Doctrine.
doctrine - migrazione
Per poter allineare il database ai cambiamenti, è
necessario generare e applicare una nuova migrazione:

php app/console doctrine:migrations:diff
php app/console doctrine:migrations:migrate
doctrine - fixtures
/src/PUGX/BookBundle/DataFixtures/ORM/LoadAuthorData.php

elemento
$this->addReference('author-beck', $author);

riferimento
$this->getReference('author-beck')


public function getOrder() {
  return 1;
}
template
/src/PUGX/BookBundle/Resources/views/Default/books.html.twig

/src/PUGX/BookBundle/Resources/views/Default/bookDetail.html.twig


{{ book.author.name }} {{ book.author.surname }}
problema!!!
causa
profiler
$nbQuery =
 $client->getProfile()->getCollector('db')-
>getQueryCount();

$this->assertEquals(1, $nbQuery);
custom repository
/**
 *
 * @ORMEntity(repositoryClass="PUGXBookBundleRepositoryBookRepository")
 * @ORMTable(name="book")
 */
class Book


/src/PUGX/BookBundle/Repository/BookRepository.php

/src/PUGX/BookBundle/Tests/Repository/BookRepositoryTest.php

/src/PUGX/BookBundle/Controller/DefaultController.php
$books = $em->getRepository('PUGXBookBundle:Book')-
>findAllWithAuthors();
DQL
public function findAllWithAuthors()
{
  $query = $this->getEntityManager()
          ->createQuery('
             SELECT b, a
            FROM PUGXBookBundle:Book b
            INNER JOIN b.author a
            ORDER BY b.title ASC
  ');

    return $query->getResult();
}
tag cap6


git checkout cap6
DefaultControllerTest

metodo

testCreate
form type


/src/PUGX/BookBundle/Form/Type/BookType.php
routing problem
/src/PUGX/BookBundle/Resources/config/routing.yml

pugx_book_detail:
  pattern: /books/{bookId}
  defaults: { _controller: PUGXBookBundle:Default:bookDetail }



pugx_book_create:
  pattern: /books/create
  defaults: { _controller: PUGXBookBundle:Default:bookCreate }
solution
/src/PUGX/BookBundle/Resources/config/routing.yml

pugx_book_detail:
  pattern: /books/{bookId}
  defaults: { _controller: PUGXBookBundle:Default:bookDetail }
  requirements:
    bookId: d+

pugx_book_create:
  pattern: /books/create
  defaults: { _controller: PUGXBookBundle:Default:bookCreate }
form action
/src/PUGX/BookBundle/Controller/DefaultController.php



public function bookCreateAction(Request $request) {
 ...
}
twig form

/BookBundle/Resources/views/Default/bookCreate.html.
twig
flash messages

$this->get('session')->getFlashBag()->add('notice', 'Book successfully created');


{% for flashMessage in app.session.flashbag.get('notice') %}
 <div class="notice">
  {{ flashMessage }}
 </div>
{% endfor %}
tearDown

Eliminazione del record inserito con il test
tag cap7


git checkout cap7
security
tag extra1


git checkout extra1

High Performance Web Apps con PHP e Symfony 2