Filtry
- Úvod
- Registrace filtru
- Více filtrů
- Parametry filtrů
- HasMany vazby
- Implicitní filtry
- Objekt Filtering - anonymní filtry
- SQL strategie
Filtry umožňují doladit Lean Mapperem připravený databázový dotaz těsně před tím, než se pošle do databáze. Pokud např. máme entitu Author a načítáme autorovy knihy (entitu Book) pomocí $author->books, Lean Mapper připraví potřebný dotaz (objekt LeanMapper\Fluent) a dovolí nám v něm cokoli upravit.
Důvodů pro úpravu připraveného dotazu může být mnoho - můžeme výsledek dotazu limitovat, řadit, přijoinovat další tabulku, apod. Fantazii se meze nekladou.
Pojďme k praktické ukázce. Řekněme, že chceme autorovy knihy vždy načítat seřazené podle názvu. Nadefinujeme si tedy entity Book a Author.
/**
* @property int $id
* @property string $name
* @property Author $author m:hasOne
*/
class Book extends LeanMapper\Entity
{
}
/**
* @property int $id
* @property string $name
* @property Book[] $books m:belongsToMany m:filter(bookOrderByName)
*/
class Author extends LeanMapper\Entity
{
}
Na entitě Book není nic zajímavého, zajímavější je entita Author. Všimněte si příznaku m:filter u položky $books.
Tímto příznakem říkáme Lean Mapperu, že má při získávání kolekce autorových knih zavolat filtr pojmenovaný bookOrderByName. Aby to fungovalo, musíme ještě samotný filtr naprogramovat a zaregistrovat.
Registrace filtru
Filtrem může být jakákoli volatelná metoda, nebo funkce (klidně anonymní). Filtr zaregistrujeme pomocí metody LeanMapper\Connection::registerFilter(), které předáme název filtru a odkaz na zvolenou metodu/funkci.
$connection->registerFilter('bookOrderByName', ['BookFilter', 'orderByName']);
Uvedený řádek zaregistruje filtr bookOrderByName a zároveň říká, že se má při použití tohoto filtru zavolat statická metoda BookFilter::orderByName. Samotný filtr bude úplně jednoduchý, jen přidá do připraveného dotazu klauzuli ORDER BY s názvem sloupce:
class BookFilter
{
public static function orderByName(LeanMapper\Fluent $fluent)
{
$fluent->orderBy('[name]');
}
}
Více filtrů
Filtry v příznaku m:filter můžeme zřetězit - Lean Mapper filtry zavolá jeden po druhém. Jednotlivé filtry se oddělují čárkou.
* @property Book[] $books m:belongsToMany m:filter(bookOrderByName, bookOnlyAvailable)
Uvedená definice způsobí, že na dotaz bude kromě filtru bookOrderByName aplikován i filtr bookOnlyAvailable.
Parametry filtrů
Kromě instance LeanMapper\Fluent může filtr obdržet i další parametry. Pojďme si je probrat v pořadí, ve kterém jsou filtru předány.
Databázový dotaz
Prvním parametrem filtru je vždy instance objektu LeanMapper\Fluent, která obsahuje předpřipravený dotaz.
Parametry předané přes auto-wiring
Další parametry můžou obsahovat entitu a property (LeanMapper\Reflection\Property), u které byl filtr zapsán. Aby k předání došlo, musíme při registraci filtru uvést jednu z konstant LeanMapper\Connection::WIRE_*.
$connection->registerFilter('bookOrderByName', ['BookFilter', 'orderByName'], LeanMapper\Connection::WIRE_ENTITY_AND_PROPERTY);
Uvedený zápis řekne Lean Mapperu, že má filtru předat ve druhém parametru entitu a ve třetím property:
class BookFilter
{
public static function orderByName(LeanMapper\Fluent $fluent, LeanMapper\Entity $entity, LeanMapper\Reflection\Property $property)
{
// ...
}
}
Dalšími možnými hodnotami jsou:
LeanMapper\Connection::WIRE_ENTITY- předá jen entituLeanMapper\Connection::WIRE_PROPERTY- předá jen property
Poznámka: místo konstant lze použít i textové alternativy - znak e odpovídá konstantě WIRE_ENTITY, znak p konstantě WIRE_PROPERTY a ekvivaletnem ke konstantě WIRE_ENTITY_PROPERTY jsou řetězce ep a pe.
Adresované parametry
Další parametry filtru můžou obsahovat tzv. adresované parametry. To jsou hodnoty, které uvedeme přímo v definici entity a vztahují se vždy ke konkrétnímu filtru. Obecný filtr pro limitování výsledků by tedy mohl vypadat takto:
class CommonFilter
{
public static function limit(LeanMapper\Fluent $fluent, $limit)
{
$fluent->limit($limit);
}
}
$connection->registerFilter('limit', ['CommonFilter', 'limit']);
/**
* @property int $id
* @property string $name
* @property Book[] $books m:belongsToMany m:filter(limit#10)
*/
class Author extends LeanMapper\Entity
{
}
Při volání $author->books tedy obdržíme vždy pouze prvních 10 knih.
Poznámka: uvedený příklad nebude fungovat zcela podle očekávání - viz kapitola SQL strategie.
Dynamicky předané parametry
Na závěr můžou následovat parametry dynamicky předané volajícím pomocí getteru. Hodnoty budou předány do všech filtrů uvedených v příznaku m:filter. Při použití výše uvedeného filtru pro limitování výsledků to může vypadat nějak takto:
class CommonFilter
{
public static function limit(LeanMapper\Fluent $fluent, $limit)
{
$fluent->limit($limit);
}
}
$connection->registerFilter('limit', ['CommonFilter', 'limit']);
/**
* @property int $id
* @property string $name
* @property Book[] $books m:belongsToMany m:filter(limit)
*/
class Author extends LeanMapper\Entity
{
}
$books = $author->getBooks(20); // získá prvních 20 knih
Poznámka: uvedený příklad nebude fungovat zcela podle očekávání - viz kapitola SQL strategie.
HasMany vazby
V případě hasMany vazeb se pokládají celkem 2 dotazy - jeden načítá data ze spojovací tabulky a další z cílové tabulky. Pomocí filtrů můžeme ovlivnit oba položené dotazy, stačí filtry v příznaku m:filter oddělit pomocí svislítka |.
class CommonFilter
{
public static function limit(LeanMapper\Fluent $fluent, $limit)
{
$fluent->limit($limit);
}
public static function orderBy(LeanMapper\Fluent $fluent, $column)
{
$fluent->orderBy('%n', $column);
}
}
$connection->registerFilter('limit', ['CommonFilter', 'limit']);
$connection->registerFilter('orderBy', ['CommonFilter', 'orderBy']);
/**
* @property int $id
* @property string $name
*/
class Tag extends LeanMapper\Entity
{
}
/**
* @property int $id
* @property string $name
* @property Tag[] $tags m:hasMany m:filter(limit#10|orderBy#name)
*/
class Book extends LeanMapper\Entity
{
}
Výše uvedený příklad načte pomocí $book->tags prvních 10 tagů souvisejících s knihou a seřadí je podle názvu. Všimněte si příznaku m:filter - pomocí filtru limit nejprve limitujeme dotaz do tabulky book_tag a následně upravujeme dotaz do tabulky tag pomocí filtru orderBy tak, aby vrátil tagy seřazené podle jména.
Poznámka: uvedený příklad nebude fungovat zcela podle očekávání - viz kapitola SQL strategie.
Implicitní filtry
Výchozí, neboli implicitní, filtry jsou takové filtry, které budou aplikovány vždy bez ohledu na to, jestli jsou uvedeny v příznaku m:filter, či nikoli. Implicitní filtry se spouští při traverzování mezi entitami a z repozitáře v rámci metody LeanMapper\Repository::createFluent() - kvůli tomu nemůžou obdržet parametry předávané pomocí auto-wiringu. Využití naleznou např. pro soft-deleted entity a další podobné případy.
K definici implicitních filtrů slouží metoda LeanMapper\IMapper::getImplicitFilters, která vrací buď pole s názvy filtrů, nebo instanci objektu LeanMapper\ImplicitFilters - ta může kromě názvů obsahovat i adresované parametry, které se mají filtrům předat.
Pojďme si to ukázat v praxi - řekněme, že tabulka knih obsahuje sloupec available, který indikuje, zda je kniha dostupná, či nikoli a nabývá hodnot 0 a 1. Při načítání knih kdekoli v aplikaci pak chceme, aby se načetly jenom dostupné knihy.
/**
* @property int $id
* @property string $name
* @property bool $available
*/
class Book extends LeanMapper\Entity
{
}
/**
* @property int $id
* @property string $name
* @property Book[] $books m:belongsToMany
*/
class Author extends LeanMapper\Entity
{
}
class CommonFilter
{
public static function restrictAvailables(LeanMapper\Fluent $fluent, $table)
{
$fluent->where('%n.[available] = 1', $table);
}
}
$connection->registerFilter('restrictAvailables', ['CommonFilter', 'restrictAvailables']);
class Mapper extends LeanMapper\DefaultMapper
{
public function getImplicitFilters($entityClass, LeanMapper\Caller $caller = null)
{
if ($entityClass === 'Book') {
return new LeanMapper\ImplicitFilters(
['restrictAvailables'],
[
'restrictAvailables' => [ // parametry pro filtr 'restrictAvailables'
$this->getTable($entityClass),
],
]
);
}
return parent::getImplicitFilters($entityClass, $caller);
}
}
$mapper = new Mapper;
$books = $author->books;
Nejprve definujeme entity Book a Author, poté si vytvoříme obecný filtr CommonFilter::restrictAvailables. Nejdůležitější je v tomto případě vlastní mapper - v něm definujeme metodu getImplicitFilters, která pro entitu Book vytvoří instanci objektu LeanMapper\ImplicitFilters - té předá nejprve seznam filtrů (zde pouze filtr restrictAvailables) a následně i adresovaný parametr s názvem tabulky, na kterou má být omezení aplikováno. Díky tomu nám bude volání $author->books vždy vracet jenom dostupné knihy.
Metoda getImplicitFilters může kromě názvu entity obdržet ještě nepovinný parametr $caller. Implicitní filtry jsou obvykle aplikovány při volání metody Repository::createFluent - pak parametr $caller obsahuje odkaz na repositář, nebo při traverzování mezi entitami - v tom případě parametr $caller obsahuje odkaz na entitu a property, přes kterou se traverzuje.
Objekt Filtering - anonymní filtry
Filtry lze použít nejen pomocí příznaku m:filter a implicitních filtrů, ale i ve vlastních přístupových metodách uvnitř entit. Stačí vytvořit instanci objektu LeanMapper\Filtering a tu předat do volaných metod - např. do metody getValueByPropertyWithRelationship. Vytvoříme tak něco, co se dá nazvat anonymními filtry.
/**
* @property int $id
* @property string $name
* @property Book[] $books m:belongsToMany(#union)
*/
class Author extends LeanMapper\Entity
{
public function getNewestBook()
{
$books = $this->getValueByPropertyWithRelationship('books', new LeanMapper\Filtering(function (LeanMapper\Fluent $fluent) {
$fluent->orderBy('pubdate')->desc()
->limit(1);
}));
return empty($books) ? null : reset($books);
}
}
Volání $author->getNewestBook() vrátí vždy nejnovější autorovu knihu.
Poznámka: v uvedeném příkladu používáme u příznaku m:belongsToMany dovětek #union. Účel tohoto dovětku popisuje kapitola SQL strategie.
SQL strategie
S filtry souvisí tzv. SQL strategie. Blíže se tomuto tématu věnujeme v samostané kapitole.
| « Persistence | Mapper » |