Lekce 7 - Standardy jazyka PHP - PSR-6 (cachovací rozhraní)
Předchozí lekce, Standardy jazyka PHP - PSR-4 a autoloader, byla věnována specifikaci PSR-4, která se týka autoloaderu.
Cache (vyslovujeme [keš], v češtině mezipaměť) je běžně využívána ke zrychlení všech aplikací a projektů. Díky tomu jsou cachovací systémy jednou z nejběžnějších součástí všech frameworků. Mnoho knihoven si tak tvoří i své vlastní cachovací systémy s různým stupněm funkcionality. Kvůli rozdílům pak vývojáři musí mít znalosti o více systémech zároveň, z nichž některé můžou, ale také nemusí podporovat zrovna ty funkce, které potřebují. Zároveň samotní developeři cachovacích knihoven musí volit mezi podporou pouhé části frameworků a velkým množstvím nadbytečných tříd umožňujících kompatibilitu.
PSR-6: Caching Interface
Tyto problémy by mělo vyřešit právě společné rozhraní, které PSR-6 definuje. Vývojáři pak můžou očekávat stejné výsledky od různých systémů a ty naopak musí implementovat jenom několik rozhraní namísto celé škály přídavných tříd.
Klíčové koncepty
Nejdříve začneme u klíčových konceptů.
Pool
Pool nebo fond reprezentuje kolekci prvků v cachovacím systému. Všechny cachovatelné prvky jsou z poolu vraceny jako objekty a jakákoliv interakce s těmito objekty musí probíhat za pomocí poolu.
Items
Item (prvek) reprezentuje objekt nebo hodnotu v poolu, kterému odpovídá klíč. Tento klíč je primárním a unikátním identifikátorem a MUSÍ být nezměnitelný. Hodnota ale MŮŽE být změněna.
Definice
Ve standardu jsou použity pojmy, které si nejdříve vysvětlíme.
Calling Library / Volající knihovna
Calling Library je knihovna nebo část kódu, která využívá cache, ale o vnitřní implementaci cachovacích systémů nemá žádné informace.
Implementing Library / Implementující knihovna
Implementing Library implementuje standard za účelem dodání cachovacích
systémů pro volající knihovny. MUSÍ dodat rozhraní
Cache\CacheItemPoolInterface
a
Cache\CacheItemInterface
a MUSÍ podporovat alespoň TTL
funkcionalitu. Co jednotlivá rozhraní definují si vysvětlíme na konci
článku v samostatné kapitole.
TTL (The Time To Live) / Doba životnosti
TTL je čas, po který je objekt brán jako platný. Jedná se o dobu od vzniku objektu až po jeho zánik.
Expirace
Expirace je opravdový čas, kdy vyprší doba životnosti objektu. Většinou se počítá přidáním TTL k času, kdy prvek vznikl, ale může být změněn. Implementující knihovny MOHOU dobu životnosti objektu zkrátit, ale MUSÍ se k prvku chovat jako k neplatnému, pokud příslušný čas již vypršel. Implementující knihovny MOHOU použít výchozí nastavení TTL, pokud ale žádné neexistuje, MUSÍ prvek uložit do cache napořád (dokud je to možné).
Klíč
Klíč je řetězec o minimálně jednom znaku, který
je unikátní pro daný prvek v mezipaměti. Implementující
knihovny MUSÍ podporovat klíče v UTF-8
kódování až
do délky 64 znaků, které obsahují malá a velká písmena,
celá čísla, podtržítko a tečku v jakémkoliv pořadí. Podporu dalších
znaků nebo delších řetězců MOHOU přidat. NEMOHOU však přidat podporu
znaků {}
, ()
, /
, \
,
@
a :
, protože ty jsou vyhrazeny pro budoucí
potřeby a rozšíření. Zároveň jsou implementující knihovny zodpovědné
za své vlastní escape sekvence. MUSÍ být ale schopny vrátit
původní klíč bez modifikací.
Hit a miss
Pokud se volající knihovna pokusí vyhledat prvek podle klíče a je
nalezen, má platnou dobu životnosti a data nejsou žádným způsobem
poškozena, jedná se o tzv. cache hit. V opačném případě
se jedná o tzv. cache miss, dokonce i když je pouze
překročena doba životnosti. Volající knihovny by MĚLY ověřit
isHit()
v každém volání get()
.
Odložení (deferred)
Odložené uložení do mezipaměti poukazuje na skutečnost, že prvek
nemusí být ihned permanentně uložen poolem (například do databáze). Pool
MŮŽE pozdržet uložení prvku, aby mohly být využity hromadné operace
podporované některými paměťovými moduly. Pool MUSÍ zajistit, že
jakýkoliv pozdržený prvek je nakonec uložen a data nejsou ztracena. MŮŽE
je také uložit předtím, než o to požádá volající knihovna. Když
volající knihovna použije metodu commit()
, všechny zbývající
odložené prvky MUSÍ být uloženy. Implementující knihovna MŮŽE použít
jakoukoliv logiku, aby určila, kdy odložené prvky uložit (například
destruktor, vytvoření všech najednou při zavolání save()
,
maximálního počtu prvků). Žádost o prvek v mezipaměti, která byla
odložena, MUSÍ vrátit odložený, ale ještě neuložený, prvek.
Data
Všechny knihovny MUSÍ podporovat všechny serializovatelné datové typy (ty, které lze ukládat a přenášet přímo):
String
– řetězce znaků o nahodilé délce v jakémkoliv formátování, které PHP podporujeInteger
– všechna celá čísla až do 64-bit signed (= záporné i pozitivní čísla)Float
– čísla s desetinnou čárkou až do velikosti 64-bit signed (= záporné i pozitivní čísla)Boolean
– pravda/nepravda (true
/false
)Null
– prázdná hodnotaArray
– indexované pole, včetně multidimenzionálních polí o libovolné dimenziObject
– jakýkoliv objekt, který podporuje bezeztrátovou (de)serializaci, např.$o == unserialize(serialize($o)
. Objekty MOHOU využívat dvou tzv. magických metod__sleep()
a__wakeup()
nebo podobných funkcionalit jazyka, pokud je potřeba.
Pokud není možné přesně vrátit uloženou hodnotu z jakéhokoliv důvodu, tak MUSÍ knihovny odpovědět pomocí cache miss namísto poškozenými daty.
Zachytávání chyb
Ačkoliv cachování nemalým dílem přispívá k výkonnosti aplikace, nemělo by nikdy být nepostradatelnou součástí. Proto by chyba v cachovacím systému NEMĚLA vyústit v selhání aplikace. Z tohoto důvodu NESMÍ implementující knihovny házet jiné výjimky, než jaké jsou definovány v rozhraní. Také BY MĚLY zachytit jakékoliv chyby nebo výjimky a neumožnit jim „vybublat“.
Implementující knihovny BY MĚLY tyto chyby logovat anebo je jinak předat ke zpracování administrátorům.
Pokud volající knihovna zažádá o smazání jednoho nebo více prvků, nebo o smazání celého poolu, NESMÍ vyústit v chybu, pokud hledaný prvek neexistuje. Koncová podmínka je stejná (pool je prázdný a prvek je smazán – klíč neexistuje), a proto není důvod k vyvolání chyby.
Rozhraní
V průběhu byla zmíněna různá rozhraní. Co vlastně pro standard znamenají?
CacheItemInterface
Definuje prvek v cachovacím systému. Každý prvek MUSÍ být spojen se
specifickým klíčem, který může být přiřazen na základě
implementujícího systému a je obvykle předáván pomocí
Cache\CacheItemPoolInterface
objektu.
Objekt Cache\CacheItemInterface
zapouzdřuje ukládání a
vyvolávání nacachovaných prvků a je generován pomocí objektu
Cache\CacheItemPoolInterface
, který je zároveň zodpovědný za
prvotní nastavení a přiřazení klíčů. MUSÍ být schopen uložit a
vrátit jakýkoliv serializovatelný datový typ (viz sekce data na začátku
článku).
Volající knihovny NESMÍ vytvářet instance prvků. Mohou je pouze volat z
poolu pomocí metody getItem()
. Zároveň by NEMĚLY
předpokládat, že prvek vytvořený pomocí jedné implementující knihovny
je kompatibilní s poolem jiné.
Rozhraní CacheItemInterface
může vypadat například
takto:
<?php namespace Psr\Cache; interface CacheItemInterface { public function getKey(); /** * Vrací klíč pro cachovaný prvek * * Klíč je poskytnut implementující knihovnou, ale měl by být * k dispozici ostatním volajícím, pokud je zapotřebí * * @return string * Klíč */ public function get(); /** * Získá hodnotu prvku z cache spojené s daným klíčem * * Vrácená hodnota musí být identická s hodnotou uloženou pomocí * set() * Pokud isHit() vrátí false, tato metoda MUSÍ vracet null * Null však může být uložená hodnota, proto musí rozlišovat * mezi „nic nenalezeno“ a „nalezeno null“ * * @return mixed */ public function isHit(); /** * Potvrdí, jestli hledání skončilo jako cache hit * NESMÍ nastat souběh mezi voláním isHit() a get() * * @return bool * Pokud se jedná o cache hit, vrací true, jinak false */ public function set($value); /** * Uloží hodnotu reprezentovanou cachovaným prvkem * * @param mixed $value * Serializovatelná hodnota, kterou chceme uložit * * @return static */ public function expiresAt($expiration); /** * Nastaví expiraci pro daný prvek NA určitou dobu * * @param \DateTimeInterface|null $expiration * Doba, kdy MUSÍ být prvek považován za expirovaný * Pokud nastavíme null, MŮŽE být použita výchozí hodnota * nebo prvek uložíme po maximální možnou dobu * * @return static * Volaný objekt */ public function expiresAfter($time); /** * Nastaví expiraci, ale PO určité době * * @param int|\DateInterval|null $time * Předáváme integer, který reprezentuje čas ve vteřinách * nebo null jako v předchozí funkci * * @return static */ }
CacheItemPoolInterface
Hlavním účelem tohoto rozhraní je přijímat klíče od volající knihovny a vracet objekty s nimi spojené. Zároveň funguje jako vstupní bod pro interakci s celou cache kolekcí. Jakákoliv inicializace a konfigurace poolu je přenechána implementující knihovně.
Jako příklad rozhraní CacheItemPoolInterface
si můžeme
uvést třeba toto:
<?php namespace Psr\Cache; /** * CacheItemPoolInterface vytváří CacheItemInterface objekty */ interface CacheItemPoolInterface { public function getItem($key); /** * Vrací prvek reprezentující daný klíč * * MUSÍ vracet CacheItemInterface objekt, a to i v případě * cache miss. NESMÍ vracet null * * @param string $key * Klíč hledaného objektu * * @throws InvalidArgumentException * V případě neplatného klíče MUSÍ být vyhozena * \Psr\Cache\InvalidArgumentException * * @return CacheItemInterface */ public function getItems(array $keys = array()); /** * Vrací sadu traversable (možné iterovat pomocí foreach) prvků * @param string[] $keys * Pole klíčů * * @throws InvalidArgumentException * Pokud i jeden klíč je neplatný, MUSÍ být hozena * \Psr\Cache\InvalidArgumentException * * @return array|\Traversable * Traversable kolekce cachovaných prvků i s jejich klíči * Cachovaný prvek bude vrácen pro každý klíč, i když nebyl * nalezen * Pokud nespecifikujeme žádný klíč, MUSÍ být vrácena prázdná * traversable kolekce */ public function hasItem($key); /** * Potvrdí, jestli cache obsahuje hledaný prvek * * Tato metoda může kvůli výkonu pozdržet získání hodnoty. * To by mohlo vyústit v souběh s metodou get(). * Abychom se této situaci vyhnuli, je doporučeno používat * spíš isHit(). * * @param string $key * * @return bool * True, pokud prvek existuje, jinak false */ public function clear(); /** * Odstraní všechny prvky v poolu * * @return bool * True, jestliže byla operace úspěšná */ public function deleteItem($key); /** * Odstraní prvek z poolu * * @param string $key * Klíč prvku * * @throws InvalidArgumentException * Pokud klíč není platný, MUSÍ být vyhozena * \Psr\Cache\InvalidArgumentException * * @return bool * True, pokud byl prvek úspěšně odstraněn */ public function deleteItems(array $keys); /** * Odstraní více prvků z poolu * * @param string[] $keys * Pole klíčů prvků, které mají být odstraněny * * @throws InvalidArgumentException * Pokud jakýkoliv klíč není platný * * @return bool */ public function save(CacheItemInterface $item); /** * Okamžitě uloží cachovaný prvek (př. do databáze) * * @param CacheItemInterface $item * Prvek, který chceme uložit * * @return bool * True, pokud byla operace úspěšná, jinak false */ public function saveDeferred(CacheItemInterface $item); /** * Určí, že má být prvek uložen později * * @param CacheItemInterface $item * Prvek, který má být uložen * * @return bool * False, pokud prvek nemohl být zařazen do fronty, nebo pokud pokus o uložení selhal. Jinak true. */ public function commit(); /** * Uloží pozdržené prvky * * @return bool * True, pokud byly všechny prvky úspěšně uloženy, * nebo nebyly žádné, které by na uložení čekaly. */ }
CacheException
Je určeno pro zachytávání kritických chyb jako například při připojení ke cache serveru nebo ověřování. Jakákoliv výjimka hozená implementující knihovnou MUSÍ mít toto rozhraní.
InvalidArgumentException
Rozhraní pro neplatné cache argumenty implementuje rozhraní
CacheException
:
<?php namespace Psr\Cache; /** * Rozhraní pro neplatné cache argumenty * * Kdykoliv je metodám předán neplatný argument, musí být hozena * výjimka, kterou implementuje * Psr\Cache\InvalidArgumentException. */ interface InvalidArgumentException extends CacheException { }
V další lekci, Standardy jazyka PHP - PSR-7 (Obecná specifikace a proudy), se zaměříme na standard PSR-7, který se zabývá rozhraním pro HTTP komunikaci.