Playing Symfony – Slides 2/2

Download Report

Transcript Playing Symfony – Slides 2/2

Doctrine ORM
Dal modello di business alla persistenza
Doctrine ORM
Presentation layer
Application layer
Domain layer
Infrastructure layer
1. Per quanto sia db agnostic,
non potremo non tenere
conto di cosa abbiamo
“sotto”, soprattutto per casi
particolari.
2. A seconda della nostra
strategia e della complessità
del progetto, possiamo
utilizzare l’ORM per
implementare il nostro
modello di business o
semplicemente come livello
anemico di accesso alla
persistenza.
3. E’ indubbio che siamo in
presenza di uno strumento
RAD, con tutte le gioie e i
dolori connessi.
Doctrine ORM
Creiamo il nostro database
php app/console doctrine:database:drop —force!
php app/console doctrine:database:create
Occhio ai default di charset e collation, di solito sono
impostati a Latin1 invece che UTF8. Mojibake in agguato.
Possiamo risolvere in due modi (ipotizzando l’utilizzo di MySQL).
;/path/to/my.cnf!
[mysqld]!
collation-server = utf8_general_ci!
character-set-server = utf8
$ mysql!
> DROP DATABASE IF EXISTS dbname;!
> CREATE DATABASE dbname CHARACTER SET utf8 COLLATE utf8_general_ci;
Doctrine ORM
Le entity
PHP class!
=!
Database table
Doctrine Entity = PHP class + metadata
Doctrine ORM
Definire un’entity
// src/Acme/StoreBundle/Entity/Product.php!
namespace Acme\StoreBundle\Entity;!
!
use Doctrine\ORM\Mapping as ORM;!
annotazioni di classe !
/**!
* @ORM\Entity!
* @ORM\Table!
*/!
class Product!
{!
/**!
* @ORM\Column(type="integer")!
* @ORM\Id!
* @ORM\GeneratedValue(strategy="AUTO")!
*/!
protected $id;!
!
//…!
}
php app/console doctrine:schema:update --force
annotazioni per
definire tipo di
colonna, id, strategia
di generazione
Doctrine ORM
Persistere un’entity sul db
// src/Acme/StoreBundle/Controller/DefaultController.php!
!
// ...!
use Acme\StoreBundle\Entity\Product;!
use Symfony\Component\HttpFoundation\Response;!
!
public function createAction()!
{!
$product = new Product();!
$product->setName('A Foo Bar');!
$product->setPrice('19.99');!
$product->setDescription('Lorem ipsum dolor');!
!
!
$em = $this->getDoctrine()->getManager();!
$em->persist($product);!
$em->flush();!
return new Response('Created product id '.$product->getId());!
}
Doctrine ORM
I repository
public function showAction($id)!
{!
$product = $this->getDoctrine()!
->getRepository('AcmeStoreBundle:Product')!
->find($id);!
!
!
if (!$product) {!
throw $this->createNotFoundException(!
'No product found for id '.$id!
);!
}!
// …!
}
$product
$product
$product
$product
$products
$products
$products
=
=
=
=
=
=
=
$repository->find($id);!
$repository->findOneById($id);!
$repository->findOneByName(‘foo');!
$repository->findOneBy([…]);!
$repository->findAll();!
$repository->findByPrice(19.99);!
$repository->findBy([…]);
Doctrine ORM
Update e delete di un’entity
public function updateAction($id)!
{!
$em = $this->getDoctrine()->getManager();!
$product = $em->getRepository('AcmeStoreBundle:Product')->find($id);!
!
!
!
!
if (!$product) {!
throw $this->createNotFoundException(!
'No product found for id '.$id!
);!
}!
flush() persiste le
$product->setName('New product name!');!
$em->flush();!
modifiche
fetch da repository
$em->remove($product);!
$em->flush();!
return $this->redirect($this->generateUrl('homepage'));!
}
rimuove l’entity dal database
prepara l’entity alla rimozione
(non è ancora eliminata in questo momento)
Doctrine ORM
Query builder e DQL
$repository = $this->getDoctrine()!
->getRepository('AcmeStoreBundle:Product');!
!
$query = $repository->createQueryBuilder('p')!
->where('p.price > :price')!
->setParameter('price', '19.99')!
->orderBy('p.price', 'ASC')!
->getQuery();!
!
$products = $query->getResult();
Fluent interface (per i builder
possiamo accettarla).
!
Costruisce la query elemento
per elemento tramite micrometodi.
Query builder
$em = $this->getDoctrine()->getManager();!
$query = $em->createQuery(!
'SELECT p!
FROM AcmeStoreBundle:Product p!
WHERE p.price > :price!
ORDER BY p.price ASC'!
)->setParameter('price', '19.99');!
Sembra SQL, ma ricordatevi
che quelli nella query sono
oggetti e proprietà, non tabelle
e colonne.
!
$products = $query->getResult();
DQL
Il posto giusto per costruire o definire le nostre query sono i custom repositories,
NON I CONTROLLER.
Doctrine ORM
Mappare le relazioni tra entity (OneToMany)
// src/Acme/StoreBundle/Entity/Category.php!
!
use Doctrine\Common\Collections\ArrayCollection;!
!
class Category!
Una Category Molti prodotti
{!
/**!
* @ORM\OneToMany(targetEntity="Product", mappedBy="category")!
*/!
protected $products;!
!
proprietà di riferimento in Product
public function __construct()!
{!
$this->products = new ArrayCollection();!
}!
}
Sostanzialmente un array, gestisce le collezioni
di oggetti in relazione.
Doctrine ORM
Mappare le relazioni tra entity (ManyToOne)
// src/Acme/StoreBundle/Entity/Product.php!
!
Molti prodotti
class Product!
Una Category
{!
/**!
* @ORM\ManyToOne(targetEntity="Category", inversedBy="products")!
*/!
protected $category;!
}
proprietà di riferimento in
Category
•
Le relazioni possono essere uni o bidirezionali.!
•
Una relazione bidirezionale ha sia l’owning side (dove compare
“inversedBy”) che l’inversed side (dove compare “mappedBy”).!
•
Una relazione unidirezionale ha solamente l’owning side.!
•
Doctrine cercherà cambiamenti nella relazione solamente nell’owning side.
Doctrine ORM
Lifecycle callbacks
/**!
* @ORM\Entity()!
* @ORM\HasLifecycleCallbacks()!
*/!
class Product!
{!
private $createdAt;!
•
•
•
•
•
•
preRemove!
postRemove!
prePersist!
postPersist!
preUpdate!
postUpdate!
•
•
•
•
•
•
postLoad!
loadClassMetadata!
preFlush!
onFlush!
postFlush!
onClear
!
/**!
* @ORM\PrePersist!
*/!
public function setCreatedAtValue()!
{!
$this->createdAt = new \DateTime();!
}!
}
Non usateli per eseguire logica di business! !
Solo semplici (de)normalizzazioni o operazioni di consistenza sui dati.
I Form
Gestire in semplicità l’input dell’utente.
I Form
Come funziona il tutto
FormView
passata a
View
Form
genera
Request
Form::createView()
gestisce
valida
Data object
FormBuilderInterface
FormType
I Form
Dalla parte del controller
public function newAction(Request $request)!
{!
$form = $this->createForm(new TaskType());!
$form->handleRequest($request);!
!
FormInterface
!
if($form->isValid()) {!
// esegui logica dopo la validazione!
}!
FormType
Form gestisce Request
return $this->render(‘AcmeTaskBundle:Default:new.html.twig’, [!
'form' => $form->createView(),!
]);!
}
Form crea FormView
FormView passata alla View
Form::isValid controlla sia la validità dei dati immessi, sia il metodo della Request.
I Form
Rendering del form in twig
{{ form(form) }}
Ovviamente questa funzione twig è da “primo sprint”.
Altre funzioni ci permetteranno di prendere il controllo sul
rendering dei singoli componenti del form:
form_start(), form_end(), form_row(), form_widget(),
form_label(), form_errors()
I Form
La validazione
Presentation layer
Application layer
Domain layer
Infrastructure layer
Domanda: dove, all’interno dell’applicazione, devono
essere validati i dati di input?
I Form
La validazione
Presentation layer
Possibilmente qui
Facoltativamente qui
Application layer
Domain layer
OBBLIGATORIAMENTE!
QUI
Infrastructure layer
Il domain layer è l’unico vero responsabile della
consistenza dei nostri dati.
I Form
La validazione
use Symfony\Component\Validator\Constraints as Assert;!
!
class Task!
{!
/**!
* @Assert\NotBlank()!
*/!
public $description;!
!
Alias all’intero ns delle constraints
$description non può essere vuota
/**!
* @Assert\NotBlank()!
* @Assert\Type("\DateTime")!
*/!
protected $dueDate;!
}
$dueDate deve essere di tipo \DateTime
Se non vi piacciono le annotazioni, potete utilizzare files di
configurazione Yaml, XML o PHP.
I Form
I FormType
// src/Acme/TaskBundle/Form/Type/TaskType.php!
namespace Acme\TaskBundle\Form\Type;!
!
use Symfony\Component\Form\AbstractType;!
use Symfony\Component\Form\FormBuilderInterface;!
!
class TaskType extends AbstractType!
{!
public function buildForm(FormBuilderInterface $builder, array $options)!
{!
$builder!
->add('description')!
->add('dueDate', null, array('widget' => 'single_text'))!
->add('save', 'submit');!
}!
!
public function getName()!
{!
return ‘task';!
}!
Fluent interface per definire gli elementi
}
L’interfaccia richiede il metodo getName().
Il nome deve essere unico e serve per poter richiamare il FormType tramite…
Service container
Dependency Injection made easy
Non abusate del
service container!
Utilizzatelo solamente se avete bisogno della flessibilità che vi
offre. Ricordate sempre che il service container viola la Legge di
Demetra!
In programmazione a oggetti, la LoD afferma che un oggetto non dovrebbe
interagire direttamente con oggetti a cui accede solo indirettamente. Questo
garantisce che un oggetto sia indipendente dalla struttura interna e dalle proprietà
degli altri oggetti, compresi i loro eventuali componenti interni o le loro relazioni.
Service container
Il nostro primo servizio
services:!
my_mailer:!
class:
arguments:
Senza service
Acme\HelloBundle\Mailer!
[sendmail]
class HelloController extends Controller!
{!
container public function sendEmailAction()!
{!
$mailer = new Mailer(‘sendmail’);!
!
$mailer = $this->get(‘my_mailer');!
!
Utilizzando il service
container
}
$mailer->send('[email protected]', ...);!
}!
Service container
Servizi come argomenti di servizi
Il service container può essere parametrizzato
parameters:!
my_mailer.class:
Acme\HelloBundle\Mailer!
my_mailer.transport: sendmail!
newsletter_manager.class: Acme\HelloBundle\Newsletter\NewsletterManager!
!
!
services:!
my_mailer:!
class:
arguments:
!
"%my_mailer.class%"!
[“%my_mailer.transport%"]!
newsletter_manager:!
class:
"%newsletter_manager.class%"!
arguments: ["@my_mailer"]
Il servizio my_mailer passato come argomento
class HelloController extends Controller {!
public function sendEmailAction() {!
$newsletterManager = $this->get(‘newsletter_manager’);!
}!
}
Service container
I tags
services:!
foo.twig.extension:!
class: Acme\HelloBundle\Extension\FooExtension!
tags:!
- { name: twig.extension }
Servizio trattato come estensione di twig
Tags più utilizzati
•
•
•
•
•
•
console.command!
form.type!
kernel.event_listener!
kernel.event_subscriber!
monolog.logger!
twig.extension