C# týden Slevový týden - Březen
Využij náš slevový týden a získej až 30 % bodů navíc zdarma! Zároveň také probíhá C# týden se slevou na e-learning až 80 %
Hledáme fulltime programátora do ITnetwork týmu -100% homeoffice, 100% časově flexibilní #bezdeadlinu Mám zájem!

Lekce 6 - Jednoduchý redakční systém v Nette - Výpis článku

V minulé lekci, Jednoduchý redakční systém v Nette - Struktura projektu, jsme si připravili projektovou strukturu pro jednoduchý redakční systém. A jak jsem slíbil, dnes se rovnou bez zbytečných řečí vrhneme na implementaci. Takže jdeme na to! :)

Databáze

Prvně se podíváme na nastavení a obsah databáze. Vytvoříme si novou databázi pro náš projekt (např. nette-rs) a v ní spustíme následující SQL:

-- ----------------------------
-- Table structure for `article`
-- ----------------------------
DROP TABLE IF EXISTS `article`;
CREATE TABLE `article` (
    `article_id`  int(11) NOT NULL                   AUTO_INCREMENT,
    `title`       varchar(255) COLLATE utf8_czech_ci DEFAULT NULL,
    `content`     text COLLATE utf8_czech_ci,
    `url`         varchar(255) COLLATE utf8_czech_ci DEFAULT NULL,
    `description` varchar(255) COLLATE utf8_czech_ci DEFAULT NULL,
    PRIMARY KEY (`article_id`),
    UNIQUE KEY `url` (`url`)
) ENGINE = InnoDB AUTO_INCREMENT = 3 DEFAULT CHARSET = utf8 COLLATE = utf8_czech_ci;

-- ----------------------------
-- Records of article
-- ----------------------------
INSERT INTO `article` VALUES ('1', 'Úvod', '<p>Vítejte na našem webu!</p><p>Tento web je postaven na <strong>jednoduchém redakčním systému v Nette frameworku</strong>. Toto je úvodní článek, načtený z databáze.</p>', 'uvod', 'Úvodní článek na webu v Nette v PHP');
INSERT INTO `article` VALUES ('2', 'Stránka nebyla nalezena', '<p>Litujeme, ale požadovaná stránka nebyla nalezena. Zkontrolujte prosím URL adresu.</p>', 'chyba', 'Stránka nebyla nalezena.');

To nám vytvoří a naplní tabulku s články, kterou budeme dále potřebovat.

SQL skript naleznete i v archivu ve složce sql/ pod názvem create_script.sql.

app/config/con­fig.neon

Nakonec je ještě potřeba nastavit v Nette připojení do naší databáze. To uděláme v hlavním konfiguračním souboru tak, že upravíme již přednastavené parametry, tj. název naší databáze i uživatelské jméno a heslo. Výsledek by měl vypadat přibližně takto:

...
# Konfigurace databázového připojení v rámci celé aplikace.
database:
    dsn: 'mysql:host=127.0.0.1;dbname=nette-rs' # Typ, adresa a název databáze
    user: root     # Uživatelské jméno
    password: **** # Heslo
    options:
        lazy: yes
...

Pokud máte jinou konfiguraci databáze v různých prostředích (localhost vs. hosting), používá se ještě lokální konfigurační soubor config.local.neon, jehož nastavení přepíše to globální specificky pro dané prostředí.

Model

Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!

Stejně jako v minulé aplikaci začneme hezky od modelu.

app/model/Data­baseManager.php

Protože jsme všichni znalí OOP a nechceme mít zbytečné duplicity v kódu, připravíme si základní abstraktní modelovou třídu pro práci s databází, která bude pomocí Dependency Injection (zkráceně DI) získávat přístup k Nette rozhraní pro práci s databází a všechny další modelové třídy z ní budou následně dědit, aby automaticky tento přístup získaly. Třída je poměrně jednoduchá a vypadá takto:

<?php

namespace App\Model;

use Nette\Database\Context;
use Nette\SmartObject;

/**
 * Základní model pro všechny ostatní databázové modely aplikace.
 * Poskytuje přístup k práci s databází.
 * @package App\Model
 */
abstract class DatabaseManager
{
    use SmartObject;

    /** @var Context Služba pro práci s databází. */
    protected $database;

    /**
     * Konstruktor s injektovanou službou pro práci s databází.
     * @param Context $database automaticky injektovaná Nette služba pro práci s databází
     */
    public function __construct(Context $database)
    {
        $this->database = $database;
    }
}

app/CoreModule/mo­del/ArticleMa­nager.php

Další na řadě je model pro správu článků, který tedy podědí ze třídy DatabaseManager. Bude již součástí našeho CoreModulu a jeho cílem je definovat metody, které pomocí Nette rozhraní pro práci s databází umožní manipulaci s tabulkou článků, kterou jsme si vytvořili výše.

<?php

namespace App\CoreModule\Model;

use App\Model\DatabaseManager;
use Nette\Database\Table\ActiveRow;
use Nette\Database\Table\Selection;
use Nette\Utils\ArrayHash;

/**
 * Model pro správu článků v redakčním systému.
 * @package App\CoreModule\Model
 */
class ArticleManager extends DatabaseManager
{
    /** Konstanty pro práci s databází. */
    const
        TABLE_NAME = 'article',
        COLUMN_ID = 'article_id',
        COLUMN_URL = 'url';

    /**
     * Vrátí seznam všech článků v databázi seřazený sestupně od naposledy přidaného.
     * @return Selection seznam všech článků
     */
    public function getArticles()
    {
        return $this->database->table(self::TABLE_NAME)->order(self::COLUMN_ID . ' DESC');
    }

    /**
     * Vrátí článek z databáze podle jeho URL.
     * @param string $url URl článku
     * @return false|ActiveRow první článek, který odpovídá URL nebo false pokud článek s danou URL neexistuje
     */
    public function getArticle($url)
    {
        return $this->database->table(self::TABLE_NAME)->where(self::COLUMN_URL, $url)->fetch();
    }

    /**
     * Uloží článek do systému.
     * Pokud není nastaveno ID vloží nový článek, jinak provede editaci článku s daným ID.
     * @param array|ArrayHash $article článek
     */
    public function saveArticle($article)
    {
        if (empty($article[self::COLUMN_ID])) {
            unset($article[self::COLUMN_ID]);
            $this->database->table(self::TABLE_NAME)->insert($article);
        } else
            $this->database->table(self::TABLE_NAME)->where(self::COLUMN_ID, $article[self::COLUMN_ID])->update($article);
    }

    /**
     * Odstraní článek s danou URL.
     * @param string $url URL článku
     */
    public function removeArticle($url)
    {
        $this->database->table(self::TABLE_NAME)->where(self::COLUMN_URL, $url)->delete();
    }
}

Všimněte si, že Nette rozhraní pro práci s databází kopíruje pokládání klasických SQL dotazů, ovšem v rámci PHP kódu v trochu zjednodušeném formátu. A také obsahuje např. ochranu proti SQL injection. ;)

Presentery

Dále budeme pokračovat s presentery. Začneme tím, že smažeme základní presenter app/presenters/HomepagePresenter.php, protože už ho nebudeme potřebovat. Na konci této lekce už totiž budeme mít základ zcela nové aplikace.

app/presenter­s/BasePresenter­.php

Stejně jako ve většině projektů v Nette a podobně jako v našem modelu, začneme od základní abstraktní třídy, ze které dědí všechny ostatní presentery a tou je BasePresenter. V sandboxu, ze kterého vycházíme, už je vytvořen, takže ho jen trošku upravíme:

<?php

namespace App\Presenters;

use Nette\Application\UI\Presenter;

/**
 * Základní presenter pro všechny ostatní presentery aplikace.
 * @package App\Presenters
 */
abstract class BasePresenter extends Presenter
{
}

app/CoreModule/pre­senters/Article­Presenter.php

Nyní se dostáváme k presenteru, který nám pomocí ArticleManager bude předávat data článků do šablony. Tento presenter tvoří hlavní část našeho CoreModule a vypadá následovně:

<?php

namespace App\CoreModule\Presenters;

use App\CoreModule\Model\ArticleManager;
use App\Presenters\BasePresenter;
use Nette\Application\BadRequestException;

/**
 * Presenter pro vykreslování článků.
 * @package App\CoreModule\Presenters
 */
class ArticlePresenter extends BasePresenter
{
    /** @var string URL výchozího článku. */
    private $defaultArticleUrl;

    /** @var ArticleManager Model pro správu s článků. */
    private $articleManager;

    /**
     * Konstruktor s nastavením URL výchozího článku a injektovaným modelem pro správu článků.
     * @param string         $defaultArticleUrl URL výchozího článku
     * @param ArticleManager $articleManager    automaticky injektovaný model pro správu článků
     */
    public function __construct($defaultArticleUrl, ArticleManager $articleManager)
    {
        parent::__construct();
        $this->defaultArticleUrl = $defaultArticleUrl;
        $this->articleManager = $articleManager;
    }

    /**
     * Načte a předá článek do šablony podle jeho URL.
     * @param string|null $url URL článku
     * @throws BadRequestException Jestliže článek s danou URL nebyl nalezen.
     */
    public function renderDefault($url = null)
    {
        if (!$url) $url = $this->defaultArticleUrl; // Pokud není zadaná URL, vezme se URL výchozího článku.

        // Pokusí se načíst článek s danou URL a pokud nebude nalezen vyhodí chybu 404.
        if (!($article = $this->articleManager->getArticle($url)))
            $this->error(); // Vyhazuje výjimku BadRequestException.

        $this->template->article = $article; // Předá článek do šablony.
    }
}

app/CoreModule/con­fig/config.ne­on

Aby fungovalo automatické předávání závislostí pomocí DI, musíme ještě zaregistrovat ArticleManager jako službu v naší aplikaci. Konkrétně to provedeme v konfiguračním souboru našeho modulu, kde i předáme konfiguraci URL výchozího článku do našeho nového presenteru:

#
# Konfigurační soubor pro CoreModule.
#
parameters:
    defaultArticleUrl: 'uvod' # URL výhozího článku


# Nastavení služeb pro CoreModule.
services:
    - App\CoreModule\Model\ArticleManager # Vlastní služba dále přístupná pomocí DI.
    - App\CoreModule\Presenters\ArticlePresenter(%defaultArticleUrl%) # Předání nastavení při vytváření služby presenteru.

Routování

app/router/Rou­terFactory.php

Abychom se opravdu dostali k daným článkům podle jejich URL, je nutné ještě upravit routování naší aplikace. To zařídíme úpravou již existující třídy ze sandboxu RouterFactory, kde nastavíme směrování na náš nový presenter:

<?php

namespace App;

use Nette\Application\IRouter;
use Nette\Application\Routers\Route;
use Nette\Application\Routers\RouteList;
use Nette\StaticClass;

/**
 * Továrna na routovací pravidla.
 * Řídí směrování a generovaní URL adres v celé aplikaci.
 * @package App
 */
class RouterFactory
{
    use StaticClass;

    /**
     * Vytváří a vrací seznam routovacích pravidel pro aplikaci.
     * @return IRouter výsledný router pro aplikaci
     */
    public static function createRouter()
    {
        $router = new RouteList;
        $router[] = new Route('[<url>]', 'Core:Article:default');
        return $router;
    }
}

To je zatím vše. V příští lekci, Jednoduchý redakční systém v Nette - Administrace, se budeme věnovat šablonám a projekt zprovozníme.


 

 

Článek pro vás napsal Jindřich Máca
Avatar
Jak se ti líbí článek?
21 hlasů
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. :-)
Předchozí článek
Jednoduchý redakční systém v Nette - Struktura projektu
Všechny články v sekci
Základy Nette frameworku
Miniatura
Následující článek
Jednoduchý redakční systém v Nette - Administrace
Aktivity (7)

 

 

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

Avatar
Michal Dvořáček:15.2.2018 10:16

Zdravím,
chtěl bych se zeptat na BaseManager, konkrétně na Object.
Nějak jsem pochopil, že už je Nette\Object zastaralé a místo ní se udělalo Nette\SmartObject.
Co ale neumím určit, je jak se třeba konkrétně v tomto příkladu využije, respektive o co mám pak BaseManager rozšířit. Zkoušel jsem právě o SmartObject, ale nefunguje.

use Nette\SmartObject;
abstract class BaseManager extends SmartObject
{...}

Děkuji za každou radu jak tuto komplikaci vyřešit.

Editováno 15.2.2018 10:18
 
Odpovědět
15.2.2018 10:16
Avatar
dez1nd
Člen
Avatar
Odpovídá na Michal Dvořáček
dez1nd:15.2.2018 10:21

Dědit nic nemusíš, máš to v use

potom stačí jen

$objekt = new SmartObject;

kdyby jsi to neměl v use tak bys musel

$objekt = new Nette\SmartObject;
Editováno 15.2.2018 10:23
 
Odpovědět
15.2.2018 10:21
Avatar
Jindřich Máca
Tým ITnetwork
Avatar
Odpovídá na Michal Dvořáček
Jindřich Máca:15.2.2018 14:04

Ahoj, problém je v tom, že SmartObject není třída, ale PHP trait, což už mluví za vše. Příklady jejího použití pak najdeš přímo v oficiální dokumentaci - https://doc.nette.org/…/smartobject.

 
Odpovědět
15.2.2018 14:04
Avatar
David Čápka
Tým ITnetwork
Avatar
David Čápka:6.8.2018 13:24

Aktualizováno pro Nette 2.4.

Odpovědět
6.8.2018 13:24
Jsem moc rád, že jsi na síti, a přeji ti top IT kariéru, ať jako zaměstnanec nebo podnikatel. Máš na to! :)
Avatar
Fero Mikulic
Člen
Avatar
Fero Mikulic:14. března 17:20

Ahoj. Udelal sem vsechno podle postupu a haze mi to chybu

"Nette\DI\Ser­viceCreationEx­ception

Service 'routing.router': Method App\Router\Rou­terFactory::cre­ateRouter() is not callable."

 
Odpovědět
14. března 17:20
Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!
Avatar
Milan Turyna
Redaktor
Avatar
Milan Turyna:14. března 18:25

Nekoukal jsem na serial, ale myslim si že to muze byt zpusobeno ze vyuzivas novou verzi a serial je pro starsi ale nevim. Ja kdyz jsem postupoval pri tvorbe cmska pres nette jen podle dokumentace tak mi to vzdy slo. Takze mozna ta verze.

 
Odpovědět
14. března 18:25
Avatar
Milan Turyna
Redaktor
Avatar
Milan Turyna:14. března 18:26

Jeste sem muzes poslat config

 
Odpovědět
14. března 18:26
Avatar
Fero Mikulic
Člen
Avatar
Fero Mikulic:23. března 13:53

Skousel sem to i na 2.4 a stejnej error.
Tady je GitHub repo
https://github.com/…ob/nette-cms

 
Odpovědět
23. března 13:53
Avatar
Daniel Vítek
Tým ITnetwork
Avatar
Odpovídá na Fero Mikulic
Daniel Vítek:23. března 16:02

https://github.com/…rFactory.php

Máš zde špatný namespace - jen App. Uprav jej na App\Router.

Odpovědět
23. března 16:02
Na síti působím už pěknou řádku let. Pokud budeš něco potřebovat, písni mi, pokusím se ti poradit :)
Avatar
Fero Mikulic
Člen
Avatar
Odpovídá na Daniel Vítek
Fero Mikulic:24. března 15:54

Dekuji. Pomohlo to

 
Odpovědět
24. března 15:54
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 103. Zobrazit vše