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 » |