Báze / Base

Co je to báze jsme si vysvětlili zde. Nyní se podíváme na to jak báze funguje.

Báze je reprezentována těmito třídami:

Třída Význam
Jet\MVC_Base Hlavní třída reprezentující bázi. Drží základní a všeobecné informace o bázi. Tedy ID, název a základní příznaky (například zda je báze tajná a podobně)
Jet\MVC_Base_LocalizedData Tato třída reprezentuje informace o bázi závislé na konkrétní lokalizaci.
Jet\MVC_Base_LocalizedData_MetaTag Třída představující výchozí meta tag (pro HTML hlavičku). Jak název napovídá, tak to spadá pod lokalizované informace. Tedy každá lokalizace má svou sadu výchozích meta tagů.

Poznámka: Ve skutečnosti máte možnost tyto třídy nahradit svými třídami a to zcela transparentně pomocí systému továren a vaší vlastní implementací příslušných rozhraní (interface). Tedy to co je uvedeno dále si můžete předělat / upravit podle vašich potřeb bez přepisování samotné knihovny Jet.

Přehled všech vlastností a metod najdete u popisu jednotlivých tříd. Zde si popíšeme jak obecně technicky báze funguje.

Kde a jak jsou báze uložené?

Samotné definice bází jsou uložené v adresáři ~/application/bases/ (pokud konfigurací neurčíte jinak). Když se kouknete do tohoto adresáře v ukázkové aplikaci distribuované s Jetem, tak tam uvidíte tyto podadresáře:

  • admin
  • rest
  • web

To jsou již samotné báze. A teď první důležitá věc:
Název adresáře = Identifikátor báze 
(tedy admin = báze s ID admin, web = báze s ID web)

Identifikátory si volíte sami. Báze a jejich identifikátory zvolené v ukázkové aplikaci je pouze demonstrace možného použití, ale nejsou závazné.

Můžete mít X bází různého určení, s různými identifikátory... Je to na vás a na vašich projektech.

V každém tomto adresáři najdete několik podadresářů. K nim se dostaneme vzápětí.

Nyní se koukneme na soubor base_data.php

~/application/bases/**/base_data.php

To je již samotná definice báze - nebo chcete-li její konfigurace. (Proč je to PHP soubor se dočtete zde).

Koukněme se na jeden z těchto souborů, třeba na definici báze web v naší ukázkové aplikaci:

return [
    
'id' => 'admin',
    
'name' => 'Administration',
    
'is_secret' => true,
    
'is_default' => false,
    
'is_active' => true,
    
'SSL_required' => false,
    
'localized_data' => [
        
'cs_CZ' => [
            
'is_active' => true,
            
'SSL_required' => false,
            
'title' => 'PHP Jet Framework - admin',
            
'URLs' => [
                
'jet-web.lc/admin/',
            ],
            
'default_meta_tags' => [
            ],
        ],
        
'en_US' => [
            
'is_active' => true,
            
'SSL_required' => false,
            
'title' => 'PHP Jet Framework - admin',
            
'URLs' => [
                
'jet-web.lc/admin/en/',
            ],
            
'default_meta_tags' => [
            ],
        ],
    ],
    
'initializer' => [
        
'JetApplication\\Application_Admin',
        
'init',
    ],
];

Nebudu zde rozepisovat a popisovat jednotlivé hodnoty definice báze. S bází se pracuje primárně přes třídy a jejich metody. Tedy je nutné znát především rozhraní těchto tříd.

A co když si ale budete chtít bázi vytvořit?

Ano, můžete to udělat ručně. Ale sám jsem bázi nevytvářel roky. Bázi v praxi vytváří například instalátor - tak je tomu i v ukázkové aplikaci. Další možnost je použít Jet Studio. V tomto nástroji je celé rozhraní pro práci s bázemi - vyzkoušejte si to: ;-)

V neposlední řadě co v datech je se odvíjí od toho, co je ve třídách. Tyto soubory jsou pouze úložiště a jejich struktura vlastně není závazná. I když je určitě dobré o definicích bází mít povědomí.

Do souboru definice můžete zasahovat ručně. Nic tomu nebrání. Například na produkčním prostředí (kde jistě nemáte Jet Studio) můžete například aktivovat / deaktivovat určitou lokalizaci (jazykovou mutaci) webu, či dokonce celou bázi a podobně.

A nyní se podíváme na podadresáře:

~/application/bases/**/pages/

Toto je klíčový a tedy mandatorní adresář. Obsahuje definice stránek. Přesněji řečeno obsahuje podadresáře jejichž názvy odpovídají kódům jednotlivých lokalizací bází a těchto adresářích jsou již definice stránek. Ale to je již jiné téma.

~/application/bases/**/lyouts/

Tento adresář obsahuje layouty - tedy skripty mající na starost rozvržení stránek.

Layouty mohou být (a v praxi určitě jsou - ve většině případů) sdílené pro víc stránek. Dejme tomu, že jeden layout máte pro home page, další pro články, další pro detail zboží a jiný layout pro nákupní proces v e-shopu. Homepage máte sice jednu, ale layout pro detail zboží určitě bude sdílen (například pro více variant zobrazení zboží a podobně).

Ale jisto jistě vaše administrace bude mít layouty úplně jiné. Proto je adresář zde - layouty jsou v kontextu báze a proto má každá báze svůj adresář pro layouty určený.

Na druhou stranu takové REST API žádný layout nepotřebuje. To samé třeba servisní skripty. Proto adresář není povinný. Například báze rest v ukázkové aplikaci žádné layouty opravdu nemá a adresář neexistuje.

~/application/bases/**/views/

Jak název napovídá, tak je určený pro view skripty. Tedy pro něco, co generuje nějaký konkrétní výstup.

A pozor! Adresář není určen pro všechny view, které budete v projektu mít. Pokud bude vaše aplikace modulární, tak zobrazení článků bude řešit modul Content.Articles (například - název je pouze názorná ukázka, nic závazného) a v jeho adresáři views budou potřebné view skripty. Věřte mi, že je to tak lepší, než mít vše na jedné hromadě ve které je těžké se orientovat.

Ale k čemu je tedy tento adresář view vázaný na bázi, když moduly mají své view adresáře? Slouží pro obecná view. Například pro ta view, která používá systém formulářů, nebo systém generování UI. Prostě všeho co je v obecném kontextu s bází (například formuláře mohou mít jinou obecnou podobu a fronent technilogii v administraci a jinou např. na e-shopu), ale netýká se to konkrétního modulu.

A proč neexistuje pro taková obecná views jeden adresář pro celou aplikaci? Tak v první řadě nic nebrání tomu, aby existovat mohl. I toto je nastavitelné a kde jsou obecné views se ve skutečnosti nastavuje v rámci inicializátoru - tedy je to plně pod vaší kontrolou. Pokud chcete, tak můžete mít třeba view pro generování formulářových polí na jednom místě. Je to úprava na pár minut.

Ovšem je velice pravděpodobné, že na webu, či e-shopu, budete chtít mít věci (zde konkrétně UI/UX) jinak než v administraci. A představte si, že někdo začne modifikovat zobrazení nějakého formulářového pole za účelem změnit něco v administraci a rozbije tím e-shop - nechtěně, ale dobré to není. 

Proto má již ukázková aplikace v sobě tyto obecné view několikrát. Jednou pro instalátor, pak pro Jet Studio, pak pro administraci a také pro web. Může se to zdát jako otrava, ale dá se to snadno změnit. Je to především prevence možných a zcela reálně hrozících problémů, ale není to dogma.

Použití bází v aplikaci

Bázi budete potřebovat v aplikaci použít často k řadě věcí. Například když budete potřebovat:

  • Seznam lokalizací webu (či čehokoliv)
  • Budete pracovat s URL
  • Když vyvíjíte např. e-shop tak na báze můžete mít navázané informace o měnách, zaokrouhlování, cenotvorbě atd.

Prostě je to základ od kterého se spousta věcí odvíjí. Dejme tomu, že potřebujete onen zmíněný seznam lokalizací webu. Nabízí se možnost udělat takto:

foreach( MVC:getBase('web')->getLocales() as $locale_str => $locale ) {
      
//... ... ...
}

Je to možné a je to funkční způsob, ale před tímto přístupem bych chtěl varovat!

Co když v budoucnu budete potřebovat zjistit kde, jak a proč se s danou bází pracuje?

Osvědčil se mi způsob, který je také v ukázkové aplikaci. Nepoužívám přímo metody MVC::getBase, ale v aplikačním prostoru si pro jednotlivé báze připravím třídy. Tedy například JetApplication\Application_Web. Tyto třídy slouží pro zapouzdření základního bodu aplikace. Najdete v nich inicializátor (velice důležité!), ale i metodu getBase(). Pojďme si pro názornost ukázat jednu konkrétní základní třídu:

namespace JetApplication;

use 
Jet\Logger;

use 
Jet\MVC;
use 
Jet\MVC_Router;

use 
Jet\Auth;

use 
Jet\SysConf_Jet_ErrorPages;
use 
Jet\SysConf_Jet_Form;
use 
Jet\SysConf_Jet_UI;

/**
 *
 */
class Application_Web
{
    public static function 
getBaseId(): string
    
{
        return 
'web';
    }

    public static function 
getBase(): MVC_Base
    
{
        return 
MVC::getBase( static::getBaseId() );
    }

    public static function 
initMVC_Router $router ): void
    
{
        
Logger::setLogger( new Logger_Web() );
        
Auth::setController( new Auth_Controller_Web() );

        
SysConf_Jet_UI::setViewsDir$router->getBase()->getViewsPath() . 'ui/' );
        
SysConf_Jet_Form::setViewsDir$router->getBase()->getViewsPath() . 'form/' );
        
SysConf_Jet_ErrorPages::setErrorPagesDir
            
$router->getBase()->getPagesDataPath$router->getLocale() ) 
        );
    }

}

Tedy vše podstatné mám na jednom místě.

Inicializátor báze, ale i metodu pro získání instance báze a tu dále používám. Vše je v aplikačním prostoru v pokud najdete další vhodné využití, tak tuto třídu (přesněji třídy) lze použít pro další obecné základní funkce aplikace.

Pojďme si ještě jednou získat seznam lokalizací, ale již správně a tak jak se to v reálu dělá:

foreach( Application_Web::getBase()->getLocales() as $locale_str => $locale ) {
    
//... ... ...
}

Nebo si můžeme ukázat příklad jak získat správnou URL homapage báze v její české mutaci:

Application_Web::getBase()->getHomepage( new Locale('cs_CZ') )->getURL()

(Později si generování URL ukážeme lépe.)

Tedy obecně lze doporučit vytvářet si pro báze ještě vlastní fasády, podobné třídám JetApplication\Application_Web, JetApplication\Application_Admin v ukázkové aplikaci. S dalším rozvojem aplikace se to může vyplatit.

Inicializátor

To je věc, kterou jsem již opakovaně okrajově zmínil a ke které je třeba se vrátit důkladněji.

Z předchozí kapitoly víte k čemu inicializátor je. Tedy že jej volá router po té co zjistí jaké báze se požadavek týká a účelem inicializátoru je donastavit systém.

Konkrétně slouží k nastavení závislostí těch komponent systému, které donastavení očekávají už ze své podstaty. Ano, toto je to čemu se říká dependency injection. A zde dochází k nastavení závislostí do kontejnerů a fasád.

(Pozor! Kontejner je zde čistě abstraktní pojem. Nemá to vůbec být žádný konfigurační soubor a podobně. Ne, je to abstraktní pojem ze světa OOP.).

Inicializátor byl již v předchozím příkladu, ale pojďme na něj kouknout ještě jednou:

public static function initMVC_Router $router ): void
{
    
Logger::setLogger( new Logger_Web() );
    
Auth::setController( new Auth_Controller_Web() );

    
SysConf_Jet_UI::setViewsDir$router->getBase()->getViewsPath() . 'ui/' );
    
SysConf_Jet_Form::setViewsDir$router->getBase()->getViewsPath() . 'form/' );
    
SysConf_Jet_ErrorPages::setErrorPagesDir
        
$router->getBase()->getPagesDataPath$router->getLocale() ) 
    );
}

Jak vidíte, tak inicializátor jako parametr dostává instanci routeru, který právě provádí vyhodnocení.

To je praktické, protože router už identifikoval bázi a lokalizaci a je možné tyto informace použít.

Dále vidíte ono zmíněné nastavení závislostí. To sice nesouvisí přímo s MVC a bází, ale je nutné si to nyní, alespoň stručně, objasnit.

Zvolme jako příklad Logger. Ten slouží k logování událostí a operací. Například když chcete zalogovat úspěšně provedenou operaci, tak použijete volání Logger::success( ... ). Logger se používá (a má používat) často. Jeho použití a volání tedy musí být maximálně snadné - až primitivní.

Ovšem Logger potřebuje nějaký backend. Něco, co informace fakticky zapíše do nějakého logu - ať je to cokoliv (databáze, soubory, ...) a kdekoliv.

A řekl bych, že nebudete chtít "motat" dohromady záznamy logů administrace a logů platební brány vašeho e-shopu. Dejme tomu že logy budete pro snadné zpracování ukládat do databáze, ale minimálně každý log do jiné - samostatné tabulky (jsou možné i úplně odlišné technologie ukládání informací, ale nekomplikujme to). Je tedy jasné, že pro každou bázi budeme potřebovat jiný a jinak nastavený logger.

A právě zde v inicializátoru a právě takto ten backend Loggeru předáte a nastavíte: Logger::setLogger( new Logger_Web() );

Již nikde jinde v aplikaci inicializaci loggeru a jeho závislosti neřešíte. Pokud pak chcete globálně vyměnit backend za lepší, tak to jednoduše provedete na tomto jednom místě.

Jak vidíte na příkladu, tak tento princip se samozřejmě netýká pouze Loggeru, ale je to obecně platný princip platformy Jet. Logger zde posloužil jako názorný příklad.

A ještě jednou se vraťme zpět. Tentokrát k souboru base_data.php. Bude nás zajímat tento kousek definice:

'initializer' => [
    
'JetApplication\\Application_Web',
    
'init',
],

Takto se inicializátor do definice zapisuje. Klasickou PHP cestou ['třída', 'metoda']. Všimněte si prosím, že je tam uveden plný název třídy jako řetězec. Nabízí se možnost použít hezčí cestu zápisu: Application_Web::class. Ano, tak je to všude jinde používáno. Ale zde je to z technických důvodů nutné ponechat zapsané takto staromódně.

Předchozí kapitola
Jet MVC
Další kapitola
Jet\MVC_Base