Hello World
Každá aplikace v PClib začíná dvěma řádky: připojením knihovny a vytvořením objektu aplikace pclib\App, který obsahuje služby a údaje sdílené různými komponentami aplikace nebo se vztahující k aplikaci jako celku. To je vše, ihned poté můžete začít tvořit váš projekt!
<?php
include 'vendor/autoload.php';
$app = new pclib\App('nazev_aplikace');
// Zde nasleduje vas kod...
Například formulář můžeme vypsat takto:
$form = new pclib\Form('tpl/sablona_formulare.tpl');
print $form;
Pro výpis údajů z databáze pomocí datagridu se musíme nejprve připojit k databázi:
$app->db = new pclib\Db('pdo_mysql://user:password@localhost/testovaci_databaze');
A pak vytvořit objekt grid a vypsat data z databázové tabulky:
$grid = new pclib\Grid('tpl/sablona_gridu.tpl');
$grid->setQuery('select * from tabulka');
print $grid;
Struktura aplikace
Ačkoliv lze psát kód přímo pod řádek s objektem aplikace, v reálných projektech se kód člení do jednotlivých podadresářů a souborů. Doporučenou strukturu pclib aplikace můžete najít v šabloně https://github.com/lenochware/pclib-app. Podle architektury MVC (Model / View / Controller) se pak akce volané uživatelem definují v příslušném controlleru.
Například k zobrazení formuláře bychom vytvořili akci show:
<?php
...
public function showAction()
{
$form = new pclib\Form('tpl/home/sablona_formulare.tpl');
return $form;
}
...
Po zadání adresy http://localhost/moje-aplikace/home/show se v prohlížeči vypíše příslušný formulář.
Při práci s MVC bude v souboru index.php pouze konfigurace aplikace a volání aplikační metody run() a out(), která vykoná příslušný kód a vypíše výsledek do prohlížeče. Níže je obvyklá podoba index.php, která by měla vyhovovat většině aplikací a kterou budeme předpokládat v následujících příkladech.
<?php
include 'vendor/autoload.php';
$app = new pclib\App('test_app');
$app->addConfig('./config.php');
$app->db = new pclib\Db('pdo_mysql://user:password@localhost/test_app');
$app->setLayout('tpl/layout.tpl');
$app->run();
$app->out();
Vytvoříme objekt $app, načteme konfiguraci ze souboru config.php, připojíme se k databázi test_app a nastavíme šablonu layoutu do které se budou vypisovat všechny výstupy. Nakonec se podle zavolané url vykoná příslušná akce (metoda controlleru) a vypíše se výsledek.
test-app/ ├── controllers/ ├── models/ ├── tpl/ ├── css/ ├── js/ ├── vendor/ ├── index.php └── config.php
V adresáři controllers/ jsou jednotlivé controllery (potomci třídy pclib\Controller) pro každý modul aplikace. Jestliže používáte pclib ORM, mohou být v models/ databázové modely (potomci třídy pclib\Model). Adresář tpl/ obsahuje soubory šablon obvykle rozdělené do podadresářů pro každý modul. vendor/ je adresář s nainstalovanými knihovnami, spravovaný composerem a css a js obsahuje soubory stylů a javascriptu.
Adresářová struktura není závazná, místa, kde pclib hledají příslušné soubory, lze změnit v konfiguraci.
Konfigurace
📖 Seznam konfiguračních parametrů
Konfigurační soubor načteme při inicializaci aplikace v index.php příkazem $app->addConfig('./config.php'). Konfigurační parametry jsou pak uložené v poli $app->config. Samotný konfigurační soubor tvoří obyčejné php pole $config obsahující konfigurační parametry. Může obsahovat i další obdobná pole $develop a $production.
$config = [
'konfiguracni-klic-1' => 'hodnota',
'konfiguracni-klic-2' => 'hodnota',
];
$develop = [
'konfiguracni-klic-2' => 'hodnota pro develop',
];
$production = [
'konfiguracni-klic-2' => 'hodnota pro produkcni server',
];
Hodnoty v poli $develop se uplatní na vývojovém serveru, $production slouží pro ostrý provoz. Implicitně se $develop načte při spuštění z localhostu a $production všude jinde. Pole $config se načítá vždy.
O jaké prostředí jde, udává hodnota $app->environment. $app->environment = 'production' nastaví produkční prostředí. K nastavení podle IP lze použít funkci $app->environmentIp(['ip-adresa' => 'develop', 'ip-adresa' => 'production', ...])
$develop = [ 'pclib.errors' => ['display' => true, 'develop' => true], ]; $production = [ 'pclib.errors' => ['display' => true, 'develop' => false, 'log' => true, /*'template' => 'tpl/error.tpl' */], ];
Pomocí konfiguračního klíče pclib.app můžete nastavit základní konfiguraci objektu aplikace, jako je šablona layoutu, implicitní routa a automaticky spouštěné služby (další možnost je nastavení pomocí php v souboru index.php).
$config = [
'pclib.app' => [
'language' => 'cs',
'default-route' => 'products',
'layout' => 'tpl/layout.tpl',
'autostart' => ['db', 'auth', 'fileStorage'],
],
'service.db' => ['dsn' => 'pdo_mysql://user:password@localhost/test-app'],
'service.fileStorage' => ['rootDir' => './uploaded'],
];
Po otevření aplikace se zobrazí stránka products/index, jazyk je nastavený na češtinu a layout se načte z příslušného souboru. Automaticky se spustí služba databáze, autorizační systém a služba pro ukládání souborů.
Ke každé službě můžete přistupovat pomocí $app->jmeno_sluzby např. $app->db. Služby lze konfigurovat klíčem 'service.jmeno_sluzby'.
Layout
class pclib\Layout🔸Seznam elementů layoutu
Layout aplikace je základní šablona aplikace, do které se vkládá veškerý obsah. Nastavíme ji v konfiguraci nebo příkazem $app->setLayout('tpl/layout.tpl'); Layout může vypadat například takto:
<?elements
head HEAD scripts "css/website.css,js/global.js,{pclib}/www/pclib.js"
messages PRECONTENT noescape
string CONTENT noescape
?>
<!doctype html>
<html lang="cs">
<head>
<meta charset="utf-8">
<title>{TITLE}</title>
{HEAD}
</head>
<body>
<main>
{PRECONTENT}{CONTENT}
</main>
</body>
</html>
Výrazy ve složených závorkách jako{CONTENT} jsou elementy šablony, které jsou nahrazeny vloženým obsahem. V sekci <?elements ?> lze doplnit typ a parametry vypisovaného elementu. Viz šablonovací systém. Layout obsahuje tři hlavní elementy: HEAD pro vkládání stylů a skriptů, PRECONTENT slouží k výpisu notifikačních a varovných hlášení aplikace a CONTENT je vlastní obsah. Hodnota vrácená controllerem bude před vypsáním vložená do layoutu jako CONTENT:
public function helloAction()
{
return "Hello world!";
}
K layoutu aplikace lze přistupovat pomocí $app->layout a pracovat s ním jako s každou šablonou.
$app->layout->values['TITLE'] = 'Titulek aplikace';
Pokud potřebujeme podmíněně přidat styly a skripty jen pro některou stránku, můžeme to provést pomocí metody $app->layout->addScripts():
$app->layout->addScripts('js/editor.js', 'css/editor.css');
Pokud nějaká část webu používá odlišný layout než zbytek, lze ho snadno změnit:
public function helloAction()
{
$this->app->setLayout('tpl/hello-layout.tpl');
return "Hello world!";
}
Controllers
Kontrolery obsahují veškeré akce, které aplikace vykonává. Po stisknutí tlačítka nebo zadání webové adresy router rozhodne, která metoda kontroleru se zavolá a předá jí případné parametry. Kontroler je třída odvozená od pclib\Controller, její název končí na Controller a je uložená ve stejnojmeném souboru v adresáři controllers/. Obvykle každému modulu aplikace odpovídá příslušný controller.
Například eshop by mohl obsahovat HomeController, ProductsController a OrdersController pro úvodní obrazovku, přehled a detail produktů a zpracování objednávek - v odpovídajících souborech HomeController.php, ProductsController.php a OrdersController.php
class ProductsController extends pclib\Controller {
public function indexAction()
{
$grid = new pclib\Grid('tpl/products/grid.tpl');
$grid->setQuery("select * from products");
return $grid;
}
public function showAction($id)
{
$product = $this->app->db->select('products', ['id' => $id]);
if (!$product) $this->app->error("Produkt nenalezen!", "alert alert-danger");
return $this->template('tpl/products/detail.tpl', $product);
}
}
Metodě kontroleru končící na Action odpovídá url při jehož zadání se zavolá. Například showAction() odpovídá url test-app/index.php?r=products/show nebo při použití friendly url: test-app/products/show. indexAction je speciální a volá se implicitně. Akce mohou mít parametry - například akce show, která zobrazí detail produktu, vyžaduje jako parametr id produktu.
| Route | Url | Volaná akce |
|---|---|---|
| products | /products | ProductsController->indexAction() |
| products/show/id:1 | /products/show?id=1 | ProductsController->showAction(1) |
| products/moje-oblibene | /products/moje-oblibene | ProductsController->mojeOblibeneAction() |
| products/list/sleva:1/kategorie:pc | /products/list?sleva=1&kategorie=pc | ProductsController->listAction(1, 'pc') |
Uvnitř kontroleru můžete přistupovat k objektu aplikace pomocí $this->app. Například ke službě databáze lze přistupovat pomocí $this->app->db. Aplikační funkce $app->error() vypíše chybové hlášení a ukončí aplikaci /druhý parametr jsou css třídy k nastylování messageboxu/
Kontroler obsahuje několik pomocných metod. Funkce $this->template('tpl/products/detail.tpl', $product); vytvoří šablonu produktu a naplní ji hodnotami z pole $product.
Často používaná je funkce redirect(), která přesměruje na příslušnou routu. Takto po aktualizaci produktu přesměrujeme na stránku detailu produktu:
...
public function updateAction($id)
{
$form = new pclib\Form('tpl/products/form.tpl');
if (!$form->validate()) {
$this->app->error("Chybně zadané parametry.", "alert alert-danger");
};
$form->update('products', ['id' => $id]);
$this->app->message('Položka byla uložena.', "alert alert-success");
$this->redirect('products/show/id:' . $id);
}
Načte se formulář, zkontroluje se, jestli je správně vyplněný (podle validačních pravidel v šabloně), uloží se do databáze, nastaví se informační hlášení a nakonec se provede přesměrování.
Pokud uživatel zadá neexistující routu (např. products/xyz) , můžete ji zpracovat, pokud nadefinujete funkci defaultAction():
public function defaultAction($action)
{
$this->app->error("Stránka neexistuje: " . $action->path, "alert alert-danger");
}
public function deleteAction($id)
{
$this->authorize('products/delete');
$this->app->db->delete('products', ['id' => $id]);
$this->app->message('Položka byla smazána.', "alert alert-success");
$this->redirect('products');
}
Pokud používáte ORM (databázové modely) lze využít pomocné funkce model() a selection() k jejich získání a dotazování.
Získame ORM model produktu s id=1:
$product = $this->model('products', 1);
print $product->title;
Procházíme všechny produkty s cenou vyšší než 1000:
$sel = $this->selection('products')->where('price>1000');
foreach($sel as $product) {
print $product->title;
}
Objekt Aplikace
Objekt aplikace ($app) třídy pclib\App je obvykle první objekt, který v pclib vytváříme. Slouží jako fasáda poskytující přístup k různým službám a funkcím pclib. Z metody controlleru se na ní můžeme odkazovat pomocí atributu $this->app. Aplikace obsahuje tyto veřejné atributy:
| $app->name | string | Název aplikace |
| $app->config | array | Pole konfiguračních parametrů |
| $app->language | string (např . 'en', 'cs') | Jazyk aplikace |
| $app->environment | 'develop' | 'production' | Vývojové nebo produkční prostředí |
| $app->debugMode | true | false | Debugovací režim se zapnutým debugbarem |
| $app->layout | pclib\Layout | Šablona layoutu |
| $app->request | pclib\Request | Hodnoty požadavku, jako url, ip-adresa nebo hlavičky |
| $app->router->action | pclib\Action | Aktuální volaná akce controlleru |
Aplikace obsahuje metody k spuštění a vykonání požadavku a zobrazení výsledku a další pomocné metody, díky kterým nemusíte přistupovat k příslušným komponentám frameworku napřímo.
| $app->addConfig($path) | Načte konfigurační soubor |
| $app->setLayout($path) | Načte šablonu layoutu |
| $app->run($routeStr = null) | Spustí akci controlleru |
| $app->out() | Zobrazí výslednou stránku |
| $app->message($message, $cssClass = null) | Vypíše informační hlášení (flash-message) |
| $app->error($message, $cssClass = null) | Vypíše chybové hlášení a ukončí aplikaci |
| $app->log($category, $message_id, $message = null, $item_id = null) | Zapíše zprávu do logu |
| $app->redirect($route, $code = null) | Přesměruje aplikaci na zadanou routu |
Další atributy a metody třídy App
Objekt aplikace slouží také jako kontejner pro různé služby, jako je práce s databází, autentizace, práce se soubory, logování, překlad cizojazyčných textů a podobně. Každá služba je objekt, který je interně uložený v poli $app->services. Pro práci s ním lze použít metody getService(), setService(), ale obvykle s ním pracujeme pomocí $app->jmeno_sluzby (aplikace vytváří property, která interně volá tyto dvě metody)
//Vytvoření služby 'db' a práce s ní
$app->db = new pclib\Db('pdo_mysql://jmeno:heslo@host/jmeno_databaze');
$result = $app->db->select('tabulka', ['id' => 1]);
Služby můžeme inicializovat také pomocí konfiguračního klíče aplikace 'pclib.app':
$config = [
'pclib-app' => [
'autostart' => ['db']
],
'service.db' => ['dsn' => 'pdo_mysql://jmeno:heslo@host/jmeno_databaze'],
];
V poli 'autostart' jsou uvedené služby, které se mají automaticky spustit a 'service.jmeno_sluzby' obsahuje konfiguraci služby.
Pomocí parametru $app->request můžeme získat informace o aktuálním requestu, jako například url, clientIP, headers a další - např. $aktualniUrl = $app->request->url. Viz třída pclib\Request v referenčním manuálu.
Někdy potřebujeme aby aplikace měla konfigurační parametry, které mohou editovat správci v administračním rozhraní (v eshopu by to mohly být například notifikační emailová adresa, cena poštovného a podobně). K tomu slouží databázová tabulka APP_PARAMS. Příslušný parametr tam s popisem přidáme a správce pak může jeho hodnotu měnit v aplikaci padmin. V kódu k parametrům přistupujeme pomocí služby $app->params. Službu nejprve inicializujeme (např pomocí 'autostart' => ['params'] v konfiguraci) a pak můžeme číst parametry: $app->params->get('CENA_POSTOVNE') nebo rovnou $app->params->CENA_POSTOVNE.
Šablony
class pclib\Tpl🔸Seznam elementů šablony
Šablony jsou HTML soubory obsahující proměnné šablon {POLE}, jejichž hodnoty se nastavují z kódu. Se šablonami pracuje třída pclib\Tpl a také třídy pclib\Grid a pclib\Form, které přidávají elementy datagridu (stránkování a řazení) resp. elementy formuláře, jako je input, select, textarea a podobně.
Jestliže šablona obsahuje například proměnnou {title}, nastavíme její hodnotu pomocí atributu values: $tpl->values['title'] = 'Titulek šablony'; Výsledné html získáme pomocí funkce html(): print $tpl->html();
Šablona nemusí, ale může, obsahovat sekci <?elements ... ?> kde lze definovat typ, způsob zobrazení, formátování a jiné parametry pro každou proměnnou šablony.
<?elements
string description format "n"
bind category lookup "categories"
string published_at date "d.m.Y"
number price format "2,."
block sleva noprint
?>
<h1>{title}</h1>
<p>{description}</p>
<p>Kategorie: {category}</p>
<p><img src="{@baseurl}{image_path}" class="product-image"></p>
<p>Datum vydání: {published_at}</p>
{if price}<p>Cena: {price} Kč</p>{/if}
{if not price}<p>Cena dohodou.</p>{/if}
{block sleva}
<div class="alert alert-info">Produkt je ve slevě.</div>
{/block}
V horní části je sekce elements, definující formátování některých polí, pod ní je html kód s proměnnými šablony, který se vypíše.
Formátování: V případě pole description se odřádkování zkonvertují na značku <br>, published_at se zformátuje jako datum a cena (price) bude zaokrouhlená na dvě místa s uvedenými oddělovači tisíců a desetinnou tečkou, např. 1,100.50 Kč.
Parametr {@baseurl} je proměnná dostupná v kterékoliv šabloně, obsahující hlavní (nejvyšší) url aplikace.
Jestliže máme databázovou tabulku products, která obsahuje sloupce id, title, description, price a published_at, můžeme výše uvedenou stránku detailu produktu vypsat takto:
$tpl = new pclib\Tpl("tpl/products/detail.tpl");
$product = $this->db->select('products', ['id' => $id]);
$tpl->values = $product;
return $tpl->html();
Nebo pomocí funkce controlleru template():
$product = $this->db->select('products', ['id' => $id]);
return $this->template("tpl/products/detail.tpl", $product);
Bloky
Někdy je potřeba vypsat části šablony podmíněně. K tomu slouží v šabloně bloky a "ify".
Blok {block name}...{/block} ohraničuje část šablony, je možné ho skrýt nebo hromadně nastavit atributy a hodnoty polí v něm. Blok sleva má nastavený atribut noprint, takže je skrytý. Aktivujeme ho v kódu pomocí $tpl->enable('sleva');
Je možné i nastavit proměnné viditelné pouze v bloku:
order.php:
$tpl->values['address'] = ['street' => 'Vodičkova', 'city' => 'Praha', ...];
order.tpl:
{block address}
<p>{street}</p>
<p>{city}</p>
{/block}
Sekce {if price}...{/if} se vypíše pouze pokud je nastavené pole "price". Jinak se vypíše "Cena dohodou."
Podšablony (include)
Šablonu je možné rozdělit na více částí a vkládat podšablony pomocí include. Například jestliže máme ve více formulářích checkboxy souhlas s obchodními podmínkami a zpracování osobních údajů, můžeme vytvořit podšablonu agreement a pak ji použít ve všech formulářích: <?elements
class form name "order-form"
include agreement file "tpl/partial/agreement.tpl"
...
?>
...definice objednávkového formuláře...
{agreement}
Pclib před vykreslením šablony vloží šablonu agreement a sloučí i definici elementů obou šablon.
Občas se může hodit vložit do šablony přímo i výsledek volání nějaké routy. To zajistí element action.
<?elements
action title_image route "catalog/image/id:{id}"
...
?>
{title_image}
Tento kód vloží do šablony obsah cesty catalog/image, tedy výsledek volání CatalogController->imageAction($id). Routa může obsahovat i parametry.
Vlastní výpis pole
Nastavením atributu onprint pro libovolné pole ho můžeme vypsat pomocí vlastní funkce.
string price onprint "printPrice"
function printPrice($tpl, $id, $sub, $value) {
if ($value) print "$value Kč";
else print "Cena dohodou";
}
Funkce printPrice() přebírá jako parametry odkaz na objekt šablony, id pole (zde "price"), id modifikátoru (např. 'lb') a hodnotu pole.
Do onprint lze přiřadit libovolný php callable, takže například použít funkci controlleru můžeme takhle:
class ProductsController extends pclib\Controller {
public function showAction($id)
{
$product = $this->app->db->select('products', ['id' => $id]);
if (!$product) $this->app->error("Produkt nenalezen!", "alert alert-danger");
$tpl = new pclib\Tpl('tpl/products/detail.tpl');
$tpl->values = $product;
//nastavime atribut 'onprint' pro pole 'price'
$tpl->elements['price']['onprint'] = [$this, 'printPrice'];
return $tpl;
}
public function printPrice($tpl, $id, $sub, $value)
{
if ($value) print "$value Kč";
else print "Cena dohodou";
}
}
Grid
class pclib\Grid🔸Seznam elementů gridu
Třída Grid (potomek Tpl) slouží k výpisu záznamů, jako je seznam produktů, ve stránkovatelném seznamu. Podle jednotlivých sloupců lze řadit a seznam lze filtrovat.
public function indexAction()
{
$grid = new pclib\Grid('tpl/products/grid.tpl', 'products');
$grid->setQuery("select * from products");
return $grid;
}
Vytvoříme šablonu (druhý parametr konstruktoru říká, že řazení, stránkování a filtr se mají pamatovat v session proměnné 'products'. To zajistí, že se nastavení bude pamatovat i když přejdete na jinou stránku. Je nutné v index.php inicializovat sessions pomocí session_start() ). Následuje dotaz, který je zdrojem záznamů. Za předpokladu, že v tabulce products jsou sloupce id, title, price atd. můžeme je použít v šabloně gridu.
<?elements
class grid name "products"
string title lb "Název produktu" sort
string description format "n" skip
bind category lookup "categories" lb "Kategorie" sort
string published_at date "d.m.Y" lb "Publikováno" sort
number price format "2,." lb "Cena" sort
link lndetail route "products/show/id:{id}" lb "Detail"
pager pager pglen "20"
?>
<table>
<tr>{grid.labels}</tr>
{block items}
<tr>{grid.fields}</tr>
{block else}
<tr><td colspan="10" align="center">Nenalezeny žádné výsledky.</td></tr>
{/block}
</table>
<div class="pager">{pager}</div>
Šablona gridu obsahuje speciální blok items, který se vypíše pro každý řádek přehledu. Pokud nebudou nalezeny žádné položky, vypíše se sekce {block else}.
Každá položka obsahuje atribut lb, který se použije jako popisek sloupce a přidáním atributu sort zajistíme, že po kliknutí se grid podle sloupce seřadí. Specifickým elementem gridu je {pager}, který zobrazí komponentu navigace mezi stránkami. Délka stránky je nastavená na 20 řádků. Element link vytvoří odkaz na detail produktu (link lze použít i v obyčejných šablonách).
V této šabloně používáme zkrácený zápis {grid.labels} a {grid.fields}, který vypíše všechny sloupce uvedené v sekci elements (sloupec lze vynechat přidáním atributu skip). Pokud potřebujete složitější layout, můžete pole v šabloně rozepsat i jednotlivě:
<table>
<tr>
<th>{title.lb}</th>
<th>{category.lb}</th>
<th>{price.lb}</th>
</tr>
{block items}
<tr class="link" onclick="{lndetail.js}">
<td>{title}</td>
<td>{category}</td>
<td>{price} Kč</td>
</tr>
{/block}
</table>
Pokud chceme datagrid filtrovat, použijeme k tomu atribut $grid->filter. Například $grid->filter['category'] = 1; zapne filtr podle kategorie. Dotaz gridu pak musíme upravit takto:
$grid->setQuery(
"select * from products
where 1
~ and category='{category}'"
);
Parametr {category} se nahradí hodnotou z $grid->filter. Vlnovka (~) na začátku znamená podmíněné použití. Jestliže není proměnná category nastavená, filtr na kategorii se nevykoná (řádek s vlnovkou bude vynechán).
Grid je možné mimo jiné exportovat do formátu csv, nebo do formátu vhodného k otevření v excelu: $grid->exportCsv('soubor.csv') nebo $grid->exportExcel('soubor.csv')
Formuláře
class pclib\Form🔸Seznam elementů formuláře
Třída Form (potomek Tpl) slouží k zobrazení, uložení a validaci formulářů. Šablony formulářů umí vytvářet všechny běžné formulářové komponenty včetně nastavení validačních pravidel, ošetřují vstupy, umožňují flexibilitu pokud jde o podobu formuláře a zároveň zachovávají práci s formuláři v kódu jednoduchou a přehlednou.
public function editAction($id)
{
$product = $this->app->db->select('products', ['id' => $id]);
if (!$product) $this->app->error("Produkt nenalezen!", "alert alert-danger");
$form = new pclib\Form('tpl/products/form.tpl');
$form->values = $product;
$form->enable('update');
return $form;
}
Tento kód vytvoří a zobrazí editační formulář produktu. Nejdřív načteme data produktu z databáze do proměnné $product, pak jimi naplníme formulář (nastavením $form->values) a zobrazíme. Stránka se zobrazí po zadání routy obsahující id produktu - například products/edit?id=1
Šablona formuláře vypadá takto:
<?elements
class form route "products/id:{GET.id}" html5
input title lb "Název produktu:" required
text description lb "Popis:" size "50x4" required
select category lb "Kategorie:" lookup "categories" required
input price lb "Cena:" size "10"
input image_path file accept "image/*" size_mb "2" lb "Obrázek"
check published lb "Publikováno" default "1"
input published_at lb "Datum vydání:" date
button insert lb "Přidat" noprint
button update lb "Uložit" noprint
button delete lb "Smazat" confirm "Opravdu smazat?" noprint
?>
<table>
{form.fields}
</table>
První řádek určuje že se formulář odesílá do ProductsControlleru, konkrétní akci udává název tlačítka. Při aktualizaci je zapnuté pouze tlačítko update, routa tedy bude products/update. Id produktu převezmeme z url pomocí parametru id:{GET.id} Atribut html5 zapíná html5 klientskou validaci formuláře.
Následující položky vytvoří pole formuláře INPUT, TEXTAREA, SELECT a CHECKBOX. Atribut required znamená povinné pole, lb jeho popisek, size uvádí velikost pole, date provádí validaci datumu a default je implicitní předvyplněná hodnota pole. Tyto atributy lze použít u kteréhokoliv pole formuláře.
Pokud chcete mít ve formuláři pole, které se nebude ukládat, zajistí to parametr nosave, needitovatelné pole (disabled) vytvoříme atributem noedit. Například input order_no lb "Číslo zakázky" noedit nosave.
Select musí obsahovat zdroj dat, odkud se načtou položky výběrového pole - zde se používá číselník categories. Položky lze načíst i pomocí dotazu (query), textového seznamu (list) nebo funkcí php (datasource). Stejným způsobem lze vytvořit i pole checkboxů nebo radiobuttonů.
Pole input file slouží k uploadování souborů. Zde je nastavená validace pouze na obrázkové soubory s maximální velikostí souboru 2MB.
Tlačítka jsou implicitně vypnutá (noprint) a zapíná se pouze tlačítko update, které odešle formulář do akce ProductsController->updateAction($id).
V této šabloně používáme zkrácený zápis {form.fields}, který vypíše všechny pole uvedené v sekci elements (pole lze vynechat přidáním atributu skip). Pokud potřebujete složitější layout, můžete pole v šabloně rozepsat i jednotlivě:
<table>
<tr>
<td>{title.lb}</td>
<td>{title}</td>
</tr>
<tr>
<td>{description.lb}</td>
<td>{description}</td>
</tr>
...
</table>
Po odeslání formuláře jsou uživatelem vyplněné hodnoty načtené v poli $form->values. Lze je upravovat, vypsat nebo uložit do databáze buď samostatně, nebo pomocí formulářových funkcí $form->insert(), $form->update() a $form->delete().
public function updateAction($id)
{
$form = new pclib\Form('tpl/products/form.tpl');
if (!$form->validate()) {
$this->app->error("Chybně zadané parametry.", "alert alert-danger");
};
$form->update('products', ['id' => $id]);
$this->app->message('Položka byla uložena.', "alert alert-success");
$this->redirect('products/show/id:' . $id);
}
Tato akce se zavolá po odeslání formuláře, provede kontrolní validaci (ta uživatelská se vykoná už v prohlížeči před odesláním) a aktualizuje produkt v databázi. Následně provede přesměrování na products/show a zobrazí informační message.
Pokud je formulář odeslaný, je nastavená hodnota $form->submitted a obsahuje název odesílacího tlačítka.
Ukládání souborů
Pro nejjednodušší práci se soubory postačí základní nastavení:
input SOUBOR lb "Obrázek" file into "/uploaded" accept "image/*"
V atributu into je cesta k adresáři, kam se má soubor uložit a formulář řeší ukládání, mazání a aktualizování souboru sám. Pokud se formulář ukládá do databáze, musí být v tabulce sloupec SOUBOR (pro každý ukládaný soubor musí existovat příslušné pole v tabulce pojmenované jako input ve formuláři). Pak funkce $form->insert(), $form->update() a $form->delete() budou automaticky aktualizovat všechny soubory ve formuláři.
Pokud chceme mít ukládání souboru zcela ve vlastní režii, můžeme v šabloně nastavit parametr nosave a po odeslání formuláře získat soubor takto: $file = $form->getFile('SOUBOR');
Použití FileStorage
Služba fileStorage umožňuje ukládat libovolné množství souborů k libovolné položce aplikace. Veškeré informace o souborech jsou v databázové tabulce FILESTORAGE, což umožňuje prohledávání, hromadné zpracování apod. Vlastní soubory jsou ukládány do podadresářů ./uploaded/rok/měsíc/
Třída FileStorage dovoluje pracovat se soubory jednotným způsobem. Nejprve inicializujeme službu v konfiguračním souboru aplikace:
$config = [
'pclib.app' => [
'autostart' => ['db', 'fileStorage'],
],
'service.fileStorage' => ['rootDir' => './uploaded'],
];
Soubory budou pak ukládány do podadresářů ./uploaded/rok/měsíc/. Tedy např. uploaded/2025/10/clients_18urYQjU.jpeg.
Zavolání $form->update('clients', 1); uloží soubory ve formuláři pro klienta s id=1. Obdobně pracují i funkce $form->insert() a $form->delete().
Chceme-li získat všechny soubory nahrané k tomuto klientovi ['clients', 1] zavoláme:
$fs = $app->fileStorage; $files = $fs->getFiles(['clients', 1]);
Každý záznam pole $files obsahuje řádek z tabulky FILESTORAGE, kde je ID, FILEPATH (cesta k souboru), ORIGNAME (originální název), SIZE (velikost souboru) a další údaje.
Se soubory můžeme pracovat pomocí funkcí třídy FileStorage: getFile(), setFile(), deleteFile() pro jeden soubor, nebo getFiles(), setFiles(), addFiles(), deleteFiles() které vrátí, nastaví nebo smaže všechny soubory přiřazené konkrétní entitě.
Konkrétní soubor lze v kterékoliv funkci adresovat jako pole [typ_entity, id_entity, id_souboru] anebo jen pomocí id záznamu tabulky FILESTORAGE – např. $fs->getFile(['clients', 1, 'SOUBOR']) resp. $fs->getFile($id)
Jedná-li se o obrázek, nebo jiný zobrazitelný soubor, můžeme ho vypsat funkcí $fs->output(), která doplní potřebné hlavičky:
$fs->output(['clients', 1, 'SOUBOR']); //Vypiš soubor
| Další tipy: | |
|---|---|
| $file = $fs->getFile($id, true) | Načte soubor včetně obsahu $file['CONTENT']. |
| $fs->output(['HASH' => $hash]) | Vypíše soubor podle jeho hashe z tabulky FILESTORAGE - bezpečnější způsob neumožňující neoprávněný přístup k jiným souborům. |
| $files = $fs->postedFiles() | Manuální načtení souborů po odeslání formuláře. |
Databáze
Pclib\Db je služba pro zjednodušení práce s databází, která mj. ošetřuje parametry dotazů, tak, aby nebyl možný útok pomocí sql-injection. Inicializovat ji můžete v index.php příkazem $app->db = new pclib\Db('pdo_mysql://jmeno:heslo@host/jmeno_databaze'); nebo pomocí konfiguračního souboru. V metodě controlleru k ní lze přistupovat jako $this->app->db.
Připojovací řetězec je jednotný a obsahuje uživatelské jméno a heslo, adresu hosta a jméno databáze - například 'pdo_mysql://root:abc123@127.0.0.1/test-app'.
Ukázka použití:
| $product = $db->select('products', ['id' => 1]) | Vrátí záznam s id=1 z tabulky products |
| $products = $db->selectAll('products', ['price' => 1000]) | Vrátí všechny produkty s cenou 1000 |
| $product = $db->select('products:id,title,price', ['id' => 1]) | Vrátí vybraná pole produktu 1 |
| $id = $db->insert('products', ['title' => 'Nový produkt', 'price' => 1000, 'category' => 1]) | Vloží nový produkt a vrátí jeho id |
| $db->update('products', ['title' => 'Nový název', 'price' => 1000], ['id' => $id]) | Aktualizuje produkt s id=1 |
| $db->delete('products', ['id' => 1]) | Smaže produkt s id=1 |
| $res = $db->query("select * from products where id=1"); $row = $db->fetch($res); |
Obecný dotaz a vrácení jeho hodnoty |
Pokud potřebujeme zadat složitější podmínku s parametry můžeme využít zadání parametrů pomocí {nazev_parametru} nebo {#nazev_parametru} pro numerickou (integer) hodnotu.
| $products = $db->selectAll('products', "price > {#price}", ['price' => 1000]) | Vrátí produkty s cenou větší než 1000 |
| $products = $db->selectAll("select * from products where title like '%{search}%' order by title", ['search' => 'notebook']) | Vrátí produkty s názvem obsahujícím 'notebook' |
| $db->update('products', ['published' => 0], "title like '%notebook%'") | Nastaví notebooky jako nepublikované |
Existují i další varianty zadávání podmínek s parametry - viz referenční manuál
Jen tak zajistíte, že vstupem z parametru nebude možné podstrčit sql-injection kód.
ORM
class pclib\orm\Selection🔸class pclib\orm\Model🔸Seznam elementů modelu
Pro pokročilejší práci s databází lze využít vrstvu ORM. Ta se skládá ze dvou objektů: Selection a Model reprezentujících databázový dotaz a databázový záznam. Nejjednodušeji je můžeme získat v controlleru metodami $this->selection() a $this->model().
public function latestAction()
{
$sel = $this->selection('products')->where("active=1")->order("published_at desc")->limit(100);
$grid = new pclib\Grid('tpl/products/grid.tpl', 'products');
$grid->setSelection($sel);
return $grid;
}
public function showAction($id)
{
$product = $this->model('products', $id);
if (!$product) $this->app->error("Produkt nenalezen!", "alert alert-danger");
return $this->template('tpl/products/detail.tpl', $product->toArray());
}
Některé další funkce třídy Selection: $sel->first() - vrátí první záznam výběru, $sel->delete() - smaže výběr, $sel->toArray() - vrátí ho jako pole, $sel->toSql() - vrátí SQL kód výběru, $sel->count() - vrátí počet řádků, $sel->sum('price') - sumarizuje cenu ze všech řádků výběru.
Model reprezentuje řádek databázové tabulky. Pokud nenadefinujeme vlastní třídu, vytvoří se jako instance bázové třídy Model. S ní lze pracovat podle návrhového vzoru ActiveRecord.
$product = $this->model('products', $id);
$product->price = 1000; //Nastavíme pole price v tabulce products
$product->save(); //Uložíme změny do databáze
Hlavní síla modelu je v tom, že můžeme vytvořit šablonu definující např. vazby mezi modely v adresáři /models/templates nebo i vlastní třídu v adresáři /models. Pokud pclib najde soubory s odpovídajícím názvem (např. models/ProductsModel.php a models/templates/Products.tpl) použije je pro tabulku products.
<?elements
class model table "products"
relation category table "categories" key "category_id" owner
relation variants table "product_variants" key "product_id" many
relation images table "product_images" key "product_id" many
event ondelete delete "variants,images"
?>
Nyní lze v modelu používat vazby (relation) kategorie a varianty produktu, odkazující se na vazební tabulky. Můžeme vypsat titulek kategorie nebo vrátit všechny varianty produktu. Vazba 1:N vrací Selection, takže můžeme varianty řadit, filtrovat apod.
Pomocí event ondelete... můžeme určit, co se má stát v případě smazání záznamu. Atribut delete definuje navazující záznamy, které budou smazány spolu s produktem. Lze použít i atribut cancel_when "relace1,relace2,...", který odmítne smazat produkt, jestliže existuje navazující záznam.
$title = $product->category->title;
print "Počet variant: " . $product->variants->count();
foreach($product->variants->order('title') as $variant) {
print "<br>";
print $variant->title;
}
Kromě šablony můžeme definovat i třídu modelu s vlastními metodami, které lze použít všude tam, kde máme načtený model (např. produktu).
class ProductsModel extends pclib\orm\Model
{
public function getPriceInCurrency($currency) {}
protected function deleteFiles() {}
//Smazani priloh pri smazani modelu.
public function delete()
{
$ok = parent::delete();
if ($ok) $this->deleteFiles();
return $ok;
}
}
Některé další funkce třídy Model: $model->save() - Uloží změny do databáze, $model->delete() - Smaže záznam, $model->find($id) - Vyhledá model s primárním klíčem $id a načte ho, $model = Model::create('tabulka', $zaznam) - vytvoří nový záznam v databázi.
Další informace o pclib ORM naleznete v PDF dokumentu Pclib ORM.
Služba Auth
class pclib\AuthSystém autentizace a autorizace pracuje se třemi typy objektů: uživatelé, role a oprávnění. Role má přiřazená oprávnění a uživatel může mít přiřazenou jednu nebo více rolí. Je možné přiřadit oprávnění i přímo konkrétnímu uživateli. Uživatel se přihlašuje pomocí uživatelského jména a hesla. V aplikaci pak můžeme testovat oprávnění přihlášeného uživatele.
Službu inicializujeme buď v souboru index.php: $app->auth = new pclib\Auth nebo prostřednictvím konfigurace.
'service.auth' => [
'algo' => 'bcrypt',
'secret' => '',
],
'pclib.app' => [
'autostart' => ['db', 'auth'],
]
Parametr 'algo' udává hashovací algoritmus pro ukládání hesel. Možnosti jsou 'md5', 'bcrypt' a 'bcrypt-md5'. Při použití md5 je nutné ještě nastavit 'secret'. Jedná se o náhodný řetězec alespoň deseti znaků, který se využívá k posílení hesla. Před použitím Auth je potřeba spustit i službu databáze, kterou Auth využívá.
Přihlášení uživatele se provede příkazem $auth->login('jmeno', 'heslo'), odhlášení pomocí $auth->logout(). Funkce login() vrací false pokud se přihlášení nepodařilo a chyby /např. chybné heslo/ lze poté vyčíst z pole $auth->errors. Po úspěšném přihlášení je přihlášený uživatel dostupný přes $auth->loggedUser.
Pro přihlášení, odhlášení, registraci, zapomenuté heslo apod. je nejlépe vytvořit samostatný controller.
class UserController extends BaseController {
function signinAction()
{
return new pclib\Form('tpl/user/signin.tpl');
}
function loginAction()
{
$form = new pclib\Form('tpl/user/signin.tpl');
$ok = $this->app->auth->login($form->values['username'], $form->values['password']);
if ($ok) {
$this->app->message('Vítáme Vás na našich stránkách.', 'alert alert-success');
$this->redirect('home');
}
else {
$this->app->message('Chybné přihlašovací údaje.', 'alert alert-danger');
$this->redirect('user/signin');
}
}
function logoutAction()
{
$this->app->auth->logout();
$this->app->message('Byl jste odhlášen.', 'alert alert-success');
$this->redirect('home');
}
}
Akce user/signin zobrazí přihlašovací formulář, akce user/login resp. user/logout uživatele přihlásí resp. odhlásí a přesměruje na home.
Nyní můžeme testovat oprávnění přihlášeného uživatele v controlleru.
function editAction($id) {
if (!$this->app->auth->hasRight('products/edit') {
$this->app->error('Nemáte oprávnění editovat produkty');
}
...
}
Lze použít i pomocnou metodu authorize() která ukončí aplikaci se standardní chybovou hláškou, jestliže uživatel nemá oprávnění. Pokud je nepřihlášený, přesměruje ho na přihlašovací formulář, standardně na routu user/signin.
function editAction($id) {
$this->authorize('products/edit');
...
}
Často bývá výhodné vytvořit si v BaseControlleru zkratku k přihlášenému uživateli, aby byl dostupný ve všech našich controllerech.
class BaseController extends pclib\Controller {
protected $user;
function __construct($app)
{
parent::__construct($app);
$this->user = $this->app->auth->loggedUser;
}
}
Proměnná $this->user je buď null pro nepřihlášeného uživatele, nebo objekt typu AuthUser.
function ordersAction()
{
if (!$this->user) {
return "Nelze zobrazit objednávky pro nepřihlášeného uživatele.";
}
$orders = $this->app->db->selectAll('orders', ['user_id' => $this->user->id]);
return $this->template('tpl/orders/list.tpl', ['orders' => $orders, 'user' => $this->user->getValues() ]);
}
Lze vytvořit oprávnění, které obsahuje wildcard '*'. Pokud má uživatel přiřazené právo products/*, znamená to, že má povolená práva products/edit, products/delete, products/jakykoliv-retezec.
| Id oprávnění | Popis |
|---|---|
| products/edit | Editace produktů |
| products/delete | Smazání produktu |
| products/* | Všechna práva produkty |
| * | Všechna práva |
Nastavení rolí, uživatelů a oprávnění je nejjednodušší pomocí aplikace padmin. Lze to ovšem i programově pomocí třídy AuthManager.
Logování
class pclib\LoggerLogování se standarně provádí do databázové tabulky LOGGER. K prohlížení, vyhledávání nebo mazání starších logů lze využít nástroj padmin. Před použitím službu logger inicializujeme v konfiguraci:
'pclib.app' => [
'autostart' => ['db', 'logger'],
]
V aplikaci se záznam do logu zapíše funkcí $logger->log(...), nebo nejčastěji v controlleru pomocí $this->app->log(...);
public function updateAction($id)
{
...
$this->app->log('action', 'products/update', null, $id);
$this->app->message('Položka byla uložena.', "alert alert-success");
$this->redirect('products/show/id:' . $id);
}
Parametry funkce log() jsou: kategorie, id zprávy, zpráva (volný text) a id položky. Kategorie a id zprávy jsou povinné a do databáze se ukládají z důvodu efektivity pomocí číselného id, které se odkazuje na číselník. Pokud id zprávy nebo kategorie dosud neexistuje, automaticky se založí.
Zprávu lze volitelně použít, pokud je potřeba více informací k logovanému záznamu: $this->app->log('action', 'mail/send', "10 mailů úspěšně odesláno.");
Poslední položka udává id objektu, kterého se záznam týká - zde id produktu. Kromě těchto informací se zaznamenává také čas, id přihlášeného uživatele, ip adresa, user agent a operační systém ze kterého byl požadavek odeslán.
Nastavením konfiguračního parametru pclib.errors['log'] na true se zapne logování chyb aplikace.
Debugging
K podpoře ladění aplikace slouží především několik nástrojů výpisu obsahu proměnných. Funkce dump($promenna1, $promenna2, ...) zobrazí vizuálně vylepšený výpis hodnoty proměnných (může se jednat i o pole nebo objekty) a ukončí běh skriptu.
Jestliže je v konfiguračním souboru zapnutý konfigurační klíč develop, pclib budou při chybě zobrazovat rozšířený výpis chyby včetně posloupnosti volání funkcí (stack trace).
$develop = [ 'pclib.errors' => ['display' => true, 'develop' => true], ];
Na produkčním serveru by naopak měl být develop vždy vypnutý, protože může zobrazovat bezpečnostně citlivé informace a naopak by mělo být aktivní ukládání chyb do logu. Pclib implicitně považují za develop localhost a production nastavení použijí kdekoliv jinde.
Ladící funkce pclib:| dump(...) | Vypíše obsah proměnných a ukončí aplikaci |
| jdump(...) | Vypíše obsah proměnných do javascript konzole prohlížeče |
| ddump(...) | Vypíše obsah proměnných do debugovací lišty pclib |
Při vývoji lze zapnout také debugovací lištu nastavením konfiguračního parametru pclib.app['debugmode'] na true. V liště se zobrazují volané požadavky, všechny databázové dotazy, hodnoty odeslané formulářem a debugovací výpisy pomocí funkce ddump().
Na serveru localhost je možné v debugovací konzoli spustit php kód v kontextu aplikace. Proměnná $app je zde objekt aplikace a $c je aktuální controller.
Vícejazyčnost
class pclib\TranslatorPřeklad textu provedeme pomocí funkce $app->translate(), která vyhledá text v aktivním jazyce ($app->language) v tabulce textů a vrátí ho, pokud existuje. Pokud text nenajde, vypíše původní text.
$app->language = 'cs';
print $app->translate('Hello world!'); // Ahoj světe!
print $app->translate('Today is %s', date('d.m.Y')); //Dnes je ...
Tabulku textů lze načíst i ze souboru: $app->translator->useFile('localization/cs.php'); ale nejčastěji se čte z databázové tabulky. Zadávat texty pro různé jazyky můžete pomocí nástroje padmin, který také umožňuje exportovat a importovat tabulku textů ve formátu csv (excel).
Pclib automaticky zařazuje některé texty jako texty k překladu. Jsou to: zprávy aplikace (texty vypisované pomocí $app->message() a $app->error() ), popisky polí (atribut "lb") v šabloně, číselníky (lze vypnout atributem "notranslate") a veškerý text v šablonách ohraničený tagem <M>...</M>
Místo vkládání všech použitých textů do tabulky ručně, je možné nastavit jazyk na source: $app->language = 'source' a pclib potom při zobrazení textu k překladu automaticky zaznamenají zdrojovou podobu textu do databáze.
Události
class pclib\EventManager🔸Seznam událostí
Globální nastavení událostí je možné pomocí služby $app->events, nastavení pro konkrétní objekt zavoláním funkce on() na tomto objektu.
//Nastavení události pro konkrétní datagrid
$grid->on('grid.before-row', $function);
//Nastavení události pro všechny gridy
$app->events->on('grid.before-row', $function);
Výše uvedená událost spustí funkci $function před výpisem každého řádku gridu. Parametrem volané funkce je objekt $event typu pclib\Event. Ten obsahuje odkaz na volající objekt $event->target a parametry eventu $event->data.
$function = function($event) {
$grid = $event->target;
if ($event->data['row']['akcni_sleva']) $grid->print_Block('akce');
};
Strom
class pclib\TreeTřída pclib\Tree slouží k vytváření stromové struktury, využitelné například pro tvorbu menu nebo rozbalovacího stromu kategorií. Položky stromu lze načíst z databáze, případně exportovat nebo importovat do textového formátu nebo jako php-pole.
$tree = new pclib\Tree('tpl/tree.tpl');
$tree->load(1);
print $tree->html();
Vytvoříme strom ze šablony tpl/tree.tpl, načteme položky z databázové tabulky TREE_LOOKUPS (TREE_ID=1) a vypíšeme. Se šablonou můžeme pracovat obdobně jako u běžné šablony, např. nastavovat její proměnné pomocí values: $tree->values['CSS_CLASS'] = 'pctree';
Pokud máme stromovou strukturu uloženou například v tabulce categories (s odkazem na rodiče pomocí sloupce parent_id), načteme položky stromu takto: $tree->fromQuery("select id, label, url, parent_id from categories where active=1");
Pokud nezadáme cestu k šabloně, použije se implicitní šablona, jinak si ji můžeme definovat běžným způsobem:
{block root}
<ul id="tree1" class="{CSS_CLASS}">{items}</ul>
{/block}
{block folder}
<li id="i{ID}" class="folder {OPEN}">
<span>{LABEL}</span>
<ul>{items}</ul>
</li>
{/block}
{block item}
<li id="i{ID}"><a href="{URL}">{LABEL}</a></li>
{/block}
Šablona stromu obsahuje tři bloky: pro kořen stromu (root), pro složku (folder) a položku stromu (item). V tomto případě vytváříme html seznam <ul>, který už se dá libovolně nastylovat. Proměnné šablony pocházejí z tabulky nebo dotazu a v podstatě můžou být jakékoliv. Vyžadovány jsou obvykle LABEL (popisek - text), ID a URL.
Načtení z textového souboru a uložení do databáze provedeme následovně:
$tree->importText($text); $tree->save(1); //Ulozit jako TREE_ID=1
$text obsahuje parametry položek stromu oddělené '|' (zde cesta s popiskami a url). Cesta do podvětví (podadresářů) je oddělená lomítkem '/'.
PATH|URL MyShop MyShop/About Us MyShop/About Us/History|https://myshop.cz/history MyShop/About Us/Partners|https://myshop.cz/partners MyShop/Categories MyShop/Categories/Computer|https://myshop.cz/categories/computers
S položkami stromu je možné pracovat i programově:
$node = $tree->find('URL', 'https://myshop.cz/partners');
$node['LABEL'] = 'Our partners';
$tree->set($node['ID'], $node);
Mailer
class pclib\MailerMailer slouží k managementu odesílání emailů. Lze definovat šablony mailů, naplánovat jejich odesílání a ukládat odeslané emaily. Správa šablon a emailových adres je k dispozici správcům v aplikaci pamin. Pro vlastní odesílání emailů je nutné nainstalovat emailovou knihovnu - v současnosti je podporován pouze phpmailer od verze 6.9. Nainstalujeme ho příkazem composer require phpmailer/phpmailer.
Nejdřív zapneme službu mailer a nastavíme připojení k SMTP serveru, popřípadě další parametry (layout pro všechny maily a odesílací adresa). Mailer používá databázi, kterou proto inicializujeme také.
'pclib.app' => [ 'autostart' => ['db', 'mailer'], ], 'service.mailer' => [ 'dsn' => 'smtp://user:password@smtp.host.com', 'from' =>'Moje aplikace <noreply@myapp.cz>', 'layout' =>'tpl/mails/layout.tpl', ], 'service.db' => ['dsn' => 'pdo_mysql://user:password@localhost/myshop'],
Nyní můžeme odesílat emaily:
$app->mailer->send('order-confirm.tpl', $order, ['to' => 'josef.novak@gmail.com']);
Tento příkaz odešle mail potvrzení objednávky (šablona order-confirm) na adresu zákazníka. Data objednávky jsou v poli $order (použijí se v šabloně). Šablona je standardní šablona, která umožňuje navíc v sekci elements definovat i emailová pole jako třeba předmět mailu. Šablony se hledají v adresáři tpl/mails/ což můžeme změnit pomocí konfiguračního parametru templates_path.
<?elements
class mail
mail_field subject default "Potvrzení objednávky"
?>
<p>Vaše objednávka číslo {ORDER_NO} byla přijata.</p>
Jak v šabloně, tak i ve funkci send() můžeme použít jakákoliv emailová pole (from, to, cc, bcc, subject, replyTo). Pokud je pole definované v šabloně i v kódu, upřednostní se hodnota z funkce send(). Pro zasílání notifikací nebo jiných "plain message" mailů si můžeme udělat jednoduchou šablonu:
$app->mailer->send('notify.tpl', ['body' => 'Zpráva z aplikace.'], ['to' => 'user@myshop.cz', 'subject' => 'MyShop Notifikace', 'cc' => 'admin@myshop.cz']);
<p>{body}</p>
Kterákoliv adresa může obsahovat také popisek: 'cc' => 'Josef Novák <admin@myshop.cz>'.
V aplikaci padmin si lze vypsat všechny emaily odeslané nebo naplánovaná za posledních deset dní (dobu uchování lze změnit konfiguračním parametrem keep_days).
Místo šablony uložené v souboru můžeme použít šablonu uloženou v databázi. Výhoda je, že tyto šablony mohou editovat správci v aplikaci padmin, aniž by bylo nutné zasahovat do kódu. Následující příkaz použije databázovou šablonu order_confirm, pokud existuje.
$app->mailer->send('order-confirm', $order, ['to' => 'josef.novak@gmail.com']);
Email se nemusí odeslat hned, ale lze ho pouze naplánovat k odeslání:
$app->mailer->schedule('order-confirm', $order, ['to' => 'josef.novak@gmail.com']);
Odeslání se provede zavoláním $app->mailer->dispatch(); např. pomocí cron-jobu.
Mail s přílohou odešleme takto:
$app->mailer->send('notify', ['body' => 'Všechno nejlepší k novému roku.'], ['to' => 'all@myshop.cz', 'subject' => 'Přání k novému roku', 'attachments' => ['uploaded/prani.pdf']);
Je možné přílohu zadat i v šabloně mailu (v sekci elements):
attachment prani default "uploaded/prani.pdf"
Mailer nabízí i další funkce jako automatické logování odeslaných mailů, události mailer.before-send, mailer.after-send nebo vývojářský režim, kdy se maily neodesílají, ale posílá se jejich preview na zadanou adresu: $app->mailer->setDeveloperOnlyMode('tester@myshop.cz');
Jobs
Pro pravidelné spouštění dávkových úloh se obvykle používá služba cron, ale ne vždy má správce přístup k nastavení cronu na webhostingu a jeho konfigurace nemusí být jednoduchá. Proto je možné spravovat dávkové úlohy pomocí padmin. Systém umožňuje úlohy snadno přidávat, zapínat a vypínat, zobrazuje čas posledního spuštění i výsledek, zaznamenává spuštění úloh do logu, lze nastavit periodu nebo úlohu spustit ručně.
K zprovoznění je nejprve nutné nastavit volání JobManageru pomocí cronu. Veškerou další správu je pak možné provádět pomocí administrátoru. V cronu nastavíme volání adresy https://moje-aplikace.cz/admin/?r=jobs/run&key=[api_key], kde [api_key] je hodnota aplikačního klíče nastavená v konfiguraci padminu.
Novou úlohu přidáme vytvořením souboru JobUloha.php v adresáři admin/jobs. Soubor obsahuje stejně pojmenovanou třídu odvozenou od třídy Job. Poté už jednoduše přidáme úlohu pomocí uživatelského rozhraní: menu Jobs -> Přidat úlohu a do kolonky "Příkaz" napíšeme název úlohy, tedy "JobUloha".
class JobCleanOrders extends Job
{
function run()
{
$res = $this->app->db->delete('orders', "status=-1 AND (created_at < NOW() - INTERVAL 1 WEEK)");
return "Smazáno ".$res->rowCount()." nedokončených objednávek.";
}
}
Výše uvedená úloha vymaže nedokončené objednávky starší než jeden týden. Vlastní kód vytvoříme implementací funkce run() jejíž návratová hodnota je výsledek úlohy, který se zobrazuje v administrátoru. Pomocí $this->app můžeme přistupovat k objektu aplikace a jeho službám.
Třída Str
Třída Str obsahuje utility pro práci s textovými řetězci. Používá multibyte funkce, takže funguje správně s utf-8 řetězci. Použití vypadá takto:
use pclib\Str; print Str::length($s); //Počet znaků utf-8 řetězceStr obsahuje jak funkce běžné v php (např: lower/upper - konverze na malá/velká písmena, lpad/rpad - doplní mezery nebo jiný znak zleva/zprava, strpos, substr, nebo contains - vrátí true pokud řetězec obsahuje podřetězec), tak i několik nových funkcí.
| Str::ascii($s) | Konverze na text bez diakritiky | Str::ascii('Řečiště'); // Reciste |
| Str::random($length) | Řetězec náhodných znaků o délce $length | Str::random(10); // Ac85ge7Qw1 |
| Str::webalize($s) | Konvertuje text na formát vhodný k použití v URL | Str::webalize('Akční nabídka'); // akcni-nabidka |
| Str::format($s, $params) | Řetězec s parametry | Str::fomat('Hello {name}!', ['name' => 'John']); // Hello John! |
| Str::filter($s, $regexp) | Zachová v řetězci pouze znaky definované regulárním výrazem | Str::filter("Zápis @porada! #5.docx", "\w."); // Zápisporada5.docx |
| Str::match($s, $regexp) | Vrátí nalezený řetězec podle regulárního výrazu | Str::match("Číslo je 123.", "/\d+/"); // 123 |
| Str::htmlTag($name, $atrib, $content) | Vytvoří html tag s atributy a obsahem | Str::htmlTag('a', ['href' => 'example.com'], 'Example link'); // <a href="example.com">Example link</a> |
ApiController
ApiController slouží k vytvoření REST API aplikace. Funguje velmi podobně jako klasický controller s tím, že rozlišuje POST, GET, PUT, DELETE požadavky a vrací výsledek jako JSON. Zároveň podporuje Bearer autentizaci. Api vytvoříme v podadresáři /api jako samostatnou aplikaci, s tím, že místo třídy Controller použijeme ApiController.
class ProductsController extends pclib\ApiController {
public function getIndexAction()
{
$products = $this->app->db->selectAll("select * from products");
return ['products' => $products];
}
public function postIndexAction($id)
{
$product = $this->request->json;
if (!$product) $this->error("Chybně zadaná data.", 400);
$this->app->db->update('products', $product, ['id' => $id]);
return ['status' => 'ok'];
}
}
Každá akce má na začátku názvu navíc HTTP metodu, takže např. postIndexAction se zavolá pouze při POST požadavku. Akce vracejí pole, které je odesláno klientovi jako json. Volání $this->request->json vrátí json parametry požadavku (lze sazmozřejmě příjmat i klasické post/get proměnné). V případě chyby můžete zavolat funkci error(), která vrátí json s chybovou zprávou a HTTP status kódem.
API je standardně chráněno autorizací, takže klient musí s každým požadavkem poslat autorizační hlavičku Authorization: Bearer {token}. Pokud chceme mít API veřejné, stačí v controlleru nastavit proměnnou $publicApi = true. Lze udělat veřejné (neautorizované) jen některé metody jejich vyjmenováním v proměnné controlleru $publicMethods.
Každý autorizační token má standardně dobu platnosti půl hodiny (od posledního použití). Pro přihlášení uživatele vytvoříme třídu UserController, která bude obsahovat metodu login, které po úspěšném přihlášení vrátí autorizační token.
class UserController extends pclib\ApiController {
public $publicMethods = ['postLoginAction'];
public function postLoginAction()
{
$user = $this->request->post('user');
$password = $this->request->post('password');
if ($user == 'eshop_api' and $password == '12345') {
return ['status' => 'ok', 'token' => $this->token->create(1)];
}
else {
$this->error("Neplatná autentizace", 403);
}
}
}
Metoda login() se volá pomocí POST, aby se přihlašovací údaje neukládaly do logu webserveru. Přečte post proměnné "user" a "password" a vrátí chybu nebo vygenerovaný token.