Estendere Blade - Switch

Vediamo come realizzare una direttiva di Blade, estendendo il sistema base già presente in Laravel.
francesco
Francesco Malatesta
07/03/2014 in Tutorial

Qualche giorno fa, lavorando ad alcuni progetti, mi sono ritrovato ad avere la necessità di usare uno switch in alcune view.

Ho dato uno sguardo alla documentazione ufficiale: niente. Ho fatto così una ricerca leggermente più approfondita, per trovare questa request di qualche mese fa. Con una risposta abbastanza lapidaria, Taylor liquidava temporaneamente la cosa con un "Not going to implement at this time".

Più chiaro di così...

Certo, prima di continuare c'è qualche osservazione da fare: non sempre lo switch è sinonimo di codice elegante. Ricordando uno dei primi capitoli di "Laravel Testing Decoded", di Jeffrey Way, quando ci sono troppi switch la cosa comincia a puzzare. C'è poco isolamento delle responsabilità, e così via.

Ad ogni modo nella vita di tutti i giorni, magari per una demo o per un qualcosa da realizzare velocemente, uno switch può sempre servire. Inoltre, da un punto di vista personale estetico, preferisco uno switch ad un if (else if, else if...), else.

Detto questo, vediamo come implementare lo switch per Blade.

Estendere Blade

Personalizzare Blade per i propri bisogni è davvero semplice. Come probabilmente già sai, Laravel "compila" le view realizzate con Blade in view elaborate e pronte ad essere usate.

Per creare un'estensione basta lavorare con il metodo Blade::extend().

Blade::extend(function($view, $compiler)
{
    // la view contiene il codice, appunto, della view
    return $view;
});

Il meccanismo di base è molto semplice. Il metodo extend prende in input una closure, che a sua volta presenta due argomenti: il primo, $view, è il codice della $view attualmente in fase di elaborazione. L'oggetto $compiler, invece, è il compilatore di Blade.

Su $view si può fare ogni tipo di operazione del caso, da un semplice _str_replace _fino al preg_replace.

La closure deve ritornare l'oggetto $view elaborato.

Lo switch, questo sconosciuto

La base di partenza per "costruire" il nostro switch è sicuramente la sua pagina sulla documentazione ufficiale. La sintassi che userò sarà quella alternativa:

<?php
    switch ($i):
        case 0:
            echo "i equals 0";
            break;
        case 1:
            echo "i equals 1";
            break;
        case 2:
            echo "i equals 2";
            break;
        default:
            echo "i is not equal to 0, 1 or 2";
    endswitch;
?>

Non c'è un motivo particolare per questa scelta: preferenza personale e nulla di più. La cosa si può fare tranquillamente anche con la sintassi più tradizionale, con le parentesi graffe.

Dando un'occhiata alla pagina sulla sintassi alternativa delle strutture di controllo, inoltre, veniamo a sapere che un costrutto del genere

<?php switch($variable): ?>
<?php case 1: ?>

Non funzionerebbe. Lo switch ed il suo primo case, infatti, devono trovarsi nello stesso blocco.

Occorre qualcosa del genere:

<?php switch($variable): 
case 1: ?>

Un elemento da tenere a mente.

Scrittura del codice

L'obiettivo è creare un costrutto per Blade di questo tipo:

<p>Value:</p>
@switch($value)
    @case(1)
        <p>Il valore è uno.</p>
    @break

    @case(2)
        <p>Il valore è due.</p>
    @break

    @default
        <p>Il valore non è uno, ma neanche due.</p>
    @break
@endswitch

I "pezzi" del nostro switch, quindi, saranno:

  • Il blocco @switch (più il primo @case);
  • Il singolo @case;
  • Il @break;
  • Il @default;
  • L'@endswitch conclusivo;

Con le idee chiare in testa possiamo scrivere il codice:

Blade::extend(function($value, $compiler)
{
    $value = preg_replace('/(?<=\s)@switch\((.*)\)(\s*)@case\((.*)\)(?=\s)/', '<!--?php switch($1):$2case $3: ?-->', $value);
    $value = preg_replace('/(?<=\s)@endswitch(?=\s)/', '<!--?php endswitch; ?-->', $value);

    $value = preg_replace('/(?<=\s)@case\((.*)\)(?=\s)/', '<!--?php case $1: ?-->', $value);
    $value = preg_replace('/(?<=\s)@default(?=\s)/', '<!--?php default: ?-->', $value);
    $value = preg_replace('/(?<=\s)@break(?=\s)/', '<!--?php break; ?-->', $value);

    return $value;
});

Et voilà!

Per concludere, ecco alcune osservazioni riguardo possibili domande che ci si può porre:

  • Non ho usato l'oggetto $compiler perché ho preferito lavorare direttamente sulla $view, senza passare per un altro oggetto. Ho voluto fare tutto tramite preg_replace, direttamente;
  • I vari blocchi (\s*) sono stati messi ai loro posti per rilevare eventuali spazi e ritorni a capo. In questo modo il codice rimane ordinato anche quando compilato da Blade a PHP vero e proprio;
  • Ogni miglioramento è ben accetto. Se hai qualche suggerimento lascia pure un commento e ne parliamo insieme!

Edit: Grazie a kylekatarnis su Github per il feedback.