ORM v PHP Jet

2. 6. 2023 6:49 Mirek Marek

Dnešním článek začíná malá série ve které představím ORM, které je integrované ve framworku Jet.

Ještě než se pustím do ORM, tak se musím k něčemu přiznat. Vím, že to tu čtou lidé co mě znají osobně a někdy i velice dlouho. Před mnoha lety jsem byl alergický jen na slovo ORM 😀 Nechtě jsem o tom ani slyšet … 

Proč? Protože práce s databází je nejužší hrdlo každé online aplikace. Webserver a aplikace se dá snadno škálovat vertikálně a hlavně i horizontálně. Nic extra složitého. Ale jak je problém s databází, myšleno když aplikace s databází špatně operuje a není dobře optimalizovaná, tak nepomůže žádné škálování a nepomůže ani svěcená voda, ba ani povolaný exorcista. Teda pokud ten exorcista náhodou není profík na online aplikace a vše co s tím souvisí – tedy i na relační databáze a SQL. A už jsem viděl mnoho aplikací, kde bylo pro vygenerování stránky potřeba stovky dotazů … A už jsem viděl rádu kolegů, kteří to vůbec neřešili. Měli ústa plná jiných „magických pouček“, ale že se jim server potí při každém obyčejném požadavku na obyčejnou stránku jim bylo jedno.

Vždy jsem zastával názor, že dobrý vývojář má relačním databázím a SQL rozumět a to co nejlépe jak je to možné. A tento názor pevně zastávám stále. To že se někdo učí pouze používat nějakou abstrakci nad databází aniž by opravdu věděl co a proč dělá a co se ve skutečnosti děje, jaké dotazy jsou spuštěné a jak jsou/nejsou optimalizované, takový přístup považuji za špatnou cestu, která se na velkém projektu zcela určitě vymstí.

A ORM jsem považoval za něco, co tento přístup (učit se „jak“ a ne se učit chápat „proč?“) podporuje. Pletl jsem se, viděl jsem to moc černobíle a neviděl zjevné výhody.

Tedy ano, nadále tvrdím, že dobrý vývojář má mít co nejlepší znalosti v oblasti databází, SQL a vše co s tím souvisí. Dobrý vývojář má umět vyhledávat problematické dotazy a má umět tyto analyzovat a řešit. Dobrý vývojář má umět přivřít oči a aplikaci si „pustit v hlavě“ – prostě u práce přemýšlet, protože právě to a ne datlování kódu je podstata naší práce. Dobrý vývojář má vědět a hlavně chápat (to především) a ne pouze něco papouškovat. Na tomto mém postoji se nic nezměnilo.

Ovšem k problematice samotné existence ORM jsem názor změnil. Jak jsem již napsal, prostě jsem se mýlil a ve finále jedno ORM sám vytvořil.

Proč? V první řadě nerad dělám dokola stále totéž. To byl jeden ze dvou hlavních důvodů. Jednoho dne už mě to prostře přestalo bavit bavit dělat po staru, ale zároveň jsem různá řešení na půli cesty neshledal jako dostatečná.

Tím se dostáváme k druhému důvodu. Ten druhý důvod byl fakt, že jsem v praxi opakovaně narazil na nutnost tutéž aplikaci provozovat na různých typech relační databáze bez nutnosti celou aplikace / projekt / produkt revidovat a “překopávat”.

Chtěl jsem ORM, které mi pomůže s otravnou a stále se opakující rutinou, ale zároveň mi ponechá kontrolu nad tím co se děje a bude co nejpřímočařejší, pokud možno jednoduché a malé. (No i když … Je to největší část PHP Jetu – má to 1,1MB )

A tak v PHP Jet vzniklo ORM zvané DataModel a později i příslušný nástroj, ze kterého se stalo Jet Studio, které již znáte z videí. A právě ORM DataModel dnes začnu představovat. 

Tak trochu NoSQL (opravdu jen trochu) 

Už ani nevím kolik je to let, co se velice intenzivně mluvilo a psalo o NoSQL databázích. Internet byl plný CautchDB, MongoDB a tak dále. Pomalu by se zdálo, že relačním databázím je konec (s nadsázkou). Pochopitelně to tenkrát byl jen další z mnoha „letních IT hitů“ a vše postupně vrátilo do starých dobrých evolučních kolejí. Relační databáze jsou prostě relační databáze. Ale myšlenka dokumentových databází není vůbec špatná. Naopak. Samotné NoSQL databáze mají své místo a využití. Ale jak říkám: na matku příslušný klíč a ne kombinačky. Ale mě nejvíc zaujal ten pohled na data jako na dokument.

V aplikacích je naprosto normální, že entita se skládá z řady tříd, které jsou hierarchicky uspořádané a provázané. Z pohledu relační databáze je to tedy vlastně několik databázových tabulek. Ale na celou takto poskládanou entitu se dá nahlížet jako na jeden celek – tedy jako na onen dokument v přeneseném smyslu slova.

Jako jednoduchý příklad použijme entitu Article – článek/text z ukázkové aplikace PHP Jet. Tato část malé ukázky praktického použití Jet rovnou počítá s tím, že obsah webu (či jiného druhu online aplikace) je vícejazyčný.

A například v administraci prostě potřebuji “natáhnout” celý článek a pracovat s tím jako by to byl jeden strukturovaný dokument a neřešit nějaké tabulky. Třeba takto:

$article = Content_Article::get( $id );
if($article) {
    $article->delete();
}

To vše bez ohledu na to zda je daný článek v jedné nebo deseti databázových tabulkách.

A toto je jednoduchý prostý článek, pouze vícejazyčný. Třeba taková definice produktu v e-shopu se reálně skládá například z deseti tabulek.

Z těch tabulek je je nutné data skládat do instancí tříd, nebo naopak instance třídy ukládat do tabulek s tím, že bude plně automatizováno propojení dat (provázanost ID záznamů).

Potřebuji něco, co za mě bude řešit otravnou a stále se opakující rutinu při čtení, ukládání či mazání dat.

Potřebuji aby například načítání bylo velice rychlé. To v praxi znamená, že počet dotazů se bude vždy rovnat počtu tříd ze kterých je entita poskládána a nebude odvislý od počtu záznamů. Prostě potřebuji pohodlnou práci jako by to byl jeden dokument. To je pro řadu situací moc fajn.

Ovšem potřebuji řešit i situace, kdy si z databáze potřebuji vytáhnout pouze část dat. Rovněž potřebuji řešit situace, kdy si potřebuji vytáhnout například velké množství dat v surové podobě (bez převodu na instance tříd). V praxi rovněž potřebuji řešit i úpravy velkého množství záznamů opět na úrovni surových dat. Schválně zkuste dělat přepočty cen (například) stovek tisíc záznamů s tím, že budu tahat celé instance zboží (například) … V praxi je prostě nutné volit správný přístup na řešení daného problému – to je univerzální pravidlo.

Potřebuji tedy něco, co mi umožní dělat vlastně cokoliv ať je to práce s entitou jako s (pomyslným) dokumentem, či práce s daty a při tom všem mě to zbaví rutinní práce a odstíní mě to od konkrétního typu relační databáze tak aby aplikace byla přenositelná (Jet nyní podporuje MariaDB/MySQL, SQLite, PostgreSQL a MS SQL, podpora Oracle bude také).

Potřebuji, aby to něco bylo rychlé a celkově efektivní a zároveň ne svazující. A tak jsem postupně stvořil (a v praxi již odzkoušel) to co teď konečně začnu popisovat. Hurá na to.

Definice datového modelu

Ve videích a ukázkách jste již mnohokrát měli možnost vidět jak to funguje, jak se definice entity “naklikává” v k tomu určeném nástroji Jet Studio a tak dále. Celkově jsem toto předváděl několikrát na praktických ukázkách. Tedy teď se zaměřím hlavně na speciality a věci o kterých jsem se dříve zmínil pouze okrajově či vůbec.

Definice třídy jako entity

Každá třída reprezentující entitu musí dědit od třídy Jet\DataModel.

Každá třída reprezentující subentitu – dílčí část entity – musí dědit od třídy Jet\DataModel_Related_1to1, nebo Jet\DataModel_Related_1toN. Podle toho v jaké relaci je subentita vůči hlavní entitě či nadřazené subentitě.

To jsou základní podmínky definice entit.

Dále každá třída reprezentující entity či její část musí mít definované atributy. Používá se výhradně systém atributů z PHP 8, které si Jet trochu doplňuje o možnost aplikace dědičnosti a přetěžování.

Příklad definice hlavní třídy entity:

#[DataModel_Definition(
        name: 'article',
        database_table_name: 'articles',
        id_controller_class: DataModel_IDController_UniqueString::class,
        id_controller_options: [
                'id_property_name' => 'id'
        ]
)]
class Content_Article extends DataModel
{
  .....
}

Zajímavé jsou jednotlivé hodnoty:

  • name: Název entity (či subentity). Právě tento název je používán v dotazech (ukážeme si později v samostatném článku) a vnějších relacích (totéž – později popíšu). Název entity je tedy daný definicí a měl by být neměnný. Je to pevný bod.
  • database_table_name: Název databázové tabulky kde má být entita fakticky uložena. Název může být (v příkladu pro názornost záměrně je) odlišný od názvu entity. A co je důležité, tak název databázové tabulky lze z vnějšku změnit. Tedy je možné na základě okolností měnit názvy tabulek, ale dotazy, relace a vlastně celý kód aplikace zůstává stejný. Dobré například pro testování, nebo pro situace, kdy v aplikaci opravdu může entita mít například archivní tabulky a tak dále.
  • id_controller_class a id_controller_options: Toto se týká mechanismu ID kontrolerů a k tomu se dostanu ještě v dnešním článku.

A teď příklad definice subentity, tedy zde lokalizovaných dat článku:

#[DataModel_Definition(
        name: 'article_localized',
        database_table_name: 'articles_localized',
        id_controller_class: DataModel_IDController_Passive::class,
        parent_model_class: Content_Article::class
)]
class Content_Article_Localized extends DataModel_Related_1toN
{
    .....
}

Za povšimnutí stojí:

  • id_controller_class: Opět přítomno, ale tentokrát má jinou hodnotu. K tomu se dostanu ještě v tomto článku.
  • parent_model_class: Subentita musí mít definováno co (jaká třída) je její nadřazená entita.

A pochopitelně mohou existovat i subentity subentit vlastně do (teoreticky) libovolné úrovně.

Ale vždy platí, že subentita musí být navázána na nadřazenou entitu a vše (v libovolné úrovni) musí být navázáno na hlavní entitu.

Tedy hlavní entita je úroveň 0. Její subentity jsou úroveň 1 a musí mít vazbu na úroveň 0. Úroveň 2 již musí mít vazbu na úroveň 1, ale i na úroveň 0. Úroveň 3 pak má vazbu na úroveň 2, ale opět i na 0 – na hlavní entitu. Prostě a jednoduše všechny subentity libovolné úrovně musí vědět do jaké hlavní entity náleží.

Jak se to dělá si ukážeme v rámci definice vlastností.

Definice vlastností

Definice vlastností a jejich mapování na databázi má obodný princip, tedy opět jsou použity atributy z PHP 8. Ukažme si to na příkladu třídy Content_Article:

#[DataModel_Definition(
    type: DataModel::TYPE_ID,
    is_id: true
)]
protected string $id = '';


#[DataModel_Definition(
    type: DataModel::TYPE_DATE_TIME,
)]
protected ?Data_DateTime $date_time = null;


/**
 * @var Content_Article_Localized[]
 */
#[DataModel_Definition(
    type: DataModel::TYPE_DATA_MODEL,
    data_model_class: Content_Article_Localized::class
)]
protected array $localized = [];

Atributů definic existuje samozřejmě více. Viz dokumentace.

Ale pro teď si ukažme to nejzajímavější:

  • type: Jaký ORM typ vlastnost představuje. Přehled existujících typů najdete zde. ORM se samozřejmě postará o přemapování na příslušné vhodné typy pro příslušný databázový systém na kterém má aplikace běžet (Čii část aplikace – lze vynutit použití určitého backendu pro určitou entitu. Některé entity mohou být uložené v MariaDB a jiné třeba v MS SQL. Ale to odbočuji a předbíhám). Stejně tak se ORM stará o převod na správný datový typ PHP prostřednictvím ORM typu.
  • is_id: Vlastnosti sloužící jako identifikátor záznamu (z pohledu databáze vlastně primární klíč) musí mít nastaven tento atribut. Podotýkám, že se to může týkat více vlastností a ne pouze jedné. To je velice důležité. K tomu se ještě v článku dostanu.
  • data_model_class: Položky typu DataModel::TYPE_DATA_MODEL musí mít definovaný tento atribut. Stejně jako subentita ve své definici deklaruje k jaké nadřazené entitě náleží, tak nadřazená entita říká jaké subentity patří k ní a do jaké vlastnosti třídy budou nahrány jejich instance. Tím je zajištěno vzájemné provázání.

Koukněme se ještě na definici vlastností subentity, tedy zde bude příkladem třída Content_Article_Localized:

#[DataModel_Definition(
    type: DataModel::TYPE_ID,
    is_id: true,
    related_to: 'main.id',
    do_not_export: true
)]
protected string|null $article_id = '';


#[DataModel_Definition(
    type: DataModel::TYPE_LOCALE,
    is_id: true,
    do_not_export: true
)]
protected Locale|null $locale;


#[DataModel_Definition(
    type: DataModel::TYPE_STRING,
    max_len: 255,
    is_key: true
)]
protected string $URI_fragment = '';


#[DataModel_Definition(
    type: DataModel::TYPE_STRING,
    max_len: 100,
)]
protected string $title = '';


#[DataModel_Definition(
    type: DataModel::TYPE_STRING,
    max_len: 65536,
)]
protected string $annotation = '';


#[DataModel_Definition(
    type: DataModel::TYPE_STRING,
    max_len: 655360,
)]
protected string $text = '';

Jistě jste si všimli, že řetězce již mají definovanou maximální délku (max_len), ale to není tak zajímavé.

Zajímavé je toto:

  • related_to: Tento atribut definuje s jakou vlastností nadřazené nebo hlavní entity je vlastnost subentity provázána. Zde je vlastnost $article_id třídy Content_Article_Localized provázána s vlastností $id třídy Content_Article.

    Definice vazby vždy musí existovat. Subentita musí být vždy navázána na nadřazenou entitu. Pokud má hlavní entita (pro teď ji označme v rámci stručnosti jako A) 2 vlastnosti označené atributem is_id: true, pak subentita (nazvěme ji A1) musí mít také dvě vlastnosti stejného ORM typu u nichž bude definována vazba na hlavní entitu A.

    Tedy dejme tomu, že entita A má ID tvořené dvěma vlastnostmi, například: id a version. Pak subentita A1 musí mít vlastnosti například a_id a a_version s definicí vazby: related_to: ‚main.id‘ a related_to: ‚main.version‘

    Ovšem může existovat ještě subentita A1_2 (nebo následně A1_2_3 – princip je stále stejný). Tam se situace trochu mění. Subentita subentity již musí definovat vazbu na hlavní entitu. Tedy stejně jako subentita A1 bude mít A1_2 dvě vlastnosti příslušného typu s příslušnou definicí vazby.

    Ovšem A1_2 musí být vázána na svou bezprostředně nadřazenou subentitu A1. Tedy musí být známa vazba A1 na A1_2. Dejme tomu že A1 přidává další vlastnost představující ID, například kód lokalizace $locale.

    V takovém případě musí mít A1_2 také mít odpovídající vlastnost a u ní definovanou provázanost, tentokrát ovšem takto: related_to = ‘parent.locale’ – main se zmenilo na parent

    Tedy existuje rozdíl v definici vazby. Buď je definována vazba na hlavní entitu (‘main.id’), nebo na přímo nadřazenou subentitu.

    ORM tyto vazby musí znát – hlavní důvod si ukážeme za chvíli.

    Kromě toho je možné definovat i vnější vazby mezi nezávislými entitami, ale o tom příště.

    Dobrá zpráva je, že o to vše se Jet Studio postará. Všechny vazby nastavuje automaticky při vytváření entit. Jasně, jde to dělat i ručně, ale proč by to člověk dělal?

  • do_not_export: Zde jen malá ochutnávka informací ke kterým se vrátím v budoucnu. Entity je možné samozřejmě exportovat – hlavně od JSON. Je tak maximálně usnadněna tvorba REST API ( to bude samostatné téma ). Ale může se stát, že některé vlastnosti rozhodně nikdy nikam nebudeme chtít exportovat. Nejlepším příkladem jsou hesla uživatelů (i když to není plain text, tak i tak to nemá “uniknout”) a podobně.

Tolik stručně k definicím. Ostatní definice viz dokumentace.

Identifikace záznamů a ID kontrolery

A teď konečně jak je to s těmi ID a co jsou to ID kontrolery.

Ve světa PHP je hodně lidí navyknutých na MySQL / MariaDB a její auto-increment. To je moc fajn způsob jak identifikovat záznamy tabulky a pro řadu věcí je to dostačující a nejlepší řešení.

Ovšem není to řešení zdaleka na vše.

V praxi můžete mít systém (zažil jsem opakovaně), kde je více databází mezi kterými je nutné přenášet dílčí záznamy entit (třeba pouze jeden jednotlivý článek či jiný prvek obsahu z mnoha) mezi různými databázemi a je nutné minimalizovat možnost kolize záznamů. A tam se číselné sekvence vyloženě nehodí. Je lepší například kód tvořený časovým razítkem a textovým řetězcem s dostatečnou entropií. Prostě tak aby bylo možné data konkrétní entity (fakticky záznamy z více tabulek) přenést z databáze A do databáze B s velmi nízkou pravděpodobností kolize identifikátorů záznamu.

To byl jeden příklad. Druhý příklad je situace, kdy je identifikátor konkrétního záznamu (z pohledu databáze primární klíč) tvořen ne jednou vlastností (z pohledu databáze sloupečkem), ale více vlastnostmi / sloupečky. A ještě ke všemu nastavení hodnot těchto záznamů podléhá čistě aplikační logice a ne databázi. Tak mám například vyřešené verzování obsahu v rámci CMS použitého pro web php-jet.net.

Zkrátka a jednoduše: Ne, auto-increment id v reálné praxi opravdu neřeší vždy vše.

Vraťme se nyní k entitě Article / článek jako celku. To jak je tato ukázka v ukázkové aplikaci PHP Jet koncipována není náhoda, ale čistý záměr demonstrace celého principu.

Připomeňme si, že třída Content_Article má vlastnost $id, ale to není int, je to string.

Třída Třída Content_Article_Localized má pak dvě vlastnosti označené jako ID (is_id:true) a to $article_id a $locale. První jmenovaná vlastnost je řetězec a má definovanou vazbu na vlastnost hlavní entity. Druhá jmenovaná vlastnost $locale je kód lokalizace (v reálu uloženo jako kód lokalizace dle ISO). Nemá definovanou žádnou vazbu. Obě vlastnosti dohromady tvoří identifikátor konkrétního záznamu – primární klíč.

Definici tedy máme. Ale co se musí stát při uložení nového záznamu, tedy při založení nového článku?

ID článku v tomto ukázkovém případě není číselná sekvence přidělená databází. Něco tedy musí vygenerovat onen náhodný řetězec který bude představovat ID článku. To se musí stát ještě před uložením článku.

Vidíte někde ve třídě Content_Article něco takového? Nehledejte, není to tam.

O vygenerování onoho řetězce se postará to čemu říkám ID kontroler. Ta věc na kterou jsme narazili již v kapitole o definicích.

Ovšem ono vygenerování řetězce samo o sobě nestačí. Však i subentita Content_Article_Localized bude nutně potřebovat vědět jaké je ID článku a to ještě před tím než se začne ukládat do databáze.

Opět – tuto logiku v aplikačních třídách nehledejte. I o to se postará ORM v PHP Jet. Vygenerovaná hodnota ID je předána tam kam předána má být. O otravnou rutinu je postaráno.

Dobře, ale co když chcete použít staré dobré číselné sekvence (které jsou v MySQL/MariaDB pořešené dle mého názoru nejelegantněji, ale jsou k dispozici i v jiných relačních DB). Samozřejmě. To není problém. V rámci ukázkové aplikace jsou takto identifikováni například uživatelé. Ukažme si pro názornost například ukázkovou implementaci návštěvníka:

#[DataModel_Definition(
        name: 'user',
        database_table_name: 'users_visitors',
        id_controller_class: DataModel_IDController_AutoIncrement::class,
        id_controller_options: ['id_property_name' => 'id']
)]
class Auth_Visitor_User extends DataModel implements Auth_User_Interface
{


        #[DataModel_Definition(
                type: DataModel::TYPE_ID_AUTOINCREMENT,
                is_id: true,
        )]
        protected int $id = 0;
}

Ovšem v případě ukládání nového záznamu je zde úplně jiná posloupnost.

ID negeneruje nic v aplikaci, ale přiděluje jej databázový systém. Je tedy nutné nejprve hlavní záznam uložit, pak si od databáze převzít přidělené sekvenční číslo. A pozor! Samozřejmě je nutné toto číslo nastavit vazbám subentit ještě předtím, než dojde k jejich ukládání.

Opět je potřeba určitá logika a tentokrát obrácená: nejprve uložit, pak máme ID a to ID opět předat subentitám. A opět tuto logiku v aplikačních třídách nenajdete.

Je to totiž strašná a neskutečná otrava. Tedy zde je prostor pro ORM aby našince oné otravy zbavilo.

Ovšem důležité je, že ORM neříká jak mají záznamy být identifikovány. Ne. ORM pouze říká, že potřebuje znát logiku vazeb. To je v pořádku. Ostatně to je nutné znát i z hlediska přehlednosti aplikace. A v neposlední řadě ta povinnost subentity navázat na hlavní entitu (na onu zmiňovanou úroveň 0) napomáhá optimalizaci aplikace – prostě je možné data nahrávat jednoduše a rychle.

Nic víc ovšem ORM nediktuje, ale naopak poslušně pomáhá.

PHP Jet nabízí systém ID kontrolerů. ID kontrolery jsou třídy, které řídí onu logiku přidělování ID. Tedy stačí pouze definovat jaké vlastnosti a jakého typu budou tvořit ID a jak s ID chceme zacházet. O vše ostatní (o otravno rutinu) se již ORM postará.

Ano, k tomu jsou tyto definice nad třídami, které jste již v článku viděli:

#[DataModel_Definition(
        id_controller_class: DataModel_IDController_AutoIncrement::class,
        id_controller_options: ['id_property_name' => 'id']
)]

či

#[DataModel_Definition(
        id_controller_class: DataModel_IDController_UniqueString::class,
        id_controller_options: ['id_property_name' => 'id']
)]

nebo

#[DataModel_Definition(
        id_controller_class: DataModel_IDController_Passive::class,
)]

Prostě vývojář určí: toto jsou ID, takto jsou provázána v rámci celé entity a takto se s nimi bude zacházet. To je vše.

A ještě jedna dobrá zpráva. V rámci PHP Jet jsou předchystané tři ID kontrolery:

  • DataModel_IDController_AutoIncrement
    Klasicka s číselnou sekvencí
  • DataModel_IDController_UniqueString
    Generování náhodného textového identifikátoru s časovým razítkem
  • DataModel_IDController_Passive
    Pasivní kontroler – žádné ID negeneruje. Ovšem i v tomto případě jsou nastavovány hodnoty ID v rámci provázanosti při ukládání nového záznamu.

Ale framework PHP Jet má otevřenou architekturu. Tedy je velice jednoduché udělat si vlastní ID kontroler, který bude dělat přesně to co chcete a jak chcete vy.

Shrnutí

Dnes jsem těm co mě znají osobně a čtou to vysvětlil proč jsem začal mít rád ORM – zcela jsem změnil názor.

Ale především jsem ukázal nejen definice entit datového modelu, ale hlavně definice provázání entit a jejich subentit, které dohromady tvoří jednu entitu.

A také jsem naznačil co jsou to ID kontrolery, jaká je jejich návaznost na sekvenci ukládání záznamů a jakou to má souvislost s provázaností subentit.

Jak jsem i zde naznačil, tak práce s databází je velice často nejbolavějším místem online aplikací z hlediska jejich výkonnosti (a také bezpečnosti). Proto se příště zaměřím jednak na princip tvorby dotazů a zejména pak na celou řadu způsobů jak pomocí tohto ORM nahrávat data z databáze – jak nahrávat co nejrychleji, pokud možno jen to co doopravdy potřebuji (například částečné nahrávání instancí a tak dále).

A pochopitelně si také ukážeme vnější relace. Chybět nebude ani práce se surovými daty a tak dále. Tak příště.

Co nového v PHP Jet?

Na Jetu se intenzivně pracuje (proto teď nebyl prostor na článek či video každý týden), vyšla další verze 2023.6. Co je nového?

Jet

Nový subsystém DataListing

Systém pro snadný vývoj různých přehledů dat a seznamů.

Jedná se o refaktorovaný starý Data_Listing s mnohem lepší architekturou a více funkcemi (převzaté z chystané platformy Jet Shop):

  • Lepší definice sloupců
  • Lepší definice filtrů
  • Hromadné operace
  • Export dat
  • Funkce „předchozí položka“ / „další položka“
  • a další …

Starý Data_Listing zůstává kvůli zpětné kompatibilitě plně zachován.

Přidána funkce Auth::loginUser.

Ta slouží k přihlášení uživatele na základě jeho instance.

Auth kontrolery mohou ale nemusí tuto funkci implementovat. Je to vhodné například po snadné přihlášení uživatele po registraci.

Jet Studio

Šablona modulu BasicAdmin přejmenována na BasicCRUD pro lepší srozumitelnost.

Integrován nový subsystém DataListing

Ukázková aplikace

Nový subsystém DataListing plně integrován

Moduly EventViewer.*: Přidán export CSV (příklad exportu dat pomocí DataListing)

Moduly ManageAccess.*.Users: Přidána hromadná operace zablokování uživatele / odblokování uživatele (příklad hromadné operace DataListing).

Přidán příklad registrace návštěvníků (nový modul a jeho integrace do ukázkového webu).

Jet má konečně logo

… a zde vám jej s radostí představuji:

A to už je pro dnešek vše. Za týden či za dva se zase ozvu.

Přeji vám krásné a pohodové letní dny.

Sdílet