Úč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