Evidence knih v PHP/MySQL s využitím MVC

PHP Ostatní Evidence knih v PHP/MySQL s využitím MVC

V soutěži Machr na PHP bylo za úkol vytvořit webové rozhraní pro evidenci knih. V jednom z výherních webů jsem využil architektury MVC (možná by se dalo říci MVP). Návrh aplikace vypadal následovně:

  • Model se stará o aplikační data. Vybírá/modifikuje data uložená v databázi (MySQL).
  • Controller získává data z modelové vrstvy a předává je vrstvě zobrazovací. Také se stará o události, jako jsou akce a zpracování formulářů.
  • View je ve formě šablony Smarty.

Application

Celý životní cyklus jsem obalil třídou Application, díky které stačilo mít v indexu pouhých pár řádků:

require "loader.php";

$app = new Application($_POST, $_GET);
$app->setControllers(array(
        "user" => "User",
        "homepage" => "Homepage",
        "book" => "Book"
));
$app->setDefaultController("book");
$app->run();

V loader.php je pak trocha základního nastavení, společného pro celou aplikaci (spuštění sessions, nastavení kódování, vytvoření class autoloaderu a zaregistrování Smarty helperu).

Základní myšlenka použití třídy Application je, aby se o životní cyklus aplikace postarala sama. Proto potřebuje mít přístup k parametrům z URL a POST, které se předají přes konstruktor. Zároveň musí mít seznam controllerů, které se postarají o vykreslení jednotlivých stránek a zpracování různých akcí.

Seznam předáme pomocí metody setControllers ve formě asocativního pole (název stránky => název třídy). Také je možnost určit defaultní controller, který se použije pro domovskou stránku. Pokud neexistuje žádný controller k dané stránce, automaticky se použije tzv. error controller, který vykreslí stránku dle šablony 404.tpl.

Metoda run() poté vyhledá controller k příslušné stránce. K tomu jí pomůže pomocná metoda getController():

/**
 * @return Controller
 */
public function getController()
{
        if (!isset($this->get["page"])) {
                if ($this->defaultController === NULL) {
                        throw new ControllerException("No default controller set.");
                }

                $controller = ucFirst($this->defaultController) . "Controller";

                return new $controller($this->post, $this->get, $this->session);
        } else {
                if (!isset($this->controllers[$this->get["page"]])) {
                        throw new ControllerException("Controller for this page doesn't exist.");
                }
        }

        $controller = $this->controllers[$this->get["page"]] . "Controller";

        return new $controller($this->post, $this->get, $this->session);
}

Metoda se pokusí sestavit název controlleru dle parametru "page" v URL adrese. Např. pokud nastavíme, že pro stránku "book" bude controller "Book", pak metoda vrátí instanci BookController. V případě selhání se vyhodí výjimka ControllerExcep­tion. Jelikož je tato výjimka vyhozena v případě, který by neměl nastat v ostré verzi, není defaultně nikde zachycena. Nic ale vývojáři nebrání výjimku zachytit v indexu a chybu zalogovat.

V metodě run() poté stačí pouze provést pár základních událostí:

  • Controller::i­nit() - nastavení proměnných, kontrola přihlášení uživatele atp.
  • Controller::pro­cessForm() - zpracování formuláře (proběhne pouze v případě, že pole $_POST není prázdné).
  • Controller::pro­cessAction() - zpracování neformulářové akce.
  • Controller::pro­cessRender() - ta se postará o vykreslení stránky.

V případě neexistující stránky se použije error controller, který vykreslí chybovou stránku.

Ještě než si popíšeme samotnou controller vrstvu, podíváme se na pár pomocných tříd.

Template

Template je miniaturní třída, která pouze zpříjemňuje přiřazování proměnných šabloně. Místo Smarty zápisu $smarty->assign("proměnná", "hodnota") lze využít magické metody __set() a proměnné přiřazovat kratším způsobem $template->promenna = "hodnota".

Config

Statická třída, která se stará o načítání konfiguračních dat z primitivního .ini souboru. Ty je nutné ukládat do složky config (tu nesmí být možnost zobrazit v prohlížeči). Pokud se jedná o vývojářský režim, použije data ze souboru "config.local.ini". V produkčním serveru se pak parsuje soubor "config.ini".

Database

Usnadňuje psaní metod PDO. Místo volání PDO::prepare() a PDOStatement::e­xecute() lze volat metody query(), fetch() atp. rovnou tak, že prvním parametrem je SQL dotaz a ostatní parametry se předají metodě PDO::execute().

Paginator

Slouží pro zobrazení odkazů směřující na stránky výpisu dat.

Session

Malé rozhraní pro práci se sessions. Umožňuje ukládat, získávat, mazat a kontrolovat hodnoty. Při získávání hodnoty je možné uvést defaultní hodnotu (pokud se nenalezne dle klíče) a případně vyhodit výjimku (pokud by bylo nezbytné hodnotu získat).

ModelFactory

Návrhem se velmi blíží DI containeru. Jak ale z názvu vyplývá, vytváří pouze instance modelů. Názvy metod jsou ve formátu create<název modelu>Model. Jako parametry většinou přijímají ID (např. ID uživatele, knihy, ...). ModelFactory se zároveň postará o to, aby dané modely získaly spojení s databází. Jelikož jsem nevytvářel framework, celé DI bych prakticky moc nevyužil.

Controller

Nyní se dostaneme k samotné controller vrstvě. Základem je abstraktní třída Controller, která se stará o všechny události (jejichž přímé zpracování určí potomci).

init()

Je volána jako první a je vhodné ji využít pro nastavení proměnných, kontrolu přihlášení uživatele a tak dále.

processForm()

Pokud je v URL adrese parametr "form" (název formuláře) a pole $_POST není prázdné, pak se pomocí názvu formuláře sestaví název metody. Ta je ve tvaru form<název formuláře>Sub­mitted(). Pokud taková metoda existuje, zavolá se a předá se jí parametr "id" z URL (pokud existuje).

final public function processForm()
{
        if (!isset($this->get->form) || !(array) $this->post) {
                return;
        }

        $method = "form" . ucFirst($this->get->form) . "Submitted";
        if (method_exists($this, $method)) {
                $id = !empty($this->get->id) ? (int) $this->get->id : NULL;
                $this->$method($id);
        }
}

processAction()

Dělá prakticky to samé, co metoda processForm(), jen s tím rozdílem, že nevyžaduje data v POST. Metoda se sestaví podle parametru "action" v URL. Tvar metody je pak action<název akce>().

final public function processAction()
{
        if (!isset($this->get->action)) {
                return;
        }

        $method = "action" . ucFirst($this->get->action);
        if (method_exists($this, $method)) {
                $id = !empty($this->get->id) ? (int) $this->get->id : NULL;
                $this->$method($id);
        }
}

U obou metod processForm() a processAction() je vhodné stránku přesměrovat pomocí metody redirect(). Jediný povinný parametr je název stránky. Druhým, nepovinným parametrem je pak "sub" (např. page=book&sub=e­dit). Pomocí posledního parametru lze předat další parametry v URL (např. ID editované knihy).

processRender()

Zpracovali jsme formuláře a akce a nyní je potřeba vykreslit stránku. Prvně se sestaví název souboru ve tvaru application/tem­plates/<název stránky>/<sub stránka>.tpl. Pokud není zadaná sub stránka, použije se šablona index.tpl.

Samotné vykreslení pak probíhá následovně:

  • Zaregistrují se důležité proměnné pro všechny stránky (flash zpráva a seznam formulářových chyb).
  • Zavolá se metoda beforeRender().
  • Zavolá se metoda render<sub stránka>(). V té se nastaví proměnné pro danou sub stránku.
  • Zavolá se metoda Template::render(), která pomocí Smarty vygeneruje HTML.

Vytváření vlastních stránek

Nové modely, controllery, šablony, CSS a JS se ukládá do složky application.

Zpracování formulářů

Chyby lze postupně ukládat pomocí metody addFormError(). Poté pomocí vlastnosti formErrors lze zjistit, zda byl formulář odeslán úspěšně nebo ne.

Ukázka zpracování přihlášení:

public function formLoginSubmitted()
{
        $p = $this->post;

        if (empty($p->name)) {
                $this->addFormError("Nebylo vyplněno jméno.");
        }
        if (empty($p->password)) {
                $this->addFormError("Nebylo vyplněno heslo.");
        }

        if (!$this->formErrors) {
                try {
                        $userId = $this->userModel->authenticate($p->name, $p->password);
                        $this->session->set("userId", $userId);
                        $this->setMessage("Byl jste úspěšně přihlášen.", "success");
                        $this->redirect("user", "account");
                } catch (AuthenticationException $e) {
                        $this->addFormError($e->getMessage());
                }
        }
}

Zpracování akcí

Akcím lze předat parametr ID. Samotný kód je pak podobný tomu v metodě processForm().

Ukázka smazání knihy:

/**
 * @param int
 */
public function actionDelete($id)
{
        if (empty($id)) {
                $this->redirect("book");
        }
        if (!$this->userModel->isLogged()) {
                $this->redirect("user", "login");
        }

        $bookModel = $this->modelFactory->createBookModel($id);
        $deleted = $bookModel->delete();
        if ($deleted) {
                $this->setMessage("Kniha byla úspěšně smazána.", "success");
        } else {
                $this->setMessage("Tato kniha nemohla být smazána.", "error");
        }

        $this->redirect("book");
}

Renderování jednotlivých stránek

O to se starají metody render<sub stránka>().

Ukázka předání proměnných šabloně book/index.tpl:

public function renderIndex()
{
        $limit = 10;
        $page = !empty($this->get->pageNum) ? (int) $this->get->pageNum : 0;
        $numItems = $this->bookModel->getBooksCount();
        $paginator = new Paginator($numItems, $page, $limit);

        $this->template->books = $this->bookModel->getAll($limit, $paginator->getOffset());
        $this->template->paginator = $paginator;
}

Zprávy

Zprávy se posílají přes sessions. Do šablony se dostanou ve formě instance stdClass, která obsahuje dvě vlastnosti (text, status - použije se do atributu class). Zpráva se při jejím získáním ihned smaže.


Galerie

Program byl vytvořen v roce 2014.

 

Stáhnout

Staženo 329x (269.07 kB)
Aplikace je včetně zdrojových kódů v jazyce php

 

  Aktivity (1)

Program pro vás napsal Martin Konečný (pavelco1998)
Avatar
Autor se o IT moc nezajímá, raději by se věnoval speciálním jednotkám jako jsou SEALs nebo SAS. Když už to ale musí být něco z IT, tak tvorba web. aplikací v PHP.

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


 


Miniatura
Všechny články v sekci
Ostatní tutoriály v PHP
Miniatura
Následující článek
Šablonovací systém PHP_JUI - Úvod

 

 

Komentáře

Avatar
David Čápka
Tým ITnetwork
Avatar
David Čápka:

Teda, popsal jsi to dokonale :) Doufám, že důvodem není limit znaků na články, toto je program, takže tam stačí jen odstavec.

Odpovědět 9.3.2014 11:11
Miluji svou práci a zdejší komunitu, baví mě se rozvíjet, děkuji každému členovi za to, že zde působí.
Avatar
Petr Nymsa
Redaktor
Avatar
Odpovídá na David Čápka
Petr Nymsa:

Aspoň je tu pěkný příklad jak někdo může MVC architekturu implementovat. Takže druhá inspirace k seriálu :)

Odpovědět 9.3.2014 11:15
Pokrok nezastavíš, neusni a jdi s ním vpřed
Avatar
Martin Konečný (pavelco1998):

Díky :)
Pokud by měl někdo nějakou kritiku, rád si ji vyslechnu a příští články se pokusím sepsat lépe.

 
Odpovědět 9.3.2014 11:32
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 3 zpráv z 3.