Vydělávej až 160.000 Kč měsíčně! Akreditované rekvalifikační kurzy s garancí práce od 0 Kč. Více informací.
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

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\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.', '[email protected]');

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 - Standardy jazyka 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í 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

 

Předchozí článek
Standardy jazyka PHP - PSR-3 a specifikace loggeru
Všechny články v sekci
Standardy jazyka PHP
Přeskočit článek
(nedoporučujeme)
Standardy jazyka PHP - PSR-4 a autoloader
Článek pro vás napsal David Hartinger
Avatar
Uživatelské hodnocení:
6 hlasů
David je zakladatelem ITnetwork a programování se profesionálně věnuje 15 let. Má rád Nirvanu, nemovitosti a svobodu podnikání.
Unicorn university David se informační technologie naučil na Unicorn University - prestižní soukromé vysoké škole IT a ekonomie.
Aktivity