¡Hola! My name is

Martin Zlámal

← back to the archive

Fluent interface a PCRE

Na následujících řádcích předvedu dvě věci. První je úžasný nápad jak vytvářet regulární výrazy pomocí fluent zápisu (inspirace .{target:_blank}), což je druhá věc o které bych se rád zmínil.

Regulární výrazy jsou peklo #

Ačkoliv znám pár lidí, které regulární výrazy umí, je jich opravdu pár. A nikdo z nich o sobě neřekne, že je umí. Následuje příklad velmi triviálního výrazu, který je ovšem dosti špatný, což je dobře, protože se k tomu vrátím později:

/^(http)(s)?(\:\/\/)(www\.)?([^ ]*)(\.)([^ ]*)(\/)?$/

Tento výraz akceptuje přibližně tvar URL. Je však zřejmé, že je to zápis, který je nesmírně náročný na vymyšlení a extrémně náchylný ke tvoření chyb. Proto je vhodné si jeho tvorbu zjednodušit například nějakou třídou:

<?php

class Regexp {

    private $regexp = '';

    public function has($value) {
        $this->regexp .= "(" . preg_quote($value, '/') . ")";
        //return $this;   -   potřebné pro fluent interface
    }

    public function maybe($value) {
        $this->regexp .= "(" . preg_quote($value, '/') . ")?";
        //return $this;   -   potřebné pro fluent interface
    }

    public function anythingBut($value) {
        $this->regexp .= "([^" . preg_quote($value, '/') . "]*)";
        //return $this;   -   potřebné pro fluent interface
    }

    public function __toString() {
        return "/^$this->regexp$/";
    }

}

S tím, že její použití je prosté:

$regexp = new Regexp();
$regexp->then('http');
$regexp->maybe('s');
$regexp->then('://');
$regexp->maybe('www.');
$regexp->anythingBut(' ');
$regexp->then('.');
$regexp->anythingBut(' ');
$regexp->maybe('/');
echo $regexp . '<br>';
echo preg_match($regexp, 'http://zlml.cz/') ? 'P' : 'F';
echo preg_match($regexp, 'https://zlml.cz/') ? 'P' : 'F';

Nemusím však říkat, že to minimálně vypadá naprosto otřesně. Spousta psaní, až moc objektové chování. Elegantnější řešení přináší právě fluent interface.

Fluent interfaces, regulární peklo chladne #

Fluent interface je způsob jak řetězit metody za sebe. Používá se poměrně často, ušetří spoustu zbytečného psaní a velmi prospívá srozumitelnosti kódu. Nevýhodou je, že se musí v každé metodě vrátit objekt return $this;, na což se nesmí zapomenout. Každopádně výsledek je skvostný:

$regexp = new Regexp();
$regexp->then('http')
        ->maybe('s')
        ->then('://')
        ->maybe('www.')
        ->anythingBut(' ')
        ->then('.')
        ->anythingBut(' ')
        ->maybe('/');
echo $regexp . '<br>';
echo preg_match($regexp, 'http://zlml.cz/') ? 'P' : 'F';
echo preg_match($regexp, 'https://zlml.cz/') ? 'P' : 'F';

Teprve zde vynikne to, jak je důležité správně (čti stručně a jasně) pojmenovávat metody. Díky fluent interfaces lze programovat téměř ve větách, které jsou naprosto srozumitelné.

Ne, peklo je opět peklem #

Ačkoliv by se mohlo zdát, že díky objektu, který pomáhá tvořit regulární výrazy je jejich kompozice jednoduchou záležitostí, není tomu tak. Vrátím se k původnímu výrazu, který není dobrý. Proč? V reálném světě je kontrola, resp. předpis, který musí daná adresa mít daleko složitější. Například http nemusí být vůbec přítomno, pokud však je, musí následovat možná s a zcela určitě ://. To samé s doménou. Ta může být jen určitý počet znaků dlouhá, může obsahovat tečky (ale ne neomezené množství), samotná TLD má také určitá pravidla (minimálně co se týče délky) a to nemluvím o parametrech za adresou, které jsou téměř bez limitu.

Zkuste si takový objekt napsat. Ve výsledku se i nadále budou regulární výrazy psát ručně, nebo se ve složitějších případech vůbec používat nebudou.

Do you have any comments? That's great! Tweet them so everyone can hear you…

← back to the archive