7. díl - Jednoduchý redakční systém v Nette - Administrace

PHP Nette Framework Základy Jednoduchý redakční systém v Nette - Administrace

Vítám vás všechny u pokračování seriálu tutoriálů o tvorbě webových aplikací v PHP frameworku Nette. V minulém díle jsme již vytvořili základní strukturu pro výpis článků kromě šablon, které dnes přidáme. Tím výpis zprovozníme a budeme pokračovat s tvorbou jejich administrace.

Šablony

Nyní je tedy čas podívat se na zoubek šablonám (templates).

Opět zde již můžeme smazat složku app/templates/Homepage/, protože stejně jako HomepagePresenter ji už nebudeme dále potřebovat.

app/templates/@layout.latte

Samozřejmě nezačneme ničím jiným než úpravou celkového vzhledu naší aplikace, který v tomto případě zajišťuje Latte šablona @layout.latte, opět předpřipravená v sandboxu, ze kterého vycházíme.

{**
 * @param string   $basePath cesta k webovému obsahu např. CSS souborům
 * @param array    $flashes  pole zpráv
 *}

<!DOCTYPE html>
<html lang="cs-cz">
        <head>
                <meta charset="UTF-8" />
                <title>{include title|striptags}</title>
                <meta name="description" content="{include description|striptags}" />

                {block css}
                <link rel="stylesheet" href="{$basePath}/css/style.css" type="text/css"/>
                {/block}

                {block head}{/block}
        </head>

        <body>
                <header>
                        <h1>Jednoduchý redakční systém v Nette</h1>
                </header>

                {* Výpis zpráv. *}
                <p n:foreach="$flashes as $flash" class="message">{$flash->message}</p>

                <nav>
                        <ul>
                                <li><a n:href=":Core:Article:">Úvod</a></li>
                                <li><a href="#">Seznam článků</a></li>
                                <li><a href="#">Kontakt</a></li>
                        </ul>
                </nav>
                <br clear="both" />

                <article>
                        <header>
                                <h1>{include title}</h1>
                        </header>
                        <section>
                                {include content} {* Vložení obsahu do šablony. *}
                        </section>
                </article>

                <footer>
                        <p>Ukázkový tutoriál pro jednoduchý redakční systém v Nette z programátorské sociální sítě
                        <a href="http://www.itnetwork.cz" target="_blank">itnetwork.cz</a></p>
                </footer>

                {block scripts}{/block}
        </body>
</html>

app/CoreModule/templates/Article/default.latte

Nakonec přidáme šablonou pro výchozí akci (renderDefault) našeho ArticlePresenter, která je relativně jednoduchá:

{define title}{$article->title}{/define}
{define description}{$article->description}{/define}
{block content}
{$article->content|noescape}

Nyní si již můžete zkusit web spustit a vidět úvodní stránku. Je to jistě příjemný pocit po tom, co jsme změnili tolik kódu, že? :)

Úvodní stránka redakčního systému v Nette frameworku

Úplný základ máme zprovozněný, nyní se podíváme hlouběji do CoreModule a to konkrétně na administraci článků ;)

Presentery

Jelikož model máme již nachystaný z minula, začneme rovnou od presenterů.

app/CoreModule/presenters/ArticlePresenter.php

Jelikož v Nette můžeme mít více akcí v jednom presenteru, budeme pokračovat v rozšiřování naší třídy ArticlePresenter a přidáme do ní následující metody dalších akcí:

<?php

namespace App\CoreModule\Presenters;

use App\CoreModule\Model\ArticleManager;
use App\Presenters\BasePresenter;
use Nette\Application\BadRequestException;
use Nette\Application\UI\Form;
use Nette\Database\UniqueConstraintViolationException;
use Nette\Utils\ArrayHash;

/**
 * Zpracovává práci s články.
 * @package App\CoreModule\Presenters
 */
class ArticlePresenter extends BasePresenter
{
        /** Konstanta s hodnotou URL výchozího článku. */
        const DEFAULT_ARTICLE_URL = 'uvod';

        /** @var ArticleManager Instance třídy modelu pro práci s články. */
        protected $articleManager;

        /**
         * Konstruktor s injektovaným modelem pro práci s články.
         * @param ArticleManager $articleManager automaticky injektovaná třída modelu pro práci s články
         */
        public function __construct(ArticleManager $articleManager)
        {
                parent::__construct();
                $this->articleManager = $articleManager;
        }

        /** Načte a vykreslí článek článek do šablony podle jeho URL.
         * @param string $url URL článku
         * @throws BadRequestException Jestliže článek s danou URL nebyl nalezen.
         */
        public function renderDefault($url)
        {
                if (!$url) $url = self::DEFAULT_ARTICLE_URL; // Pokud není zadaná URL, vykreslí se výchozí článek.
                // Pokusí se načíst článek s danou URL a pokud nebude nalezen, vyhodí chybu 404.
                if (!($article = $this->articleManager->getArticle($url))) throw new BadRequestException();
                $this->template->article = $article; // Předá článek do šablony.
        }

        /** Vykreslí seznam článků do šablony. */
        public function renderList()
        {
                $this->template->articles = $this->articleManager->getArticles();
        }

        /**
         * Odstraní článek.
         * @param string $url
         */
        public function actionRemove($url)
        {
                $this->articleManager->removeArticle($url);
                $this->flashMessage('Článek byl úspěšně odstraněn.');
                $this->redirect(':Core:Article:list');
        }

        /**
         * Vykresluje editaci článku podle jeho URL.
         * @param string $url URL adresa článku, který editujeme, pokud není zadána, vytvoří se nový
         */
        public function actionEditor($url)
        {
                // Pokud byla zadána URL, pokusí se článek načíst a předat jeho hodnoty do editačního formuláře, jinak vypíše chybovou hlášku.
                if ($url) ($article = $this->articleManager->getArticle($url)) ? $this['editorForm']->setDefaults($article) : $this->flashMessage('Článek nebyl nalezen.');
        }

        /**
         * Vrátí formulář pro editor článků.
         * @return Form formulář pro editor článků
         */
        protected function createComponentEditorForm()
        {
                $form = new Form;
                $form->addHidden('article_id');
                $form->addText('title', 'Titulek')->setRequired();
                $form->addText('url', 'URL')->setRequired();
                $form->addText('description', 'Popisek')->setRequired();
                $form->addTextArea('content', 'Obsah');
                $form->addSubmit('submit', 'Uložit článek');
                $form->onSuccess[] = [$this, 'editorFormSucceeded'];
                return $form;
        }

        /**
         * Funkce se vykonaná při úspěsném odeslání formuláře; zpracuje hodnoty formuláře.
         * @param Form $form formulář editoru
         * @param ArrayHash $values odeslané hodnoty formuláře
         */
        public function editorFormSucceeded($form, $values)
        {
                try {
                        $this->articleManager->saveArticle($values);
                        $this->flashMessage('Článek byl úspěšně uložen.');
                        $this->redirect(':Core:Article:', $values->url);
                } catch (UniqueConstraintViolationException $ex) {
                        $this->flashMessage('Článek s touto URL adresou již existuje.');
                }
        }
}

Zde bych upozornil především na způsob předávání výchozích hodnost do editačního formuláře a to, že se to provádí v actionEditor, nikoliv v renderEditor metodě a také si všimněte použití Nette forms pro vytvoření editačního formuláře, což nám v šabloně ušetří spoustu kódu.

app/CoreModule/presenters/AdministrationPresenter.php

Vytvoříme si prázdný presenter pro administrační rozhraní, protože v tuto chvíli mu v podstatě nepotřebujeme do šablony nic předávat:

<?php

namespace App\CoreModule\Presenters;

use App\Presenters\BasePresenter;

/**
 * Zpracovává vykreslování administrační sekce.
 * @package App\CoreModule\Presenters
 */
class AdministrationPresenter extends BasePresenter
{

}

Příště si přidáme ještě ContactPresenter a poté šablony, kterými administraci článků v našem redakčním systému dokončíme :)


 

Stáhnout

Staženo 185x (651.49 kB)
Aplikace je včetně zdrojových kódů v jazyce PHP

 

  Aktivity (1)

Článek pro vás napsal Jindřich Máca
Avatar
Autor se věnuje převážně webovým technologiím, ale má velkou zálibu ve všem vědeckém, nejen ze světa IT. :-)

Jak se ti líbí článek?
Celkem (2 hlasů) :
55555


 



 

 

Komentáře
Zobrazit starší komentáře (9)

Avatar
Dominik Gavrecký:

createComponent ti vytvorí komponentu a EditorForm už je názov tej komponenty. Ak by ťa zaujímalo niečo viac tak https://doc.nette.org/cs/2.3/forms

Odpovědět  +1 23. února 15:00
Hlupák nie je ten kto niečo nevie, hlupákom sa stávaš v momente keď sa na to bojíš opýtať.
Avatar
Jindřich Máca
Tým ITnetwork
Avatar
Jindřich Máca:

Ahoj,

je to přesně tak, jak říká Dominik Gavrecký.

  • Mezi action*() a render*() je jediný technický rozdíl a to, že action*() se pro danou akci (to co se dosadí za *) presenteru vykonává dřív než render*(). Potom je tady nějaká konvence (ne pravidlo), že v render*() by se měli předávat data do šablony a obecně řešit operace ohledně vykreslování stránky, zato v action*() by se mělo řešit například plnění komponent (formuláře) výchozími daty nebo přesměrování (logicky dřív, než se začne stránka vykreslovat). Dále nic nezabraňuje tomu, aby existovala např. actionDefault() a renderDefault() najednou v tom samém presenteru. Poté se akorát vykonávají v daném pořadí a měli by (nemusí) dodržovat výše popsané rozdělení zodpovědností.
  • K úplnému pochopení odpovědi na ten druhý dotay, bude možná lepší nejdříve objasnit, jak funguje vztah prezenterů a komponent. Každý presenter může obsahovat libovolné množství různých komponent. Jejich instance poté uchovává ve svém "vnitřním poli" (implementuje totiž interface ArrayAccess). A jak dané instance získává? Při jeho životním cyklu (viz. https://doc.nette.org/…3/presenters#…), konkrétně po metodě startup(), je vytváří podle všech metod z předpisem createComponen­t*() a pojmenovává je podle jejich názvu, tedy to co je v tomto případě * (pozor, mění se tady počáteční písmeno názvu na malé a důrazně se to rozlišuje). Poté, tedy už v libovolné action*() případně i render*(), je tedy k dispozici instance dané komponenty přes ono "vnitřní pole" presenteru a to právě pomocí $this[*] (kde * je právě název komponenty, pouze s počátečním malým písmenem). Ale znalost práce s třídami implementujícími ArrayAccess se již předpokládá ze standardního OOP v PHP. :)

Tak to bylo docela vyčerpávající, ale pokud by byli další dotazy, neváhej se zeptat. ;)

 
Odpovědět 23. února 15:13
Avatar
saavikam
Člen
Avatar
saavikam:

Děkuju, už je mi to jasné. Andrea

 
Odpovědět 25. února 18:08
Avatar
rosatislav
Člen
Avatar
rosatislav:

Zdravím, celkem by mně zajímalo tohle:

try {
                        $this->articleManager->saveArticle($values);
                        $this->flashMessage('Článek byl úspěšně uložen.');
                        $this->redirect(':Core:Article:', $values->url);
                } catch (UniqueConstraintViolationException $ex) {
                        $this->flashMessage('Článek s touto URL adresou již existuje.');
                }

přesněji článek s touto URL adresou již existuje.
Nedokážu moc pochopit, jak může k takové chybě dojít. Chápu, že je to asi myšleno, že pokud by při editaci někdo změnil kolonku URL adresy za stejnou, která v databázi už je, tak by to nemělo projít. Nikde jsem ovšem nenašel ošetření, kde by se ověřovalo, zda-li jsme nezadali takovou url, která by již byla použita (pokud se to teda neřeší až v dalších článcích)

 
Odpovědět 14. září 10:23
Avatar
rosatislav
Člen
Avatar
Odpovídá na rosatislav
rosatislav:

Aha, tak už jsem na to přišel... taková ostuda :D je to dáno už z definice v předchozím článku

ADD UNIQUE KEY `url` (`url`);

každopádně, pokud bych se mohl zeptat, tak co dělá přesně třída UniqueConstra­intViolationEx­ception?

Editováno 14. září 11:18
 
Odpovědět 14. září 11:17
Avatar
Petr Linhart
Člen
Avatar
Odpovídá na rosatislav
Petr Linhart:

UniqueConstra­intViolationEx­ception je výjimka, kterou ti vyhodí db driver právě při té duplicitě klíčů

 
Odpovědět  +1 14. září 15:10
Avatar
danhosek
Člen
Avatar
danhosek:

AHoj.Co mám udělat, když chci předat do editor.latte hodnoty editovaného článku?
Zkusil jsem do public function actionEditor($url) přidal:

($article = $this->articleManager->getArticle($url));
$this->template->article = $article; // Předá článek do šablony.

Ale nefunguje mi to. Sem patří přiřazení proměnných do šablony ne? Proměnné pak v šabloně vypisuju standartně: $article->$article_id
Chci je tam posílat z důvodu další práce ve snippetu s proměnýma.
Děkuji.

Editováno 5. listopadu 0:24
 
Odpovědět 5. listopadu 0:23
Avatar
Jindřich Máca
Tým ITnetwork
Avatar
Odpovídá na danhosek
Jindřich Máca:

Nezlob se na mě, ale tenhle dotaz na mě opět působí tak, že vlastně moc nevíš, co děláš. Opravdu se mě ptáš na to, jak předat proměnou do šablony?

Sem patří přiřazení proměnných do šablony ne?

NE, je to v těch článcích dokonce popsané! Pokud to lze, měl by jsi předávat proměnné do šablony v metodách render*(). Každopádně, pokud na to dojde, můžeš to udělat i v metodě action*() a bude to fungovat...

V uvedeném kódu pak máš úplně zbytečně závorky kolem přiřazení proměnné, proč?! A potom nemáš vůbec ošetřené, co se stane, pokud článek s danou URL neexistuje. :(

A poslední věc, která mě opravdu dorazila:

Proměnné pak v šabloně vypisuju standartně: $article->$article_id

Vážně, odkdy se to takhle zapisuje? Nemá to být náhodou takhle:

$article->article_id

Tohle je úplně začátečnická chyba, kterou každý, kdo alespoň trochu umí PHP, hned vidí. Což značí opět buď Tvoji nepozornost nebo neznalost PHP, a jak už jsem jednou psal, umět PHP je nutně potřeba než se pustíš vlastně do jakéhokoliv PHP frameworku.

Upřímně mě to už nebaví. Můj čas je drahý a rád bych ho strávil odpovídáním na dotazy, které nevyplývají z neznalosti základních věcí. Rozhodně neříkám, že všechny Tvoje dotazy byly takové, ale tenhle... škoda mluvit. Takže příště až uvidím něco podobného, tak prostě už asi nebudu reagovat. Doufám, že si rozumíme. :)

 
Odpovědět  +1 5. listopadu 23:35
Avatar
danhosek
Člen
Avatar
Odpovídá na Jindřich Máca
danhosek:

Ahoj, rozumíme. Chtěl jsem jen dodělat ten snipped, který poslední chyběl. Byla tam nepozornost... umím spoustu věcí, ale bohužel pouze teoreticky a v praxi pak přehlédnu hloupé chyby. Ale děkuji ti za tvůj čas, který pro mě nebyl promarněný a dal mi spoustu cenných zkušeností.
Nyní mi to už funguje. Tak myslím, že bude odemne snad i na delší dobu klid.:o)
Už je to snad i klišé, ale moc děkuji za trpělivost s mémi dotazy. Tvé odpovědi byli pro mě plodné, cenné a získal jsem spoustu zkušeností.
Děkuji.

Editováno 6. listopadu 1:54
 
Odpovědět  +1 6. listopadu 1:52
Avatar
Jan Bezdíček
Redaktor
Avatar
Odpovídá na danhosek
Jan Bezdíček:

Vice zkusenosti bys asi ziskal, kdybys ty teoreticke znalosti uplatnil v praxi na nejakem projektu bez pouziti nette, at to PHP dostanes poradne do krve ... protoze ocividne z predchoziho komentare ani nevis, jak poradne funguje syntaxe jazyka

 
Odpovědět 6. listopadu 1:58
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 10 zpráv z 19. Zobrazit vše