Schematic ve spojení s Nette

před 2 lety

Tharos
Člen | 1042
+
+3
-

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)

před 2 lety

Tharos
Člen | 1042
+
-2
-

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)

před 2 lety

Tharos
Člen | 1042
+
+2
-

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)

před 2 lety

Tharos
Člen | 1042
+
+1
-

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)