Schematic ve spojení s Nette
- Tharos
- Člen | 1030
Ahoj kolegové,
všiml jsem si, že balíček Schematic (repo, dokumentace, představení na Poslední Sobotě) začalo pár lidí používat a napadlo mě, že bych sem mohl dát pár tipů na jeho využití ve spojení s Nette Frameworkem. Doufám, že to pro někoho bude inspirativní a usnadní mu to život. :)
Editoval Tharos (29. 7. 2017 23:40)
- Tharos
- Člen | 1030
Spojení s parametry DI kontejneru
Za užitečný use-case považuji možnost namapovat si na Schematicový
záznam parametry aplikace přítomné v DI kontejneru. Mějme následující
neon
konfiguraci:
parameters:
contactEmail: "someone@gmail.com"
defaultDateTimeFormat: "Y-m-d H:i:s"
anonymousPostsAllowed: false
Snadno ji můžeme namapovat na záznam a zaregistrovat do DI kontejneru například takto:
/**
* @property-read string $contactEmail
* @property-read string $defaultDateTimeFormat
* @property-read bool $anonymousPostsAllowed
*/
class Parameters extends Schematic\Entry
{
/**
* @param Container $container
*/
public function __construct(Container $container)
{
parent::__construct($container->getParameters());
}
}
Tuto třídu pak stačí jen zaregistrovat mezi služby:
services:
- Parameters
A voalá, kdekoliv v aplikaci budete potřebovat konfiguraci aplikace,
stačí si jen vyžádat instanci Parameters
a DI už se postará
o zbytek:
class Posts
{
/**
* @var Parameters
*/
private $parameters;
/**
* @param Parameters $parameters
*/
public function __construct(Parameters $parameters)
{
$this->parameters = $parameters;
}
/**
* @param PostParameters $postParameters
*/
public function createAnonymousPost(PostParameters $postParameters)
{
if (!$this->parameters->anonymousPostsAllowed) {
// ...
}
}
}
Pokud máte konfiguraci s více úrovněmi zanoření, vyloženě na ruku vám půjdou Schematicové asociace.
Editoval Tharos (29. 7. 2017 23:47)
- Tharos
- Člen | 1030
Spojení s Nette formuláři
Za velmi užitečný use-case považuji možnost předávat hodnoty z formuláře k dalšímu zpracování v podobě jednoduchých DTO:
/**
* @property-read string|NULL $author
* @property-read string $subject
* @property-read string $body
*/
class PostParameters extends Schematic\Entry
{
}
Takové DTO pak můžeme použít v Closure
obsluhující formulář a také v API nějaké návazné služby:
/**
* @return Form
*/
protected function createComponentPostForm()
{
$form = $this->formFactory->create();
// $form->addText...
$form->onSuccess[] = function (Form $form) {
$values = $form->getValues();
try {
$this->posts->createAnonymousPost(new PostParameters([
'author' => $values->author,
'subject' => $values->subject,
'body' => $values->body,
]);
// $this->redirect...
} catch (PostException $e) {
// $form->addError...
}
};
return $form;
}
Pokud by vám nevadilo mít DTO natvrdo provázané se strukturou formuláře, šlo by to zapsat ještě stručněji:
$this->posts->createAnonymousPost(
new PostParameters($form->getValues($asArray = true)
);
Pracovat s takovýmito DTO namísto s asociativními poli je doslova návykové… Je to mnohem pohodlnější a IDE tomu rozumí: najednou začne napovídat, upozorňovat při přístupu k neexistující property atp…
Editoval Tharos (30. 7. 2017 7:59)
- Tharos
- Člen | 1030
Spojení se šablonami
Třída EntryViewer
je primárně navržená k inicializaci
obsahu šablon, viz dokumentace.
Chtěl bych trochu víc rozvézt, jak ji v praxi používám…
Typicky to začíná jako jednorázová záležitost:
$this->template->article = EntryViewer::viewEntry($article, function (Article $article) {
return [
'id' => $article->id,
'title' => $article->title,
'author' => EntryViewer::viewEntry($article->author, function (Author $author) {
return [
'id' => $author->id,
'name' => $author->name,
];
}),
];
});
Jakmile ale vznikne potřeba takový článek předávat do šablony na více místech (různé pohledy, komponenty…), vyčlením si konverzi do samostatné služby:
class ArticleConverter extends Object
{
/**
* @param Article $article
* @return array
*/
public function convertArticleToArray(Article $article): array
{
return [
'id' => $article->id,
'title' => $article->title,
];
}
}
$this->template->article = EntryViewer::viewEntry($article, function (Article $article) {
return $this->articleConverter->convertArticleToArray($article);
});
A teď se možná ptáte, kam se poděl autor. :) Mám ve zvyku neřešit v jedné konverzní třídě více asociovaných záznamů. Ať už si to poskládá podle potřeby ten, kdo to celé volá… Takto by vypadal výsledek:
class AuthorConverter extends Object
{
/**
* @param Author $author
* @return array
*/
public function convertAuthorToArray(Author $author): array
{
return [
'id' => $author->id,
'name' => $author->name,
];
}
}
$this->template->article = EntryViewer::viewEntry($article, function (Article $article) {
$convertedArticle = $this->articleConverter->convertArticleToArray($article);
$convertedArticle['author'] = EntryViewer::viewEntry($article->author, function (Author $author) {
return $this->authorConverter->convertAuthorToArray($author);
})
];
return $convertedArticle;
});
Takto si přesně vždy na míru poskládám data, která v šabloně potřebuji. S provázáním šablon s konvertory dokážu žít, protože tyto konvertory používám právě jen ve spojení se šablonami a při refaktoringu mi IDE hned ukáže, kde se daný konvertor používá a v jakých šablonách se tedy pracuje s poli, které vrací.
Vypěstoval jsem si návyk takto šablony inicializovat a jsem s tím maximálně spokojený. Popravdě už si nedovedu dost dobře představit, že bych do šablony poslal nějaký doménový objekt (nedebože třeba entitu) a tam volal nějaké její metody…
Editoval Tharos (2. 8. 2017 17:32)