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

PHP Nette Framework Základy Jednoduchý redakční systém v Nette - Výpis článku

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 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í i obsah databáze. Nám zde bude stačit vytvořit si novou databázi, kterou použijeme pro náš projekt, a v ní spustíme upravený SQL skript, který opět naleznete v archivu pod názvem create_script.sql. Jeho obsah je následující:

CREATE TABLE IF NOT EXISTS `article` (
`article_id` int(11) NOT NULL,
  `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
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COLLATE=utf8_czech_ci;


INSERT INTO `article` (`article_id`, `title`, `content`, `url`, `description`) VALUES
(1, 'Úvod', '<p>Vítejte na našem webu!</p>\r\n\r\n<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'),
(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.');

ALTER TABLE `article`
ADD PRIMARY KEY (`article_id`), ADD UNIQUE KEY `url` (`url`);

ALTER TABLE `article`
MODIFY `article_id` int(11) NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=3;

Ten nám vytvoří a naplní tabulku s články, kterou budeme příště potřebovat. A nakonec nastavíme v Nette přístup do naší databáze. To uděláme pomocí souboru app/config/config.local.neon tak, že upravíme již přednastavené parametry tj. název naší databáze, uživatele a heslo. Pro jistotu jsem to v přiloženém archivu ještě okomentoval a vyznačil. Stejně tak jsem okomentoval i celou konfiguraci v souboru config.neon, aby jste měli představu, co které nastavení znamená, i když u většiny je to asi jasné. :)

Modely

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

app/model/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í třídu pro práci s modelem, která nám bude pomocí DI předávat přístup k Nette API pro práci s databází a všechny ostatní 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\Object;

/**
 * Základní třída modelu pro všechny modely aplikace.
 * Předává přístup k práci s databází.
 * @package App\Model
 */
abstract class BaseManager extends Object
{
        /** @var Context Instance třídy pro práci s databází. */
        protected $database;

        /**
         * Konstruktor s injektovanou třídou pro práci s databází.
         * @param Context $database automaticky injektovaná třída pro práci s databází
         */
        public function __construct(Context $database)
        {
                $this->database = $database;
        }
}

app/CoreModule/model/ArticleManager.php

Další na řadě je model správy článků, který jak už bylo řečeno, podědí z třídy BaseManager:

<?php

namespace App\CoreModule\Model;

use App\Model\BaseManager;
use Nette\Database\Table\IRow;
use Nette\Database\Table\Selection;
use Nette\Utils\ArrayHash;

/**
 * Třída poskytuje metody pro správu článků v redakčním systému.
 * @package App\CoreModule\Model
 */
class ArticleManager extends BaseManager
{
        /** Konstanty pro manipulaci s modelem. */
        const
                TABLE_NAME = 'article',
                COLUMN_ID = 'article_id',
                COLUMN_URL = 'url';

        /**
         * Vrátí seznam článků v databázi.
         * @return Selection seznam č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 bool|mixed|IRow první článek, který odpovídá URL nebo false při neúspěchu
         */
        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ý, jinak provede editaci.
         * @param array|ArrayHash $article článek
         */
        public function saveArticle($article)
        {
                if (!$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.
         * @param string $url URL článku
         */
        public function removeArticle($url)
        {
                $this->database->table(self::TABLE_NAME)->where(self::COLUMN_URL, $url)->delete();
        }
}

Presentery

Dále budeme pokračovat s presentery.

Nyní zde již můžeme smazat základní app/presenters/HomepagePresenter.php, protože už ho dále nebudeme potřebovat, jelikož na konci toho dílu budeme mít základ zcela nové aplikace.

app/presenters/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 to 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/presenters/ArticlePresenter.php

Nyní se dostáváme k presenteru, který nám pomocí ArticleManager bude předávat data článků do šablony.

<?php

namespace App\CoreModule\Presenters;

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

/**
 * Zpracovává vykreslování článků.
 * @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.
        }
}

Aby samozřejmě naše automatická DI fungovala, musíme ještě zaregistrovat ArticleManager jako vlastní anonymní službu v naší aplikaci. Konkrétně to provedeme v konfiguračním souboru modulu app/CoreModule/config/config.neon:

# Konfigurační soubor pro CoreModule.

parameters:

# Nastavení služeb dále přístupných pomocí DI v rámci celé aplikace.
services:
        - App\CoreModule\Model\ArticleManager

app/router/RouterFactory.php

Nakonec musíme ještě upravit routování naší aplikace, abychom se opravdu dostali k daným článkům podle jejich URL v databázi. To zařídíme úpravou již existující třídy ze sandboxu RouterFactory:

<?php

namespace App;

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

/**
 * Routovací továrnička.
 * Řídí routování v celé aplikaci.
 * @package App
 */
class RouterFactory
{
        /**
         * Vytváří router pro aplikaci.
         * @return RouteList výsledný router pro aplikaci
         */
        public static function createRouter()
        {
                $router = new RouteList();
                $router[] = new Route('[<url>]', 'Core:Article:default');
                return $router;
        }
}

Příští díl budeme věnovat šablonám a projekt zprovozníme.


 

Stáhnout

Staženo 216x (650.59 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 (7 hlasů) :
4.857144.857144.857144.857144.85714


 



 

 

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

Avatar
danhosek
Člen
Avatar
danhosek:

správné odsazení? jak má vypadat správné odsazení?
odentrování mezi parametry?

parameters:

    dbname: 'XXXXX' # název databáze

# Konfigurace databázové služby dále přístupné pomocí DI v rámci celé aplikace.
database:

dsn: 'mysql:host=wm87.wedos.net; port=3306; dbname=XXXX'

user: 'XXXXX'

password: 'XXXX'

options:

lazy: yes

takto to také nefunguje a vypisuje stejnou exception (Found sections 'dsn', 'user', 'password', 'options', 'lazy' in configuration, but corresponding extensions are missing.)
jakou rozšířenou konfiguraci to chce? nebo kde je tam konkrétně chyba?
děkuji.

Editováno 16. října 1:27
 
Odpovědět 16. října 1:26
Avatar
Jindřich Máca
Tým ITnetwork
Avatar
Odpovídá na danhosek
Jindřich Máca:

Tak teď jsem se upřímně zasmál... :D Takhle jsem to odsazení myslel:

parameters:
    dbname: 'XXXXX' # název databáze

# Konfigurace databázové služby dále přístupné pomocí DI v rámci celé aplikace.
database:
        dsn: 'mysql:host=wm87.wedos.net;dbname=%dbname%'
        user: 'XXXXX'
        password: 'XXXX'
        options:
                lazy: yes

Žádnou další konfiguraci to nechce. Jde o to, že NEON je prostě citlivý na odsazení a pokud tam úplně chybí, jako v tomto případě, bere parametr dsn atd. jako část globálního nastavení, podobně jako např. services. Samozřejmě taková položka v globální nastavení není, takže to hází onu chybu. ;)

 
Odpovědět 16. října 1:40
Avatar
danhosek
Člen
Avatar
Odpovídá na Jindřich Máca
danhosek:

:-D mno...:-D
tedy jsem snad správně odsadil ale stále je problem:

Found sections 'options', 'lazy' in configuration, but corresponding extensions are missing.

parameters:

    dbname: 'XXXXX' # název databáze

# Konfigurace databázové služby dále přístupné pomocí DI v rámci celé aplikace.
database:
    dsn: 'mysql:host=wm87.wedos.net; port=3306; XXXXX'
    user: 'XXXX'
    password: 'XXXX'

options:
lazy: yes

options a lazy jsem zkusil odsazené i neodsazené (vím, zběsilé zkjoušení všeliakých možností :-D)
je to problem odsazení, nebo muže to být třeba špatným portem? nevim, kde ho zjistit, tak tento port mi dala podpora, stím že si není jistý :-D

 
Odpovědět 16. října 12:40
Avatar
danhosek
Člen
Avatar
Odpovídá na danhosek
danhosek:

už to mám, jen pozdě a příspěvek již nešel odstranit:-D mno jen správně odsadit, děkuji moc, a doufám, že jsi se alespon dobře pobavil na večer :o)

 
Odpovědět  +1 16. října 12:46
Avatar
danhosek
Člen
Avatar
danhosek:

Ahoj, když chci přidat funkci, která podle url článku vrátí ID článku, bude vypadat takto:
ale nevím, jak má vypadat ten požadavek do db

public function getArticleId($url)
    {
        return $this->database->table(self::TABLE_NAME)->get(article_id)->where(self::COLUMN_URL, $url);
    }

Může to být takto?
a pro jistotu volání této funkce v modelu, a vrátí číslo id vypadá takto:

$this->getReferenceId($url)

děkuji.

 
Odpovědět 30. října 16:51
Avatar
danhosek
Člen
Avatar
Odpovídá na danhosek
danhosek:

mySQL dotaz by vypadal takto :

SELECT `article_id` FROM `article` WHERE `url`=$url
 
Odpovědět 30. října 17:25
Avatar
danhosek
Člen
Avatar
Odpovídá na danhosek
danhosek:

tak můj finální výsledek je toto:

return $this->database->table('article')->where('url', $url)->get("article_id");

ale nemůžu to odzkoušet, protože to mám rozdělaný a ted to nepujde spustit.

 
Odpovědět 30. října 18:53
Avatar
danhosek
Člen
Avatar
danhosek:

Ahoj, ještě bych chtěl poradit, s funkcí

public function saveArticle($article)
        {
                if (!$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);
        }

a chci přidat return, který vrací article_id nově uloženého článku.
Děkuji.

 
Odpovědět 30. října 19:31
Avatar
Jindřich Máca
Tým ITnetwork
Avatar
Odpovídá na danhosek
Jindřich Máca:

Ahoj, tak to vezmu zase postupně, protože je opět vidět, že děláš něco, čemu úplně nerozumíš. :(

  1. Máš chybu hned v názvu metody. Zase nějaká nepozornost? Funkci máš pojmenovanou getArticleId() a voláš metodu getReferenceId().
  2. Úplně špatně jsi pochopil chování metody get(). Dokumentace říká

Returns row specified by primary key.

To znamená, že vrátí celý záznam (všechny sloupečky) podle konkrétního zadaného primárního klíče (nebo false, pokud záznam s takovým primárním klíčem neexistuje).

Každopádně tu metodu jako takovou nemáš úplně špatně. Vypadat by měla nějak takto:

/* Měl by se vykonat přesně ten databázový dotaz, který jsi poslal, který ale může vrátit pole
záznamů v případě, že sloupeček s URL není unikátní, takže pro jistotu vytáhneme první shodu. */

// Načte první záznam článku s danou URL z databáze, pokud existuje.
if (!($article = $this->database->table('article')->select('article_id')->where('url', $url)->fetch())) {
        // Co se má udělat pokud článek se zadanou URL neexistuje.
} else {

        /* V proměnné $article jsou teď uloženy všechny sloupečky daného záznamu vyžádané
        v SELECT, takže zde je to pouze sloupeček 'article_id'. Z principu je to ale pole, takže samotné
        ID z něj musíme ještě vytáhnout. */

        return $article['article_id']; // Vrací ID článku.
}

Pozn.: Nezkoušel jsem to.

  1. Ta druhá funkce, na kterou jsi se ptal a měla by vracet ID vloženého článku, se upraví asi nějak takto:
if (!$article[self::COLUMN_ID]) {
        $row = $this->database->table(self::TABLE_NAME)->insert($article);
        return $row[self::COLUMN_ID];
} else {
        $this->database->table(self::TABLE_NAME)->where(self::COLUMN_ID, $article[self::COLUMN_ID])->update($article);
        return $article[self::COLUMN_ID];
}

Pozn.1: Opět jsem to nezkoušel.
Pozn.2: Nedělal jsem tady kontrolu toho, jestli se článek skutečně podaří vložit, či editovat.

  1. To, že to máš rozpracované by nemělo vadit, pokud používáš např. verzovací systém jako je Git , či jenom verzování v rámci IDE, nebo si na to jednoduše napiš Unit test. :)

Doufám, že už je to všechno jasnější. ;)

P.S.: Neber si to špatně, ale čím víc dotazů pokládáš, tím mám větší pocit, že ještě úplně neovládáš programování v PHP a principy OOP jako takové. Možná bych nejdříve doporučil zaměřit se na ně, místo toho, aby ses rovnou vhrnul na celý e-shop ve frameworku. Navíc, pokud to neděláš jenom jako cvičení a plánuješ komerční použití, tak je potřeba myslet ještě na spoustu dalšího zabezpečení a neudělat v kódu amatérské chyby, jinak hrozí potenciální katastrofa v podobě např. úniku uživatelských dat apod.

 
Odpovědět  +1 31. října 18:45
Avatar
danhosek
Člen
Avatar
Odpovídá na Jindřich Máca
danhosek:

děkuju, to je přesně to co jsem potřeboval. Neboj se, nechci použít eshop komerčně, vím kolik je tam dalších aspektů, kterým plně nerozimim, ale zde mi jde čistě jen o tu administraci obrázků "Widget".
Jinak ty chyby, že místo Article je reference, je že to dělám pro obrázky do referencí a chtěl jsem to přizpusobit do seriálu na article, ale někde mi to uniklo a nepřepsal jsem to.
Děkuji.

 
Odpovědět  +1 31. října 19:06
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 88. Zobrazit vše