High Performance Web Apps con PHP e Symfony 2

Giorgio Cefaro
Giorgio CefaroSoftware Development Manager at Hootsuite
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
1 of 76

More Related Content

What's hot(20)

TYPO3 CMS 8.1 - Le novitàTYPO3 CMS 8.1 - Le novità
TYPO3 CMS 8.1 - Le novità
Roberto Torresani297 views
Closure Visto Da VicinoClosure Visto Da Vicino
Closure Visto Da Vicino
davide ficano672 views
TYPO3 CMS 7.6 - Le novitaTYPO3 CMS 7.6 - Le novita
TYPO3 CMS 7.6 - Le novita
Roberto Torresani559 views
J hueryJ huery
J huery
nicrizzo317 views
Perl Template ToolkitPerl Template Toolkit
Perl Template Toolkit
Stefano Rodighiero1.3K views
Battaglia NavaleBattaglia Navale
Battaglia Navale
PaoloVanacore1.2K views
Laravel 7 REST APILaravel 7 REST API
Laravel 7 REST API
Beniamino Ferrari724 views
Java lezione 17Java lezione 17
Java lezione 17
Sergio Ronchi279 views
Introduzione DevOps con AnsibleIntroduzione DevOps con Ansible
Introduzione DevOps con Ansible
Matteo Magni698 views
local::liblocal::lib
local::lib
Flavio Poletti936 views
Net core baseNet core base
Net core base
Beniamino Ferrari1.1K views
Java lezione 18Java lezione 18
Java lezione 18
Sergio Ronchi253 views
Php e database: php mysqlPhp e database: php mysql
Php e database: php mysql
High Secondary School2.8K views

Similar to High Performance Web Apps con PHP e Symfony 2(20)

Codemotion workshopCodemotion workshop
Codemotion workshop
eugenio pombi1.2K views
introduzione a symfony 2 introduzione a symfony 2
introduzione a symfony 2
Riccardo Franconi936 views
Linux Embedded per l'automazioneLinux Embedded per l'automazione
Linux Embedded per l'automazione
Daniele Costarella600 views
Gestione delle dipendenze con ComposerGestione delle dipendenze con Composer
Gestione delle dipendenze con Composer
Massimiliano Arione1K views
TuxIsAliveTuxIsAlive
TuxIsAlive
Claudio Mignanti475 views
Pacchi e pacchettiPacchi e pacchetti
Pacchi e pacchetti
giallu1K views
Drupal - per chi vuole iniziareDrupal - per chi vuole iniziare
Drupal - per chi vuole iniziare
Salvatore Paone3.3K views
PostgreSQL: Point in time recoveryPostgreSQL: Point in time recovery
PostgreSQL: Point in time recovery
Enrico Pirozzi1.4K views
Dominare il codice legacyDominare il codice legacy
Dominare il codice legacy
Tommaso Torti462 views
TYPO3 CMS 7.1 - Le novitaTYPO3 CMS 7.1 - Le novita
TYPO3 CMS 7.1 - Le novita
Roberto Torresani1.5K views
Idp, passo dopo passo!Idp, passo dopo passo!
Idp, passo dopo passo!
Claudio Marotta248 views
PostgrSQL 9.3&9.4 - DjangoVillagePostgrSQL 9.3&9.4 - DjangoVillage
PostgrSQL 9.3&9.4 - DjangoVillage
Miriade Spa859 views
XPages Tips & Tricks, #dd13XPages Tips & Tricks, #dd13
XPages Tips & Tricks, #dd13
Dominopoint - Italian Lotus User Group2.7K views
App Engine + PythonApp Engine + Python
App Engine + Python
Simone Marzola729 views

More from Giorgio Cefaro(10)

High Performance Web Apps con PHP e Symfony 2