High Performance Web Apps con PHP e Symfony 2
di Giorgio Cefaro ed Eugenio Pombi
Giorgio Cefaro
@giorrrgiogiorgiocefaro.com
Eugenio Pombi
@euxpomnerd2business.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/home/sf2/pugx_book
git clone [email protected]:eux/pugx_book_2.git
git tag -l
git checkout {nomeTag}
git stash
nerd2business.net
http://pugx-book.localhost/config.php
tag cap1
git checkout cap1
parameters-dist.ymlparameters: database_driver: pdo_mysql database_host: 127.0.0.1 database_port: ~ database_name: pugx_book database_user: root database_password: sf2
mailer_transport: smtp mailer_host: 127.0.0.1 mailer_user: ~ mailer_password: ~
locale: en secret: ThisTokenIsNotSoSecretChangeIt
composercurl -s https://getcomposer.org/installer | php
composer.jsoncomposer.lock
./composer.phar update
./composer.phar install
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>
/etc/hosts
127.0.0.1 pugx-book.localhost
struttura
bundle
tag cap2
git checkout cap2
il nostro bundlephp 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.3/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.3/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) PUGX\BookBundle\Tests\Controller\DefaultControllerTest::testIndex
InvalidArgumentException: The current node list is empty.
/home/eux/Documents/www/symfony2/pugx_book/vendor/symfony/symfony/src/Symfony/Component/DomCrawler/Crawler.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() insrc/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 - ORMObject Relational Mapping
class Book { public $title; public $author; public $publicationDate}
Doctrine DBAL
PostgreSQL MySQL SQLite
PDO
Doctrine DBAL
Application
Book entity/** * @ORM\Entity * @ORM\Table(name="book") */class Book{ [...] }
src/PUGX/BookBundle/Entity/Book.php
http://symfony.com/it/doc/2.3/book/doctrine.html#book-doctrine-field-types
parameters.yml/app/config/parameters.yml
database_driver: pdo_mysqldatabase_host: 127.0.0.1database_port: ~database_name: pugx_bookdatabase_user: rootdatabase_password: ~
Doctrine commandsphp app/console doctrine:database:create
php app/console doctrine:database:drop --force
php app/console doctrine:schema:create
Doctrine migrationsdirectory:/app/DoctrineMigrations
table:migration_versions
php app/console doctrine:migrations:diffphp app/console doctrine:migrations:migratephp app/console doctrine:migrations:execute --up NNNphp app/console doctrine:migrations:execute --down NNN
Doctrine fixturessrc/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/bookDetail.html.twig
tag cap5
git checkout cap5
doctrine - approfondimentiAuthor è un campo testualeUn autore è associato a più libriSpostiamo 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/** * @ORM\Entity * @ORM\Table(name="author") */class Author{ [...] }
src/PUGX/BookBundle/Entity/Author.php
http://symfony.com/it/doc/2.3/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 - associazioniin src/PUGX/BookBundle/Entity/Author.php
/**
* @ORM\OneToMany(targetEntity="PUGX\BookBundle\Entity\Book", mappedBy="author")
*/
private $books;
aggiungendo i metodi addBook, getBooks, setBooks e removeBook
doctrine - associazioniin src/PUGX/BookBundle/Entity/Book.php
/**
* @ORM\ManyToOne(targetEntity="PUGX\BookBundle\Entity\Author", inversedBy="books")
**/
protected $author;
aggiungendo i metodi setAuthor e getAuthor
doctrine - associazioniOsservazione
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 - migrazionePer poter allineare il database ai cambiamenti, è necessario generare e applicare una nuova migrazione:
php app/console doctrine:migrations:diffphp 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$client->enableProfiler();
[...]
$nbQuery = $client->getProfile()->getCollector('db')->getQueryCount();
$this->assertEquals(1, $nbQuery);
custom repository/** * * @ORM\Entity(repositoryClass="PUGX\BookBundle\Repository\BookRepository") * @ORM\Table(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();
DQLpublic 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
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
extra
git checkout extra1git checkout extra2git checkout extra3
Recommended