Účast na mé první Posobotě předčila veškeré očekávání. Perfektní workshop, skvělé přednášky a úžasní lidé kteří nic netají. Na workshopu jsem díky Filipovi a Chemixovi pronikl do základů Doctrine. Zkusím tyto informace sepsat.
Instalujeme Nette
Ani nemyslete na to že to budete zkoušet bez composeru 🙂
composer create-project nette/sandbox nette-doctrine
nette-doctrine je název složky do které se sandbox vytvoří.
Instalujeme Kdyby/Doctrine
cd nette-doctrine composer require kdyby/doctrine
Pokud máte PHP verzi alespoň 5.4 hned z konzole můžete spustit web server:
php -S localhost:8888 -t www
webserver se spustí na portu 8888. Pokud do prohlížeče zadáte http://localhost:8888 uvidíte stránku Nette Congratulations!
Práda! Můžeme začít spouštět IDE a zažneme kódovat.
PhpStorm a live templaty pro Doctrine
Rozhodně doporučuji PhpStorm do kterého Filip napsal skvělé live templaty. Ušetří spoustu psaní a času.
Live templaty stáhněte a nahrajte do:
<your home directory>\.WebIde<version>\config\templates
Rozhodně doporučuji soubory: Doctrine.xml, Nette.icls, Nette.xml
Po nahrání je nutné znovu spustit PhpStorm.
Vytvoření databáze
Pojďme si vytvořit databázi a uživatele pro přístup k databázi:
CREATE DATABASE `doctrine_devel` COLLATE 'utf8_czech_ci'; CREATE USER 'doctrine'@'localhost' IDENTIFIED BY 'doctrine_pass'; GRANT USAGE ON * . * TO 'doctrine'@'localhost' IDENTIFIED BY 'doctrine_pass' WITH MAX_QUERIES_PER_HOUR 0 MAX_CONNECTIONS_PER_HOUR 0 MAX_UPDATES_PER_HOUR 0 MAX_USER_CONNECTIONS 0; GRANT ALL PRIVILEGES ON `doctrine\_%` . * TO 'doctrine'@'localhost'; FLUSH PRIVILEGES;
Příkaz vykonáme v Admineru: http://localhost:8888/adminer
Klikneme na tlačítko SQL příkaz. Do okna vložíme kód výše a jeho vykonání potvrdíme tlačítkem Provést.
Tím jsme vytvořili databázi doctrine_devel a uživatele doctrine s heslem doctrine_pass.
Zaregistrování Doctrine do Nette
Do config.neon zamergujeme následující kód:
parameters: database: host: localhost dbname: production_db user: production password: production_pass extensions: console: Kdyby\Console\DI\ConsoleExtension events: Kdyby\Events\DI\EventsExtension annotations: Kdyby\Annotations\DI\AnnotationsExtension doctrine: Kdyby\Doctrine\DI\OrmExtension doctrine: dbname : %database.dbname% host : %database.host% user : %database.user% password : %database.password% metadata: App: %appDir%
Do config.local.neon uložíme přístupové údaje do DB pro vývoj u nás na localhostu:
parameters: database: host: localhost dbname: doctrine_devel user: doctrine password: doctrine_pass
Podle dokuentace Kdyby/Doctrine si vytvoříme první entitu:
app/model/Article.php
<?php namespace App; use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity */ class Article extends \Kdyby\Doctrine\Entities\BaseEntity { /** * @ORM\Id * @ORM\Column(type="integer") * @ORM\GeneratedValue */ protected $id; /** * @ORM\Column(type="string") */ protected $title; }
V entitě Article definuje id (celé číslo, které se bude inkrementovat) a title (text).
Vytvoření tabulky z entity
V první řadě ověříme zda funguje Doctrine/Console:
php ./www/index.php
Výstup by měl vypadat takto a měl by zobrazovat všechny dostupné příkazy pro Doctrine:
D:\www\nette-doctrine>php ./www/index.php Nette Framework version 2.2.7 Usage: [options] command [arguments] Options: --help (-h) Display this help message --quiet (-q) Do not output any message --verbose (-v|vv|vvv) Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug --version (-V) Display this application version --ansi Force ANSI output --no-ansi Disable ANSI output --no-interaction (-n) Do not ask any interactive question Available commands: help Displays help for a command list Lists commands dbal dbal:import Import SQL file(s) directly to Database. orm orm:clear-cache:metadata Clear all metadata cache of the various cache drivers. orm:clear-cache:query Clear all query cache of the various cache drivers. orm:clear-cache:result Clear all result cache of the various cache drivers. orm:convert-mapping Convert mapping information between supported formats. orm:convert:mapping Convert mapping information between supported formats. orm:generate-entities Generate entity classes and method stubs from your mapping information. orm:generate-proxies Generates proxy classes for entity classes. orm:generate:entities Generate entity classes and method stubs from your mapping information. orm:generate:proxies Generates proxy classes for entity classes. orm:info Show basic information about all mapped entities orm:schema-tool:create Processes the schema and either create it directly on EntityManager Storage Connection or generate the SQL output. orm:schema-tool:drop Drop the complete database schema of EntityManager Storage Connection or generate the corresponding SQL output. orm:schema-tool:update Executes (or dumps) the SQL needed to update the database schema to match the current mapping metadata. orm:validate-schema Validate the mapping files.
Provedeme validaci:
php ./www/index.php orm:validate-schema
OK, to je v pořádku:
[Mapping] OK - The mapping files are correct. [Database] FAIL - The database schema is not in sync with the current mapping file.
Podíváme se jaký SQL kód nám Doctrine nabízí pro synchronizaci databáze:
php ./www/index.php orm:schema-tool:update --dump-sql
SQL kód pro synchronizaci databáze:
CREATE TABLE article (id INT AUTO_INCREMENT NOT NULL, author_id INT DEFAULT NULL, title VARCHAR(255) NOT NULL, text VARCHAR(255) DEFAULT NULL, INDEX IDX_23A0E66F675F31B (author_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB; CREATE TABLE author (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB; CREATE TABLE tag (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB; CREATE TABLE tag_article (tag_id INT NOT NULL, article_id INT NOT NULL, INDEX IDX_300B23CCBAD26311 (tag_id), INDEX IDX_300B23CC7294869C (article_id), PRIMARY KEY(tag_id, article_id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB; ALTER TABLE article ADD CONSTRAINT FK_23A0E66F675F31B FOREIGN KEY (author_id) REFERENCES author (id); ALTER TABLE tag_article ADD CONSTRAINT FK_300B23CCBAD26311 FOREIGN KEY (tag_id) REFERENCES tag (id) ON DELETE CASCADE; ALTER TABLE tag_article ADD CONSTRAINT FK_300B23CC7294869C FOREIGN KEY (article_id) REFERENCES article (id) ON DELETE CASCADE;
Tento kód můžete vykonat v admineru, neb pokud máte koule můžete ho nechat vykonat přímo z příkazové řádky (hlavouni to nedoporučují):
php ./www/index.php orm:schema-tool:update --force
Nyní když znovu spustíme validaci, vidíme:
D:\www\nette-doctrine>php ./www/index.php orm:validate-schema [Mapping] OK - The mapping files are correct. [Database] OK - The database schema is in sync with the mapping files.
Namapování a databáze jsou OK, pojďme na ukládání a zobrazení dat.
Doctrine – ukládání dat
V HomepagePresenter.php si uděláme add akci na přidání záznamů do entity Article:
<?php namespace App\Presenters; use App\Article; use Nette; use Kdyby; /** * Homepage presenter. */ class HomepagePresenter extends BasePresenter { /** * @inject * @var Kdyby\Doctrine\EntityManager */ public $entityManager; public function actionAdd() { $daoArticle = $this->entityManager->getRepository(Article::getClassName()); $article = new Article(); $article->title= "Slabikář"; $daoArticle->save($article); $article = new Article(); $article->title= "Čítanka"; $daoArticle->save($article); exit(); } }
Definovali jsme $entityManager do které jsme injectli Kdyby\Doctrine\EntityManager.
V actionAdd jsme vytvořili 2 články které uložíme do DB. První s názvem Slabikář a druhý s názvem Čítanka. V DB se můžete přesvědčit že došlo k jejich uložení do tabulky article.
Doctrine výpis dat
Velice podobným způsobem jako jsme ukládali data je budeme vypisovat. Opět budeme potřebovat EntityManager:
<?php namespace App\Presenters; use App\Article; use Nette; use Kdyby; /** * Homepage presenter. */ class HomepagePresenter extends BasePresenter { /** * @inject * @var Kdyby\Doctrine\EntityManager */ public $entityManager; public function renderDefault() { $daoArticle = $this->entityManager->getDao(Article::getClassName()); $this->template->articles = $daoArticle->findAll(); } }
v šabloně tyto články zobrazíme:
{block content} {foreach $articles as $article} {$article->title}<br/> {/foreach}
Jednoduché že ano?
Přidání nového položky do entity
Naše aplikace je známá po celém světě a používají ji milony lidí kteří píší podměty že jim pouze název článku nestačí a chtěli by ještě text článku 🙂 Vyhovíme jim a přidáme do entity Article ještě položku text.
Nyní nám perfektně poslouží Filipovo live templaty. Otevřeme si soubor Article.php a pod proměnou private $title odřádkujeme a napíšeme: col <tab> (napíšeme col a stiskneme klávesu Tab). Ide nám předvygeneruje následující kód:
/** * @ORM\Column(type="string", nullable=TRUE) * @var string */ protected $;
ve kterém se pohybujeme klávesou Tab. Napíšeme název proměnné (text) a definujeme typ (text). Výsledek vypadá takto:
/** * @ORM\Column(type="text", nullable=TRUE) * @var text */ protected $text;
Provedeme validaci:
php ./www/index.php orm:validate-schema
D:\www\nette-doctrine>php ./www/index.php orm:validate-schema [Mapping] OK - The mapping files are correct. [Database] FAIL - The database schema is not in sync with the current mapping file.
která nám oznámí že nemáme synchronizovanou databázi. Podíváme se jaké úpravy databáze nám Doctrine nabízí:
D:\www\nette-doctrine>php ./www/index.php orm:schema-tool:update --dump-sql ALTER TABLE article ADD text LONGTEXT DEFAULT NULL;
Kód vykonáme v admineru, nebo 🙂
php ./www/index.php orm:schema-tool:update --force
Následná validace by měla být OK. Tím máme přidán sloupec text a můžeme do něho rovnou ukládat data. Vytáhneme námi uložené publikace a donastavíme jim text:
public function actionUpdate() { $daoArticle = $this->entityManager->getRepository( Article::getClassName() ); $article = $daoArticle->findOneBy( array( "title" => "Slabikář" ) ); $article->text = "Text knihy slabikář"; $article = $daoArticle->findOneBy( array( "title" => "Čítanka" ) ); $article->text = "Text knihy čítanka"; $this->entityManager->flush(); exit(); }
Upravíme šablonu abychom vypisovali text:
{block content} {foreach $articles as $article} <h2>{$article->title}</h2> <p>{$article->text}</p> {/foreach}
Vyšší liga – vazby mezi entitami
Toto jsme splodili na Workshopu:
Article.php
<?php namespace App; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity */ class Article extends \Kdyby\Doctrine\Entities\BaseEntity { /** * @ORM\Id * @ORM\Column(type="integer") * @ORM\GeneratedValue */ protected $id; /** * @ORM\Column(type="string") */ protected $title; /** * @ORM\Column(type="string", nullable=TRUE) * @var string */ protected $text; /** * @ORM\ManyToOne(targetEntity="\App\Author", inversedBy="articles", cascade={"persist"}) * @var Author */ protected $author; /** * @ORM\ManyToMany(targetEntity="\App\Tag", mappedBy="articles", cascade={"persist"}, orphanRemoval=true) * @var \App\Tag[]|\Doctrine\Common\Collections\ArrayCollection */ protected $tags; function __construct() { $this->tags = new ArrayCollection(); } public function addTag(Tag $tag) { $tag->addArticle($this); $this->tags->add( $tag ); } }
Author.php
<?php namespace App; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity * @property $name */ class Author extends \Kdyby\Doctrine\Entities\BaseEntity { /** * @ORM\Id * @ORM\Column(type="integer") * @ORM\GeneratedValue */ protected $id; /** * @ORM\Column(type="string") */ protected $name; /** * @ORM\OneToMany(targetEntity="\App\Article", mappedBy="author", cascade={"persist"}) * @var Article[]|\Doctrine\Common\Collections\ArrayCollection */ protected $articles; function __construct() { $this->articles = new ArrayCollection(); } public function addArticle(Article $article) { $this->articles->add( $article ); } }
Tag.php
<?php namespace App; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity * @property $name */ class Tag extends \Kdyby\Doctrine\Entities\BaseEntity { /** * @ORM\Id * @ORM\Column(type="integer") * @ORM\GeneratedValue */ protected $id; /** * @ORM\Column(type="string") */ protected $name; /** * @ORM\ManyToMany(targetEntity="\App\Article", inversedBy="tags", cascade={"persist"}) * @var \App\Article[]|\Doctrine\Common\Collections\ArrayCollection */ protected $articles; function __construct() { $this->articles = new ArrayCollection(); } public function addArticle(Article $article) { $this->articles->add( $article ); } }
HomepagePresenter.php
<?php namespace App\Presenters; use App\Author; use App\Tag; use Nette, App\Model; use App\Article; /** * Homepage presenter. */ class HomepagePresenter extends BasePresenter { /** * @inject * @var \Kdyby\Doctrine\EntityManager */ public $EntityManager; public function renderDefault() { $this->template->anyVariable = 'any value'; $dao = $this->EntityManager->getRepository(Article::getClassName()); $this->template->articles = $dao->findAll(); } public function actionAdd() { $daoArticle = $this->EntityManager->getRepository(Article::getClassName()); $daoAuthor = $this->EntityManager->getRepository(Author::getClassName()); //$author = new Author(); //$author->name = "Pokusne jmeno"; $author = $daoAuthor->findOneBy( array("name" => "Pokusne jmeno")); $article = new Article(); $article->title = "titulke sntagem"; $article->text = "text novinky s tagem"; $article->author = $author; $author->addArticle($article); $tag = new Tag(); $tag->name = "kniha"; $article->addTag($tag); $tag = new Tag(); $tag->name = "kniha dvojita"; $article->addTag($tag); $daoArticle->save($article); exit(); } }
Šablona:
{block content} {foreach $articles as $article} {$article->title}<br> {$article->author->name}<br/> {dump $article->tags} Tags: {if $article->tags}{foreach $article->tags as $tag}{$tag->name}, {/foreach}{/if}<br/> <hr/> {/foreach} {/block}
Celé databázové schéma smažeme a vytvoříme znovu:
php ./www/index.php orm:schema-tool:drop --force php ./www/index.php orm:schema-tool:create
Celou ukázku jsem nahrál na GitHub: https://github.com/venca-x/nette-sandbox-doctrine