Hlavní navigace

Jet: Aplikační moduly jako služby

24. 11. 2023 15:18 Mirek Marek

Úvod

Nejprve se chci omluvit za svou delší „absenci“. PHP Jet pochopitelně nezmizel (a já taky ne), ale pouze jsem aktivity nasměroval primárně na zahraničí, nebo lépe řečeno na globální úroveň (i když to v této počáteční fázi zní hodně nadsazeně, možná až komicky 😁).

Můj kamarád a rádce Honza (zdravím Tě! 😁) má pravdu – je lepší soustředit se primárně na celý svět. Ta naše země je přenádherná, miluji ji, je to ráj. Ale je prostě malá …

I tak ale nechci naší domovinu ignorovat a i zde budu pokračovat ve svých aktivitách. A dnes bych vás rád obeznámil s tím, jak lze aplikační moduly využívat jako služby v online aplikacích postavených s pomocí PHP Jet.

A na závěr bude malé „překvapení“. No překvapení … Ukážeme si jak velice snadno udělat z Jet framework, který služby umí předávat jako parametry akcím kontrolerů (jak už jste „někde“ mohli vidět).

Tak hurá na to 🙂

Co je to aplikační modul?

Pokud ještě Jet neznáte, tak vězte, že aplikační moduly jsou základem filozofie frameworku PHP Jet. Tato filozofie říká, že online aplikace není dobré vyvíjet jako monolit. Aplikaci je vhodné (u jen trochu větším reálných projektů vlastně nutné) rozdělit na menší celky, komponenty, mikroaplikace, služby, … a je teď vlastně jedno jak se to všechno dá pojmenovat.

Důležité je, že projekt je tvořen jednotlivými stavebními prvky, které je možné vyměňovat, nahrazovat, samostatně vyvíjet a testovat a také nasazovat, distribuovat mezi projekty, přidělovat ke správě různým týmům / vývojářům a tak dále.

Kdybych to přirovnal ke strojařině (strojařinu mám v rodině  – proto s tím „otravuji“), tak je to vlastně jako auta a hlavně to co dělají moderní velké automobilové koncerny: vyvíjejí komponenty, které sdílí a staví na nich různé produkty – různá auta tvořící celek „poskládaný“ z komponent a doladěný do požadované podoby.

Je to úplně stejná filozofie, pouze přenesená do světa SW a je to vlastně to o čem se dnes hodně mluví (komponenty, znovupoužitelnost a tak dále).

A co je důležité, tak tyto komponenty kterým se v PHP Jet se jim říká aplikační moduly spolu mohou přímo spolupracovat. Jeden modul může využívat jiný modul a to buď jako celou komplexní komponentu (příklad: může existovat správce obrázků a obrázkové galerie, který nabízí ostatním modulům widget a dialog pro výběr obrázků – právě to si ukážeme v článku), nebo systémové služby (autentizační a autorizační kontroler, logger a tak dále).

Aplikační moduly a jejich důkladné pojetí jsou vlastně jedna z hlavních výhod Jetu (jak si sám ověřuji praxí), kterou chci stále víc akceptovat a celý koncept dále rozvíjet. A s důrazem na to je i postupně upravována a vylepšována ukázková / vzorová aplikace.

V posledních verzích Jet je vzorová aplikace (se kterou je Jet distribuován) upravena tak, aby co nejvíce demonstrovala možnosti kooperace modulů a jejich využití. A o tom bude dnešní článek.

Podoba aplikačního modulu

Pro ty kteří si s PHP Jet ještě nikdy nehráli bych rád ukázal co to vlastně fakticky ten aplikační modul je. Vy co už to znáte můžete tuto část přeskočit.

Maximální podoba aplikačního modulu je vlastně aplikace sama o sobě. Například ukázková správa obrázků / obrázkové galerie je toto: application/Modules/Ad­min/Content/Images na Git Hub

Má to své kontrolery (a v nich pochopitelně akce), své view, své vlastní třídy. (Pouze datový model obrázkové galerie je využit globální, ale teď se do toho nezamotejme – o tom třeba jindy) Vše je v jednom adresáři.

Zajímavé a důležité je, že aplikační modul má svůj manifest, který slouží k integraci modulu do celého projektu a taktéž jednu hlavní třídu Main, kterou modul musí mít vždy.

Moduly lze instalovat a odinstalovat (moduly mohou mít své instalační skripty), také aktivovat a deaktivovat. K tomu slouží Jet Studio, CLI nástroj, nebo API Jetu.

A konkrétně výše uvedený aplikační modul Admin.Content.Images bude dále sloužit i ostatním modulům, které jej mohou využít jak si ukážeme dále.

Ovšem modul nutně nemusí být takto relativně komplexní aplikace s UI. Modul může mít i velice jednoduchou podobu jako například tento logger, tedy služba určená k logování událostí: application/Modules/Admin/Logger na Git Hub

Jak vidíte, tak tam již nejsou žádné kontrolery a ani view. V tomto modulu najdete pouze jeho hlavní třídu (Main – jak již bylo uvedeno, ta je mandatorní), manifest (druhá a poslední povinná část modulu) a v tomto případě i datový model události (zde s využitím ORM) a instalační skript (který při instalaci zajistí vytvoření databázové tabulky).

Za pozornost stojí i fakt, že třída Main modulu, který bude sloužit jako služba logování, implementuje rozhraní JetApplication\Application_Ad­min_Services_Logger.

V případě vzorového modulu pro správu obrázků je to pak rozhraní služby JetApplication\Application_Ad­min_Services_ImageManager. To si však rozebereme detailně později.

Využití modulů jako služeb

Než se dostanu k jádru věci, tak s dovolením ještě jednou odbočím. Pokud vás to opravdu zajímá a pokud jsme ještě nečetli, tak doporučuji přečíst starší článek o Dependency Injection v PHP Jet a jeho konceptu garantů služeb.

Ano, i při využití modulů jako služeb bude tento koncept dále použit (i když si na konci článku ukážeme jak i toto lze v Jet snadno řešit i jinak).

Pokud jste již obeznámeni s konceptem garata služeb, tak víte že samotného poskytovatele služeb je nutné injektovat do garanta, který slouží jako specifický kontejner pro danou službu a umožňuje tak jednoduché až triviální a maximálně přímočaré použití služby.

Ve starších verzích vzorové aplikace se injektování služby do garanta / kontejneru řešilo v například inicializátoru báze (ten kód, který donastaví aplikaci podle toho „kam uživatel leze“ – je něco jiného administrace, jinak se chová API, jinak veřejný web, jinak servisní služby, jinak exporty a tak dále) takto:

namespace JetApplication;

use Jet\Logger;

class Application_Admin
{
    public static function init( MVC_Router $router ): void
    {
        Logger::setLogger( new Logger_Admin() );
    }
}

To funguje, proč ne. Ale dnes si ukážeme jak to dělají nové verze vzorové aplikace (a jak to dělá i chystaná e-shop platforma Jet Shop, kde je následující způsob vhodnější řešení). Tam již najdete toto:

namespace JetApplication;

use Jet\Logger;

class Application_Admin
{
    public static function init( MVC_Router $router ): void
    {
        if(($logger=Application_Admin_Services::Logger())) {
            Logger::setLogger( $logger );
        }
    }
}

Tedy je vidět že v aplikačním prostoru (nikoliv frameworku samotném) existuje nějaká třída Application_Admin_Services, která zastřešuje služby použité v této části aplikace (zde konkrétně v administračním rozhraní).

Ano, tato třída není součástí frameworku. Existuje tendence dělat ze služeb něco démonického, něco co potřebuje nějakou mohutnou, komplexní a „inpozatní“ infrastrukturu. Možná i woodoo rituály. Opak je pravdou. V jednoduchosti je síla a zrovna službu jsou něco, co je ve své podstatě jednoduché (ne, žádná magie v tom fakt není a ani nemá být) a na druhé straně velice důležité. O službách potřebuji mít přehled a potřebuji aby jejich použití bylo maximálně jednoduché – tedy aby ve výsledku nikdo nemohl například tvrdit že neví jak zalogovat událost a proto jeho „kód neloguje“, ale abych zároveň služby mohl měnit dle potřeby bez úprav více jak jednoho (ideální stav) místa v projektu.

Tedy je to na jednu stranu velice jednoduché a na druhou stranu fajn udělat si pro projekt infrastrukturu služeb, která přesně vyhovuje daným potřebám a dané situaci. Tak to dělá i vzorová aplikace.

Ona třída zastřešující službu použité v dané části projektu (zde v administraci) má tuto podobu:

namespace JetApplication;

use Jet\Application_Modules;
use Jet\Exception;

class Application_Admin_Services
{
    public static function ImageManager() : ?Application_Admin_Services_ImageManager
    {
        return static::findService( Application_Admin_Services_ImageManager::class );
    }

    public static function AuthController() : Application_Admin_Services_Auth_Controller
    {
        return static::findService( Application_Admin_Services_Auth_Controller::class, true );
    }

    public static function AuthLoginModule() : Application_Admin_Services_Auth_LoginModule
    {
        return static::findService( Application_Admin_Services_Auth_LoginModule::class, true );
    }

    public static function Logger() : ?Application_Admin_Services_Logger
    {
        return static::findService( Application_Admin_Services_Logger::class );
    }

    public static function findService( string $service_interface, bool $service_is_mandatory=false ) : mixed
    {
        $modules = Application_Modules::activatedModulesList();
        foreach($modules as $manifest) {
            if(!str_starts_with($manifest->getName(), 'Admin.') ) {
                continue;
            }

            $module = Application_Modules::moduleInstance( $manifest->getName() );

            if($module instanceof $service_interface) {
                return $module;
            }
        }

        if($service_is_mandatory) {
            throw new Exception('Mandatory service '.$service_interface.' is not available');
        }

        return null;
    }
}

Ano, absolutně triviální.

PHP Jet nabízí infrastrukturu aplikačních modulů, která je univerzální. Je tedy jednoduché projít si relevantní aktivní moduly a najít ty jejichž hlavní třída implementuje určité rozhraní / abstraktní třídu definující danou službu.

A díky tomu je možné mít nad službami perfektní kontrolu. Například logování je možné vypnout pouhou deaktivací určitého modulu (např. v Jet Studiu, ale i přes API Jetu). Nebo mohu jeden modul nahradit jiným (odlišně fungujícím) a to třeba i dočasně pro účely testování.

Například je možné vyvinout nový modul a s ním si hrát. Dají se s tím dělat takové kousky že nainstaluji nový modul pro určitou službu, ten starý deaktivuji, nový aktivuji a vyzkouším funkčnosti, ale třeba i rekce uživatelů. A případě potřeby se dá vše vrátit zpět.

To vše umožňuje PHP Jet, na to vše má připravenou infrastrukturu. A je na vývojáři jak to použije a jakou si nad to doplní vlastní lehkou zastřešující infrastrukturu, kterou přesně přizpůsobí jeho projektu (a ve skutečnosti mu to zabere pár minut a bude to dělat přesně to co daný kolega potřebuje).

Dejme tomu, že služby mohou být v nějakém „konfiguráku“, kdyby to někdo z nějakého důvodu potřeboval. A ta konfigurace může mít „klikací“ nástroj v administraci. Proč by ne, PHP Jet nabízí i podporu pro práci s konfigurací a pro vytváření konfiguračních „klikátek“. Příkladem budiž například vzorový instalátor. Ale to už moc odbočuji a k tomu se vrátím na konci článku.

Prostě a jednoduše: PHP Jet dodržuje důležitý princip a to ten že nad službami má kontrolu vyšší autorita a v žádném případě ne konzumenty služeb – to je totiž hlavním smyslem a přínosem celé koncepce. Jak bude tato vyšší autorita implementována a na základě čeho se bude řídit? To už je věcí vývojáře a hlavně situace v rámci konkrétního projektu, konkrétní aplikace.

Mě se stále více osvědčuje používat pro tento účel princip aplikačních modulů. Tedy jede z nástrojů, které Jet nabízí. Ale díky filozofii garantů služeb toto lze implementovat jakkoliv jinak a nic tomu nebrání.

A co takhle něco zajímavějšího?

Logger je „pouhá“ systémová služba, stejně tak třeba Auth Controller – ač to je služba zajímavější, tak stále je to cosi „neviditelného“. Tedy věci jsou to důležité, ale pro uživatele neviditelné a mohlo by to na někoho působit nudně.

Ukažme si tedy něco zajímavějšího a vizuálního.

Situace: Vyvíjíte nějaké CMS, redakční systém či e-shop. A budete potřebovat nějakou správu obrázků. U e-shopu je to ještě markantnější, protože entit které potřebují pracovat s obrázky je překvapivě hodně (kategorizace, produkty, nálepky a příznaky, filtry a parametrizace, druhy dopravy a platby, infostránky a tak dále a tak dále).

A klíčový „princip jedné odpovědnosti“ se netýká pouze tříd a metod, je to obecně platný princip, který se samozřejmě může týkat (a v praxi týká) i aplikačních modulů.

Tedy: Je nevhodné aby si v systému kde jsou desítky entit (v našem případě aplikačních modulů) řešila každá taková entita záležitost, která ji nepřísluší sama a to navíc stále dokola a opakovaně. Prostě je dobré mít jednu správu obrázků a ne 40 správ obrázků.

Je jasné co v takovém případě nedodržení principu jedné odpovědnosti znamená: spousta (zbytečné) práce, spousta (vesměs duplicitního) kódu navíc, nemožnost snadno globálně ovlivnit funkcionalitu jedním zásahem.

Představte si, že si zákazník pro kterého projekt děláme vymyslí, že chce například správu obrázků napojit na nějakou službu obrázkové galerie třetí strany (lepší případ mě teď nenapadl … 🙂 ). Ve špatně navrženém systému vás čeká takové „menší peklíčko“ a řehole.

Ve správně navržené aplikaci stačí upravit jednu komponentu a to je přesně smysl a samotná podstata Jetu a jeho aplikačních modulů. Právě s tím Jet hodně pomáhá a v posledních verzích ukázkové / vzorové aplikace je to naznačeno a lépe předvedeno.

Pojďme si to konečně ukázat konkrétně. Ve vzorové aplikaci je náznak správy článků.

Článek má nějaký titulní obrázek. Pro účel demonstrace spolupráce modulů je výběr onoho obrázku řešen jako výběr obrázku z galerie – tedy jiného aplikačního modulu.

(Ano, dalo by se to řešit i jinak – obecným správcem obrázků bez galerie, který má Jet Shop, ale to teď nechme stranou – zde jde primárně o demonstraci možností a postupů, ne o hodnocení konkrétní implementace).

Z uživatelského hlediska to má tuto podobu:

Ukažme si rovnou fragmenty view edit.phtml aplikačního modulu Admin.Content.Articles aby bylo patrné jak je aplikační modul zabezpečující službu správy obrázků integrován:

namespace JetApplicationModule\Admin\Content\Articles;
    ...
    $image_manager = Application_Admin_Services::ImageManager();


    echo $image_manager?->includeSelectImageDialog();

    if($image_manager):
        ...
        echo $image_manager->renderSelectImageWidget( $image_field );
        ...
    endif;

(Poznámka: upraveno pro účely vložení do článku.)

A výsledek je vložení widgetu pro výběr obrázku, ale také dialogového okna, ve kterém je vlastně plnohodnotná správa obrázkové galerie:

Pro modul Admin.Content.Articles je vlastně zcela irelevantní jaký modul se o správu obrázků stará, jak funguje a jaké má UI, co vlastně umí. Modul používající službu dokonce počítá s tím, že správce obrázků nemusí existovat – například může být deaktivován, či znepřístupněn pomocí uživatelských oprávnění (i to lze velice snadno implementovat). Pak uživatel uvidí toto, možnost volby obrázku prostě „zmizí“, ale vše funguje dál:

A především konzumentem služby správce obrázků nemusí být pouze modul Admin.Content.Articles, ale jakýkoliv jiný modul, který výběr obrázku potřebuje. Konzumentů služby může být samozřejmě třeba sto a nemusí to být nutně další aplikační moduly. 

Uživatelský modul, nebo libovolný jiný konzument služby musí znát pouze dvě věci: Kde je garant služby (v tomto případě metoda Application_Admin_Services::I­mageManager();) a jaké má služba rozhraní. To určuje interface JetApplication\Application_Ad­min_Services_ImageManager:

namespace JetApplication;

use Jet\Form_Field;

interface Application_Admin_Services_ImageManager
{
    public function includeSelectImageDialog() : string;
    public function renderSelectImageWidget( Form_Field $form_field ) : string;
}

Jak vidíte, je to zcela primitivní. Uživatelský aplikační modul pouze musí vložit vyrenderování dialogu a také widgetu samotného.

Při renderování widgetu je nutné předat instanci definice formulářového pole ve kterém má být hodnota určující výběr obrázku (v tomto případě ID obrázku, ale může to být samozřejmě cokoliv jiného).

Jak pak funguje aplikační modul Admin.Content.Images, tedy poskytovatel služby?

Opět nic složitého. Jak bylo řečeno, tak každý aplikační modul musí mít krom manifestu povinnou třídu Main a to právě z důvodu aby byla zajištěna interakce modulů mezi sebou a také interakce např. s Jet MVC.

V tomto případě má třída Main ve vztahu k poskytování služby tuto podobu:

namespace JetApplicationModule\Admin\Content\Images;

use Jet\Application_Module;
use Jet\Factory_MVC;
use Jet\Form_Field;
use Jet\Translator;
use JetApplication\Application_Admin_Services_ImageManager;

class Main extends Application_Module implements Application_Admin_Services_ImageManager
{

    public function includeSelectImageDialog(): string
    {
        $view = Factory_MVC::getViewInstance( $this->getViewsDir() );

        $res = '';
        Translator::setCurrentDictionaryTemporary(
            $this->module_manifest->getName(),
            function() use ($view, &$res) {
                $res = $view->render('dialog/select-image/hook');
            }
        );
        return $res;
    }

    public function renderSelectImageWidget( Form_Field $form_field ) : string
    {
        $view = Factory_MVC::getViewInstance( $this->getViewsDir() );
        $view->setVar('form_field', $form_field);

        $res = '';
        Translator::setCurrentDictionaryTemporary(
            $this->module_manifest->getName(),
            function() use ($view, &$res) {
                $res = $view->render('widget/select-image');
            }
        );

        return $res;
    }
}

Neděje se vlastně vůbec nic složitého. Pouze:

  • Modul si dočasně nastaví překladový slovník „sám na sebe“
  • Pomocí vlastních instancí view vygeneruje příslušné elementy (jejichž view jsou pro pořádek pochopitelně v samostatných adresářích).

Dobře, ale modul musí ještě nějak zobrazit ten dialog, který je vlastně plnohodnotnou aplikací sám o sobě.

Je to opět primitivní. Modul si sebou nese definici MVC stránky, která představuje dialog následně umístěný do modálního okna.

V definici stránky „select-image“ aplikačního modulu pak najdete toto (teda pokud se tam chcete hrabat ručně a ne přes Jet Studio):

 'dialog-select-image',
    'name' => 'Dialog - Select image',
    'layout_script_name' => 'dialog',
    'contents' => [
        [
            'module_name' => 'Admin.Content.Images',
            'controller_name' => 'DialogSelectImage',
            'controller_action' => 'default',
            'is_cacheable' => false,
            'output_position' => '__main__',
            'output_position_order' => 1,
        ],
    ],
];

Za pozornost stojí zejména toto: ‚controller_name‘ ⇒ ‚DialogSelectImage‘,

Tedy je jasné, že dialog bude mít svůj vlastní kontroler a nebude spoléhat na běžný standardní kontroler Main.

No jo, ale to co je v dialogu je vlastně téměř to samé co nabízí standardní správa obrázků, pouze je rozdíl v nutnosti zobrazit tlačítko pro výběr obrázku.

Žádný problém. Máme přece dědičnost. V kontroleru DialogSelectImage tedy najdete pouze toto:

namespace JetApplicationModule\Admin\Content\Images;

class Controller_DialogSelectImage extends Controller_Main
{
    public function _initGalleries(): string
    {
        $this->view->setVar('select_image_mode', true);

        return parent::_initGalleries();
    }
}

Tedy celé je to doslova pár desítek řádek kódu. Dobře, pochopitelně k tomu jsou ještě úpravy ve view a malinko JavaScriptu (opravdu jen malinko). Ale i tak v tome nelze hledat nic složitého, žádnou magii.
Budu se opakovat: Je to vlastně strašně jednoduché.

A dělá to přesně to co interakce služeb a konzumentů služeb v systému má dělat:

  • Konzument služby se nestará o to co je poskytovatelem služby a jak se inicializuje a proč. (to je ten úplně nejdůležitější aspekt a smysl všeho)
  • Službu je možné snadno použít kdekoliv a jednoduše.
  • Pomocí moderního IDE je otázka sekundy zjistit kde se služba používá.
  • Službu je možné snadno vyměnit za jinou – třeba i zcela odlišně fungující, pouze se stejným rozhraním pro interakci s konzumenty služeb.
  • V tomto případě je možné službu dokonce i deaktivovat či jinak znepřístupnit na základě různých okolností.

Žádná věda. Pouze systém aplikačních modulů tomu jde naproti a podle této filozofie je navržen celý Jet.

Závěr k aplikačním modulům jako službám

Jak vidíte, tak aplikační moduly neslouží pouze k „rozkouskování“ projektu do samotných celků – což má své obří výhody a opakuji, že u jen trochu větších projektů je to nutnost.

Moduly mezi sebou mohou interagovat a v praxi se tak děje. Někdy příště si třeba ukážeme právě reálné příklady z praxe.

Důležité je, že Jet jako framework poskytuje to nejdůležitější – infrastrukturu pro aplikační moduly, kterou si mimochodem můžete modifikovat, protože správce aplikačních modulů ve frameworku je také vyměnitelná služba ( je někdo překvapen? 😄 ), jako takřka vše v Jetu. Ale zpět k tématu.

Ovšem framework neříká jak musí vypadat poslední vrstva infrastruktury pro služby v aplikaci a záměrně to nechává na vývojáři a pouze vzorová aplikace ukazuje či naznačuje jak je možné to dělat.

Ale předpokládá se, že vývojář má vlastní mozek a přemýšlí a ví nejlépe jak si takovou věc udělat tak, aby plně vyhovovala potřebám daného projektu a situaci. Však co služba, to unikáte. Jedna služba nemá žádné stavy, další služba z principu musí být globální singleton, další služba naopak nesmí být singleton a tak dále. Jedna služba je stejná pro celý projekt, další služba (či její natavení) je odlišná pro různé části projektu (jiná v administraci, jiná v API, …). To vše musí posoudit vývojář a ne framework. A jsou to velice důležité faktory dané praxí. Framework je prostě „pouze“ brašna s nářadím, které má pomoci – pomoci prakticky v reálném světě.

Zde jsem vám nabídl možnost jak pro služby využít systém aplikačních modulů. A je to něco, co se mi osvědčuje v praxi víc a víc.

Třeba to pomůže i vám 😉 Každopádně myslet „modulárně“ se opravdu vyplatí – ať už jde o služby nebo ne.

Bonus: Ale já to chci složité! 🤩

Fajn, slíbil jsem že si ukážeme jak „překopat“ chování kontrolerů v Jet.

Nechme si logování jako jednoduchý a jasný příklad. Díky principu garantů služeb je použití služby jako je logování triviální.

Tohle je například z ukázkového modulu pro správu článku zalogování přidání článku a standardní použití služby:

public function add_Action(): void
{
    $article = new Content_Article();

    $form = $article->getAddForm();

    if( $article->catchAddForm() ) {
        $article->save();

        Logger::success(
            event:               'article_created',
            event_message:       'Article created',
            context_object_id:   $article->getId(),
            context_object_name: $article->getTitle(),
            context_object_data: $article
        );
    }
}

Ale dejme tomu, že pro svůj klid v duši (nebo z nějakého jiného důvodu) chcete, aby akce kontroleru dostávala instance poskytovatelů služeb jako parametry, tedy aby to fungovalo třeba takto:

public function add_Action( Application_Admin_Services_Logger $logger ): void
{
    $article = new Content_Article();

    $form = $article->getAddForm();

    if( $article->catchAddForm() ) {
        $article->save();

        $logger->log(
            event_class:         Logger::EVENT_CLASS_SUCCESS,
            event:               'article_created',
            event_message:       'Article created',
            context_object_id:   $article->getId(),
            context_object_name: $article->getTitle(),
            context_object_data: $article
        );
    }
}

Ponechám stranou, že je to úplně to samé a že ve finále se dostanu k tomu samému poskytovateli služeb. Budu ignorovat, že jsou to pouze „písmenka navíc“ (a také potenciální problémy – třeba někdo zná z praxe). A ukážeme si jak to udělat.

Je to totiž naprosto primitivní (já už s tím slovem primitivní musím být hodně otravnej 😁 ). Stačí si udělat vlastní třídu kontroleru a přetížit metodu dispatch, která se stará o volání akci kontroleru a tuto svou třídu kontroleru použít jako rodiče pro kontrolery:

namespace JetApplication;

use Jet\Exception;
use Jet\MVC_Controller_Default;
use ReflectionMethod;

abstract class MyCrazyController extends MVC_Controller_Default
{
    public function dispatch(): void
    {
        $method = $this->content->getControllerAction() . '_Action';

        if( !method_exists( $this, $method ) ) {
            throw new Exception(
               'Controller method ' . get_class( $this ) . '::' . $method . '() does not exist'
            );
        }

        $params_reflection = (new ReflectionMethod( $this, $method ))->getParameters();
        $params = [];

        foreach( $params_reflection as $param_reflection ) {
            $ifc = $param_reflection->getType()->getName();
            $service = Application_Admin_Services::findService( $ifc );

            $params[] = $service;
        }

        call_user_func_array( [$this, $method], $params );
    }
}

Udělat něco takového v PHP ❤ prostě není žádná magie, ale trivialita.

A opravdu by se dalo pokračovat dál a udělat z toho třeba parou poháněnou vzducholoď. Klidně číst definici služeb z nějakého XML. Pokud víte proč to dělat a jaké reálné (!!!) benefity to přinese, tak proč ne. Nic vám v tom nebrání. 

Mě téměř 23 let praxe naučilo hledat jednoduchost a problémy řešit tak, že se jim prostě a jednoduše vyhnu – nejlepší problém je žádný problém. Ale i tak jsem rozhodně neviděl všechny situace a neznám všechny druhy projektů. „Vím jen, že nic nevím.“ – Sókratés

 Tedy je možné, že někde je tento přístup vhodný, ne-li nutný. Podstatné je, že Jet s tím počítá. PHP Jet má mechanismy jak lze ovlivnit co je třeba – zde pomocí dědičnosti, jinde pomocí modulů, továren, garantů a tak dále. PHP Jet počítá s tím, že jeho hlavní autor neví vše a že si vývojář určitě bude potřebovat něco přizpůsobit či změnit a musí mít možnost jak to udělat. PHP Jet vám nabízí možnosti a nevnucuje žádná „svatá pravidla č. 1“, která mohou být v některých situacích zcela správná a v jiných situacích naprosto kontraproduktivní a mimo. Je hříchem kohokoliv jakékoliv dogmata učit. Dogmata do techniky nepatří. Naopak znalost podstaty věcí a samostatné myšlení považuji za cestu správnou.

PHP Jet má být nejen o modularitě a podobných věcech, ale i svobodě (v rámci racionálních mantinelů a pravidel, které jsou integrální součástí každé svobody) a také o úctě k vývojáři jako ke svéprávné a kompetentní osobě na které je konečná odpovědnost za projekt a tedy i odpovědnost za konečná rozhodnutí.

A to je pro dnešek už vše. Zase se ozvu 😉

Mějte se krásně!

Sdílet