Načítání

Už umíme entitu definovat, uložit a mazat. Zůstává nám tedy to nejzajímavější - načítání.

Načítání dat je v běžné online aplikaci častější činnost a je to velice kritické místo aplikace, které bývá velice často příčinou pomalosti a obecně problémů s výkonností. Proto je načítání v Jet DataModel trochu košatější a je několik způsobů jak data načítat. Je důležité je znát, pochopit a hlavně správně používat (a vše si ověřovat přes profiler).

Pojďme se seznámit se všemi možnostmi nahrávání a se všemi specifiky.

Nahrání jednoho konkrétního objektu - metoda load::( )

Jedná se o základní situaci, kdy na základě nějakých podmínek potřebujete získat instanci konkrétního objektu - konkrétní entity. K tomu slouží statická metoda load. Základní použití je úplně jednoduché. Opět použijme jako příklad ukázkovou aplikaci. Máte ID článku a chcete jeho instanci:

$article Content_Article::load$id );

Pokud existuje článek, který má dané ID, v proměnné $article bude jeho instance, v opačném případě hodnota NULL.

Ale tohle zdaleka nestačí. Jak víte z kapitoly o ID kontrolerech, tak záznam může být identifikován více vlastnostmi.

Proberme si teď všechny možnosti.

Nahrání pomocí jednoduchého ID

Pokud je ID tvořené jednou vlastností a chceme instanci určité entity na základě tohoto ID, tak je situace jednoduchá. Jako parametr $id_or_where metodě load předáme toto ID a nemusí se jednat ani o instanci ID kontroleru, ale o prostou hodnotu. Příklad jsme si ukázali hned na začátku.

Nahrání pomocí ID tvořeného z více vlastností

Pokud identifikaci tvoří více jak jedna vlastnost, je nutné to zohlednit. Jednou z možností je vytvoření instance ID kontroleru, jeho naplnění a použití pro nahrání záznamu. Dejme tomu, že v ukázkové aplikaci budete chtít získat na základě ID článku pouze jeho lokalizovanou verzi - tedy subentitu. Je možné to provést takto:

$id Content_Article_Localized::getEmptyIDController();
$id->setValue('article_id'$article_id);
$id->setValue('locale'$loale);

$localized_article Content_Article_Localized::load$id );

Nahrání pomocí dotazu

Reálně nastává situace, kdy víte, že potřebujete jeden konkrétní objekt, ale neznáte jeho ID, ovšem znáte jiné jeho jedinečné atributy. Proto je možné metodě load předat dotaz. Pro příklad sáhnu tradičně do ukázkové aplikace. V té je potřeba získat instanci článku na základě jeho URL (respektive jeho části URL - cesty) a na základě aktuální lokalizace, aby článek mohl být zobrazen:

$current_article = static::load(
    [
        
'article_localized.URI_fragment' => $path,
        
'AND',
        
'article_localized.locale'       => $locale
    
]
);

Parametr $load_filter

Vaší pozornosti určitě neušel druhý parametr metody load. Ten slouží pro aktivaci režimu omezeného nahrávání. Ten se týká nahrávání obecně a vysvětlíme si jej tedy samostatně.

Nahrání více objektů (nahrání seznamu)

Pro nahrání seznamu objektů na základě kritérií jsou k dispozici dvě cesty jak to provést. Výsledek nahrávání je podobný, ale metody samotného nahrán jsou koncipovány pro různé použití.

Metoda ::fetchInstances( )

Hned na začátek si řekněme, že tato metoda nevrací seznam objektů v podobě pole, ale instanci Jet\DataModel_Fetch_Instances, která je iterátorem.

Načítání dat se neprovádí okamžitě po vytvoření instance iterátoru.

Iterátor umožňuje dále nastavit stránkování a řazení (řazení viz dále).

Až v momentě kdy chce aplikace fakticky pracovat se seznamem položek, tak se iterátor postará o nahrání dat z databáze a vytvoření instancí. A to ještě specifickým způsobem. V databázi totiž mohou být desítky tisíc nebo stovky tisíc záznamů - prostě hodně dat. Proto Jet\DataModel_Fetch_Instances funguje tak, že nenahrává rovnou všechna data, ale na základě kritérií nahraje, seřadí a případně ořízne (záznam od X do Y - např. stránkování) pouze informace sloužící jako ID záznamů. A až na základě získaných ID záznamů nahraje záznamy samotné. Tedy pokud potřebujete například procházet seznam produktů e-shopu, který jich má desítky tisíc, tak tento mechanismus se postará o to, že do poslední chvíle se pracuje s minimem dat a nahrají se pouze ta data, která aplikace potřebuje pro koncového uživatele (například pouze 50 produktů pro aktuální stránku seznamu v administraci).

Metoda ::fetch( )

Dalo by se říct, že metoda ::fetch() je nízkoúrovňová. Je určena k přímému nahrání seznamu dat. Bez iterátoru jako mezikroku, bez prodlevy a na základě jasných kritérií. Tedy zde se žádná "magie" nekoná. Je důležité objasnit si parametry této metody:

  • $where_per_model
    Jedná se o asociované pole, kde klíč je název entity (případně prázdná hodnota či klíčové slovo this - oboje identifikuje aktuální entitu) a hodnota je dotaz, na základě kterého budou data této entity / subentity nahrána. Tedy jeden dotaz může definovat jaké budou nahrány hlavní entity a další dotazy mohou definovat jaké budou nahrány subentity.
  • $order_by
    Definice řazení seznamu. Viz níže.
  • $item_key_generator
    Pokud je třeba, aby klíče pole výsledného seznamu měli určitou hodnotu (jednalo se o asociované pole), pak je možné předat generátor klíčů.
    Prototyp generátoru: function( DataModel $item ) : string
  • $load_filter
    Režim omezeného nahráván. Viz níže.

Metoda ::fetchIDs( )

Může nastat situace, že nepotřebujete instance entit, ale pouze seznam jejich identifikátorů - tedy instancí ID kontrolerů.

To umožňuje metoda ::fetchIDs, která je vlastně ekvivalentní metodě ::fetchInstances( )

Tedy metoda také nevrací ihned seznam, ale vrací instanci iterátoru - tentokrát Jet\DataModel_Fetch_IDs, který poskytuje seznam identifikátorů záznamů.

Jinak je filozofie a chování totožné jako u metody ::fetchInstances( ).

Metody ::dataFetch*( )

Samozřejmě ne vždy je dobrý nápad vytvářet "mraky" instancí nějakých objektů. Pro provedení určitých úloh je vhodné (ne-li nutné) pracovat přímo s daty bez instancí objektů jako mezivrstvy. Například pokud máte provádět nějakou údržbu a přepočty nad desítkami tisíc záznamů (nebo více), tak je to prostě nutné - a nemusí samozřejmě jít pouze o údržbu dat.

Proto Jet DataModel nabízí možnost nahrát surová data a má také další možnosti práce se surovými daty. Ano, nabízí se možnost použít přímo Jet Db a provádět klasické dotazy. Tu možnost máte a někdy může být zcela legitimní. Ovšem můžete například přijít o přenositelnost a jiné výhody ORM. Nic není černobílé - vždy záleží na konkrétní situaci.

Pro načítání surových data existuje sada statických metod jejichž název vždy začíná ::dataFetch*. Parametry těchto metod jsou stejné, liší se pouze podoba výstupních dat.

Parametry metod ::dataFetch*

  • $select
    Definice SELECT - viz dále.
  • $where
    WHERE část dotazu.
  • $group_by>
    Definice GROUP BY - viz dále.
  • $having
    HAVING část dotazu, která se tvoří stejně jako WHERE s tím, že se výrazy mohou odkazovat na položky SELECT (viz dále).
  • $order_by
    Definice řazení - viz níže.
  • $limit
    Limit počtu načtených záznamů.
  • $offset
    Offset načtených záznamů.

Seznam metod ::dataFetch*

  • dataFetchAll
    Prosté nahrání všech dat.
  • dataFetchAssoc
    První vlastnost bude brána jako klíč pole výsledného seznamu.
  • dataFetchCol
    Výsledkem bude prosté jednorozměrné pole tvořené první nahranou vlastností entity.
  • dataFetchPairs
    Vrátí asociované pole, kde klíčem bude první určená vlastnost a hodnotou ta druhá.
  • dataFetchRow
    Vrátí pouze jeden řádek.
  • dataFetchOne
    Vrátí pouze jednu hodnotu.

Specifikace SELECT, HAVING a GRUOP BY

Opět přejdeme rovnou k praktickému příkladu. Dejme tomu, že si chcete vytáhnout jaká základní třída události je ve vaší administraci nejčastější. Potřebujete tedy spočítat čestnost (použít běžnou SQL funkci), budete potřebovat i GROUP BY. A aby to bylo ještě zajímavější, budete chtít znát jenom ty položky, jejichž četnost je vyšší nebo rovna 20 - tedy bez HAVING to také nepůjde.

Je to možné a jeden příklad je zde lepší než tisíc slov:

$event_stats Logger_Admin_Event::dataFetchPairs(
    
select: [
        
'event_class',
        
'cnt' => new DataModel_Query_Select_Item_Expression'COUNT( %ID% )', ['ID' => 'id'] )
    ],
    
group_by'event_class',
    
having: ['cnt >=' => 20 ],
    
order_by'-cnt'
);

Uvedený příklad se sice týká nahrávání surových dat, ovšem definice SELECT, GROUP BY a HAVING je možná pro jakýkoliv dotaz reprezentovaný třídou Jet\DataModel_Query.

Instanci definice dotazu drží například iterátory pro načítání (Jet\DataModel_Fetch_Instances a Jet\DataModel_Fetch_IDs) a definice dotazu reprezentovaná instancí třídy Jet\DataModel_Query má potřebné metody pro nastavení SELECT, GROUP BY, HAVING, WHERE i ORDER BY.

Řazení

Řazení se dá použít na více místech, ale než si to ukážeme, tak se podívejme na obecné pravidlo definice řazení.

Řazení se definuje jako pole (protože je samozřejmě možné řadit podle více vlastností), které obsahuje názvy vlastností ve formátu nazev_entity.nazev_vlastnosti stejně jako u dotazů, nebo je možné použít prvek SELECT (viz Specifikace SELECT). S tím rozdílem, že před touto hodnotou může být znak +, nebo znak -. Znak + znamená vzestupné řazení a znak - znamená sestupné řazení. Vzestupné řazení je výchozí a znak + tedy není nutné uvádět.

Pokud budeme řadit pouze podle jedné vlastnosti, tak není nutné řazení specifikovat jako pole a je možné použít odkaz této vlastnosti, nebo prvku SELECT.

Tedy řazení lze definovat takto:

$order_by = [
    
'entity_x.property_a'
    
'-entity_x.property_b'
    
'+entity_y.property_c'
];

A teď kde řazení použít:

  • Jako parametr metod ::fetch( ) a ::dataFetch*()
  • Nastavení řazení iterátorů
    Metody ::fetchInstances( ) a ::fetchIDs( ) vrací instanci iterátoru Jet\DataModel_Fetch_Instances a Jet\DataModel_Fetch_IDs.
    U obou těchto iterátorů lze přes přístup ke generovanému dotazu nastavit řazení a to takto: $articles Content_Article::fetchInstances();
    $articles->getQuery()->setOrderBy([
        
    '-article.date_time'
    ]);
  • Nastavit výchozí řazení subentitě, která je v relaci 1:N v rámci definice:
    #[DataModel_Definition(
    default_order_by: ['property_name','-next_property_name''+some_property_name']
    )]
  • Nastavit výchozí řazení subentitě, která je v relaci 1:N pomocí metody setLoadRelatedDataOrderBy:
    SomeSubentity::setLoadRelatedDataOrderBy( ['property_name','-next_property_name''+some_property_name'] );

Režim omezeného nahrávání

V praxi je vhodné vyvarovat se nahrávání dat, které nepotřebujeme. Dejme tomu, že potřebujete seznam článků, ale pouze jejich metadata, titulky a anotaci, nikoliv samotný text článku. Jak to udělat?

Většina metod pro nahrávání (krom ::fetchData() ) má parametr $load_filter. Hodnota tohoto parametru je buď pole (v reálné praxi použitelnější), nebo rovnou instance třídy Jet\DataModel_PropertyFilter - obojí představuje nahrávací filtr, tedy pravidlo co má být nahráno.

Poznámka: Pole je vždy převedeno na instanci Jet\DataModel_PropertyFilter a interně se dále pracuje pouze s touto instancí.

Ukažme si konečně, jak filtr v praxi použít a to právě na uvedeném příkladu s články: $articles Content_Article::fetchInstances(
    
where: [
        
'date_time >=' => $some_date
    
],
    
load_filter: [
        
'article.*',
        
'article_localized.title',
        
'article_localized.annotation'
    
]
);

Jak vidíte, tak filtr je v praxi opravdu dobré definovat jako pole. A má vlastně jednoduché pravidla. Pole je opět tvořeno odkazy na entity a jejich vlastnosti. Ovšem místo názvu vlastnosti lze použít znak *. A ano, ten říká, že z dané entity chceme načíst všechny její vlastnosti.

Důležité upozornění: Pokud je objekt nahrán v omezeném režimu, tak jej nelze ukládat a ani mazat, aby se zabránilo teoretické nekonzistenci dat. Takto nahrané objekty jsou pouze pro čtení a pokus o jejich ukládání či mazání má za následek vyhození výjimky.

Doporučení

Na závěr této kapitoly mi dovolte jedno doporučení. Jet DataModel umí operovat i s voláním SQL funkcí, umí HAVING, používá GROUP BY.

Ale praxí jsem dospěl k závěru, že je dobré se čemukoliv složitému v rámci SQL vyhnout. Místo SQL funkcí raději ukládám předpřipravená data, nebo postprocessing dat nechávám na PHP. Prostě dělá vše tak, aby relační databáze byla dobrým sluhou (spíš teda raději parťákem), ale ne pánem. A to jsem prosím kdysi byl možná až SQL fanatik :-)

Předchozí kapitola
Tvorba dotazů
Další kapitola
Práce se surovými daty