5. díl - Standardy jazyka PHP - Implementace PSR-3

PHP Standardy Standardy jazyka PHP - Implementace PSR-3

V minulém dílu našeho seriálu o standardech jazyka PHP jsme si představili základní myšlenky specifikace PSR-3, která se zabývá logováním. Dnešní díl bude věnován její implementaci.

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\Logge­rInterface

<?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\Logge­rAwareInterfa­ce

<?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: LoggerAwareIn­terface.php, LoggerInterfa­ce.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í LoggerAwareIn­terface.

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.', 'jan@novak.cz');

Skript několikrát spustíme. Výsledkem je vytvoření souboru errors.txt, který má následující obsah:

Logování podle specifikace PSR-3 v PHP

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\Logge­rInterface umět používat i komponenty třetích stran, které ho nikdy neviděly :)

Příští díl je věnován specifikaci PSR-4, která se týká autoloaderu.


 

Stáhnout

Staženo 56x (6.64 kB)
Aplikace je včetně zdrojových kódů v jazyce PHP

 

  Aktivity (1)

Článek pro vás napsal David Čápka
Avatar
Autor pracuje jako softwarový architekt a pedagog na projektu ITnetwork.cz (a jeho zahraničních verzích). Velmi si váží svobody podnikání v naší zemi a věří, že když se člověk neštítí práce, tak dokáže úplně cokoli.
Unicorn College Autor se informační technologie naučil na Unicorn College - prestižní soukromé vysoké škole IT a ekonomie.

Jak se ti líbí článek?
Celkem (4 hlasů) :
4.754.754.754.754.75


 


Miniatura
Všechny články v sekci
Standardy jazyka PHP

 

 

Komentáře

Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zatím nikdo nevložil komentář - buď první!