Lekce 5 - Standardy jazyka PHP - Implementace PSR-3
V minulé lekci, Standardy jazyka PHP - PSR-3 a specifikace loggeru, jsme si představili základní myšlenky specifikace PSR-3, která se zabývá logováním.
Dnešní lekce je věnována implementaci PSR-3.
Balíček
Rozhraní a popsané třídy jsou stejně jako příslušné třídy výjimek a testovací prostředí poskytnuté jako část balíčku psr/log pro kontrolu vaší implementace. Níže je rozhraní s českými komentáři.
Rozhraní Psr\Log\LoggerInterface
<?php namespace Psr\Log; /** * Popisuje instanci loggeru * * Zpráva MUSÍ být string nebo objekt, který implementuje __toString(). * * Zpráva MŮŽE obsahovat zástupné identifikátory ve formě {foo}, kde foo * bude nahrazeno kontextovými daty pod klíčem "foo". * * Kontextové pole může obsahovat libovolná data, jediným předpokladem * implementátora je, že pokud je předávána instance Exception za účelem * produkování stack trace, MUSÍ být v klíči jménem "exception". * * Pro plnou specifikaci rozhraní navštivte * https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md */ interface LoggerInterface { /** * Systém je nepoužitelný. * * @param string $message * @param array $context * @return null */ public function emergency($message, array $context = array()); /** * Je nutné ihned provést akci. * * Příklad: Celá stránka je mimo provoz, databáze nedostupná a podobně. Metoda by * měla spustit SMS upozornění a vzbudit vás. * * @param string $message * @param array $context * @return null */ public function alert($message, array $context = array()); /** * Kritické podmínky. * * Příklad: Komponenta aplikace je nedostupná, neočekávaná výjimka. * * @param string $message * @param array $context * @return null */ public function critical($message, array $context = array()); /** * Běhové chyby, které nevyžadují okamžitou akci, ale měly by být typicky * logovány a sledovány. * * @param string $message * @param array $context * @return null */ public function error($message, array $context = array()); /** * Výjimečné události, které nejsou chybami. * * Příklad: Použití zastaralého API, nesprávné použití API, nevhodné věci, * které nemusí být nutně špatně. * * @param string $message * @param array $context * @return null */ public function warning($message, array $context = array()); /** * Normální, ale podstatné události. * * @param string $message * @param array $context * @return null */ public function notice($message, array $context = array()); /** * Zajímavé události. * * Příklad: Uživatelská přihlášení, SQL logy. * * @param string $message * @param array $context * @return null */ public function info($message, array $context = array()); /** * Detailní ladící informace. * * @param string $message * @param array $context * @return null */ public function debug($message, array $context = array()); /** * Zaloguje s libovolnou úrovní. * * @param mixed $level * @param string $message * @param array $context * @return null */ public function log($level, $message, array $context = array()); }
Rozhraní Psr\Log\LoggerAwareInterface
<?php namespace Psr\Log; /** * Popisuje instanci, která používá logger */ interface LoggerAwareInterface { /** * Nastaví objektu instanci loggeru * * @param LoggerInterface $logger * @return null */ public function setLogger(LoggerInterface $logger); }
Třída Psr\Log\LogLevel
<?php namespace Psr\Log; /** * Popisuje logovací úrovně */ class LogLevel { const EMERGENCY = 'emergency'; const ALERT = 'alert'; const CRITICAL = 'critical'; const ERROR = 'error'; const WARNING = 'warning'; const NOTICE = 'notice'; const INFO = 'info'; const DEBUG = 'debug'; }
Ukázková implementace
Překlad specifikace je sice hezká věc, nicméně chtělo by to nějaký konkrétní příklad, že? Vytvořme si jednoduchý logger, který odpovídá specifikaci PSR-3 a následně si ukažme i jeho použití. V kódu níže použijeme rovnou i PSR-4, kterou máme v plánu na příště. Ta udává jakým způsobem pojmenovávat jmenné prostory, implementovat autoloader a jak členit třídy do složek. Tuto část kódu tedy plně pochytíte až příště.
Jako první si založíme nový projekt. Vytvoříme si v něm složku vendor, v ní podsložku Psr a v ní podsložku Log. Právě sem vložíme 3 soubory s výše uvedenými zdrojovými kódy: LoggerAwareInterface.php, LoggerInterface.php, LogLevel.php. Vendor je složka pro třídy, která se dále větví podle jejich výrobců (od toho název vendor). Rozhraní máme tedy připravená, teď je implementujeme.
Ve složce vendor vytvoříme podsložku ItNetwork (protože ItNetwork je jejich výrobce), kam přidáme 2 třídy:
Logger.php
Logger je samotná implementace našeho loggeru. Z kódu jsem odstranil
komentáře, aby byl článek kompaktnější, okomentovanou verzi si můžete
stáhnout v příloze. Kód je extrémně jednoduchý, každá z 8mi metod
zatím jen zapisuje řádku do textového souboru. Připsání řádky je
atomická operace, takže se nemusíme starat o lockování. Metoda log() potom
podle typu úrovně spustí danou metodu. Ty kritičtější metody by se v
praxi doplnily o nějaké odesílání emailů, SMSek, spouštění sirén a
podobně
namespace ItNetwork; use Psr\Log\LoggerInterface; use Psr\Log\LogLevel; class Logger implements LoggerInterface { const FILENAME = 'errors.log'; public function emergency($message, array $context = array()) { $this->appendLine(LogLevel::EMERGENCY, $message, $context); } public function alert($message, array $context = array()) { $this->appendLine(LogLevel::ALERT, $message, $context); } public function critical($message, array $context = array()) { $this->appendLine(LogLevel::CRITICAL, $message, $context); } public function error($message, array $context = array()) { $this->appendLine(LogLevel::ERROR, $message, $context); } public function warning($message, array $context = array()) { $this->appendLine(LogLevel::WARNING, $message, $context); } public function notice($message, array $context = array()) { $this->appendLine(LogLevel::NOTICE, $message, $context); } public function info($message, array $context = array()) { $this->appendLine(LogLevel::INFO, $message, $context); } public function debug($message, array $context = array()) { $this->appendLine(LogLevel::DEBUG, $message, $context); } private function interpolate($message, array $context = array()) { // vytvoří nahrazovací pole se závorkami okolo kontextových klíčů $replace = array(); foreach ($context as $key => $val) { $replace['{' . $key . '}'] = $val; } // interpoluje nahrazovací hodnoty do zprávy a vrátí je return strtr($message, $replace); } private function appendLine($level, $message, $context) { $message = $this->interpolate($message, $context); file_put_contents(self::FILENAME, $level . ': ' . $message . PHP_EOL, FILE_APPEND); } public function log($level, $message, array $context = array()) { switch ($level) { case LogLevel::EMERGENCY: $this->emergency($message, $context); break; case LogLevel::ALERT: $this->alert($message, $context); break; case LogLevel::CRITICAL: $this->critical($message, $context); break; case LogLevel::ERROR: $this->error($message, $context); break; case LogLevel::WARNING: $this->error($message, $context); break; case LogLevel::NOTICE: $this->notice($message, $context); break; case LogLevel::INFO: $this->info($message, $context); break; case LogLevel::DEBUG: $this->debug($message, $context); break; } } }
EmailSender.php
Následně vytvořme objekt, který logger používá. Půjde o jednoduchý odesílač emailů, který zaloguje notice pokud se email nepodaří odeslat. Objekt bude implementovat rozhraní LoggerAwareInterface.
namespace ItNetwork; use Psr\Log\LoggerAwareInterface; use Psr\Log\LoggerInterface; class EmailSender implements LoggerAwareInterface { private $logger; public function send($address, $subject, $message, $from) { $header = "From: " . $from; $header .= "\nMIME-Version: 1.0\n"; $header .= "Content-Type: text/html; charset=\"utf-8\"\n"; if (!mb_send_mail($address, $subject, $message, $header)) $this->logger->notice('Email na adresu {address} se nepodařilo odeslat.', array('address' => $address)); } public function setLogger(LoggerInterface $logger) { $this->logger = $logger; } }
No a konečně do kořenové složky (nad vendor) přidáme index.php, kam umístíme autoloader, vytvoříme Logger a EmailSender, EmailSenderu nastavíme Logger a pokusíme se odeslat email na nějakou neexistující adresu:
use ItNetwork\EmailSender; use ItNetwork\Logger; function autoloader($class) { $class = 'vendor\\' . $class; $path = str_replace('\\', '/', $class) . '.php'; if (!include($path)) throw new Exception('Autoloader Error'); // Vyhodíme výjimku, abychom zjistili kde nastala chyba } spl_autoload_register("autoloader"); $logger = new Logger(); $emailSender = new EmailSender(); $emailSender->setLogger($logger); $emailSender->send('neexistuje', 'Žádný', 'Ahoj, toto nikdy nebudeš číst.', '[email protected]');
Skript několikrát spustíme. Výsledkem je vytvoření souboru errors.txt, který má následující obsah:

Máme univerzální logger, který můžeme centrálně používat nejen ze
svých tříd, ale dokonce ho budou díky rozhraní Psr\Log\LoggerInterface
umět používat i komponenty třetích stran, které ho nikdy neviděly
Příští lekce, Standardy jazyka PHP - PSR-4 a autoloader, je věnována specifikaci PSR-4, která se týká autoloaderu.
Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.
Stáhnout
Stažením následujícího souboru souhlasíš s licenčními podmínkami
Staženo 99x (6.64 kB)
Aplikace je včetně zdrojových kódů v jazyce PHP