Fork me on GitHub

Nbml explanation

Learn

NetBricks Markup Language

Nbml jest językiem służącym do definiowania widoków w sposób obiektowy. Za jego pomocą w łatwy sposób możemy przekształcić widok w formacie phtml (html ze wstawkami w języku php), do klas php.

Wymagania systemowe

Biblioteka nbml wymaga PHP w wersji minimum 5.3

Tworzenie instancji

Aby rozpocząć pracę z plikami nbml należy zainicjalizować ViewAutoLoader. Klasa ta rejestruje php autoloader tak, aby obsługiwał plik *.nbml. Można to zrobić korzystając z dostarczonego sandboxa lub manualnie. Należy również zainicjalizować jakiś autoloader do klas biblioteki nbml, ponieważ nbml korzysta ze standardowego sposobu ładowania klas, ta czynność jest pozostawiona programiście. Przejdźmy najpierw jednak do prostszego wariantu - rozpoczęcia pracy z wykorzystaniem pliku sandbox instantiation-sandbox.

Tworzenie instancji z wykorzystaniem sandbox

Jest to najprostsza i zarazem najmniej elastyczna forma zainicjalizowania nbmla.

$viewAutoLoader = include 'library/Nbml/sandbox_runtime.php';

Tworzenie instancji metodą manualną

Możemy oczywiście zainicjalizować interpreter ręcznie. Dzięki temu możemy w pełni skorzystać z opcji konfiguracyjnych jakie dostarcza nam interpreter. Poniżej znajduje się przykładowa konfiguracja manualna.

plik index.php

<?php

use \Nbml\AutoLoader\ClassAutoLoader;
use \Nbml\AutoLoader\ViewAutoLoader;
use \Nbml\Compiler;

require_once __DIR__ . '/AutoLoader/ViewAutoLoader.php';
require_once __DIR__ . '/AutoLoader/ClassAutoLoader.php';

$classAutoLoader = new ClassAutoLoader();
$classAutoLoader
                ->addIncludePath(__DIR__ . '/../library')
                ->register();

$viewAutoLoader = new ViewAutoLoader();
$viewAutoLoader
                ->setCompilerDefaultDestinationDir(__DIR__ . '/../tmp')
                ->setAlwaysCompile(true)
                ->addIncludePath(__DIR__ . '/../library')
                ->register();

$viewCompiler = new Compiler();
$viewCompiler
      ->addTagProcessor('\Nbml\MetadataTag\PublicMetadataTag')
      ->addTagProcessor('\Nbml\MetadataTag\StateMetadataTag')
      ->addTagProcessor('\Nbml\MetadataTag\OnDemandMetadataTag')
      ->addTagProcessor('\Nbml\MetadataTag\OnStateMetadataTag')
      ->addTagProcessor('\Nbml\MetadataTag\CssMetadataTag')
      ->addTagProcessor('\Nbml\MetadataTag\JsMetadataTag');

$viewAutoLoader->setViewCompiler($viewCompiler);

Instalacja zakłada, że posiadasz taką strukturę katalogów:

  • public

    • index.php
  • library

    • Nbml
  • tmp

A teraz po kolei. Załączami pliki klas Autoloaderów (ViewAutoLoader oraz ClassAutoLoader). Następnie należy zainicjalizować ClassAutoLoader w celu poprawnego ładowania klas kompilatora Nbml.

$classAutoLoader = new ClassAutoLoader();
$classAutoLoader
        ->addIncludePath(__DIR__ . '/../library')
        ->register();

Teraz możemy bez obaw inicjalizować autoloader do klas *.nbml

$viewAutoLoader = new ViewAutoLoader();
$viewAutoLoader
        ->setCompilerDefaultDestinationDir(__DIR__ . '/../tmp')
        ->setAlwaysCompile(true)
        ->addIncludePath(__DIR__ . '/../library')
        ->register();

Powyższy skrypt zakłada, że pliki *.nbml znajdują się w folderze library, oraz że po kompilacji będą one umieszczane w folderze tmp. Upewnij się, że serwer www ma uprawnienia do zapisu w tym folderze. Autoloader jest ustawiony w ten sposób, że niezależnie od tego czy plik był zmieniony czy nie, będzie on kompilowany każdorazowo przy wywołaniu autoloadera. Jest to bardzo czasochłonne i w środowisku produkcyjnym należy to wyłączyć. Gdy opcja setAlwaysCompile zostanie ustawiona na false, kompilator będzie uruchamiany tylko w przypadku gdy czas ostatniej zmiany pliku *.nbml jest większy od czasu ostatniej zmiany wygenerowanego pliku *.php

Następnie pozostaje nam utworzenie instancji kompilatora i dodanie jej do autoloadera.

$viewCompiler = new Compiler();
$viewCompiler
    ->addTagProcessor('\Nbml\MetadataTag\PublicMetadataTag')
    ->addTagProcessor('\Nbml\MetadataTag\StateMetadataTag')
    ...
$viewAutoLoader->setViewCompiler($viewCompiler);

Operacja ta jest odseparowana od ViewAutoLoader w celu większej customizacji - możesz utworzyć własny kompilator, czy rozszerzyć istneiejący wedle uznania. Dołączane są standardowe metadata tagi. W tym miejscu możesz załączyć własne, lub wyłączyć nieużywane w celu poprawy czytelności kodu.

Tworzenie instancji z wykorzystaniem Composer (zalecana metoda)

Nbml może być znaleziony również w packagist co czyni korzystanie z composera o wiele prostszym. Zaprezentuję w jaki sposób możemy skorzystać z composera do założenia nowego projektu.

Załóżmy, że posiadamy taką strukturę katalogów / plików:

  • public

    • index.php
  • src

    • MyNamespace
  • tmp

  • composer.json

  • composer.phar

Treść naszego pliku composer.json powinna wyglądać następująco:

{
    "require":{
        "php":">=5.3.0",
        "finalclass/nbml":"dev-master"
    },
    "autoload":{
        "psr-0":{
            "MyNamespace": "src/"
        }
    }
}

gdzie MyNamespace jest twoją przestrzenią nazw - dowolnym łańcuchem znaków. Zwróć uwagę na blok require: "finalclass/nbml":"dev-master" - zakładam tutaj użycie wersji developerskiej pakietu nbml. Nie jest to zalecane rozwiązanie - wprowadź tutaj wersję najbardziej ci odpowiadającą. Gdy utworzymy i zapiszemy ten plik, należy uruchomić skrypt composer.phar będąc w katalogu z tworzonym projektem, w ten sposób:

php composer.phar install

Skrypt ten utworzy folder vendor oraz zapełni go zależnościami. W tym momencie należy zainicjalizować ViewAutoLoader.

Treść pliku index.php

$autoLoader = include '../vendor/autoload.php';
$viewAutoLoader = include '../src/init_view_auto_loader.php';

Jak widać jest tutaj zaincludowany plik autoloadera dołączony przez composer. Dzięki temu nie ma konieczności tworzenia instancji ClassAutoLoader dostarczonej przez Nbml. W index.php jest również includowany plik init_view_auto_loader.php który należy napisać samodzielnie. Poniżej znajduje się przykładowa jego zawartość:

Treść pliku /src/init_view_auto_loader.php:

<?php

use \Nbml\AutoLoader\ViewAutoLoader;
use \Nbml\Compiler;

$viewAutoLoader = new ViewAutoLoader();
$viewAutoLoader
            ->setCompilerDefaultDestinationDir(__DIR__ . '/../tmp')
            ->setAlwaysCompile(true)
            ->addIncludePath(__DIR__ . '/../src')
            ->register();

$viewCompiler = new Compiler();
$viewCompiler
            ->addTagProcessor('\Nbml\MetadataTag\PublicMetadataTag')
            ->addTagProcessor('\Nbml\MetadataTag\StateMetadataTag')
            ->addTagProcessor('\Nbml\MetadataTag\OnDemandMetadataTag')
            ->addTagProcessor('\Nbml\MetadataTag\OnStateMetadataTag')
            ->addTagProcessor('\Nbml\MetadataTag\CssMetadataTag')
            ->addTagProcessor('\Nbml\MetadataTag\JsMetadataTag');

$viewAutoLoader->setViewCompiler($viewCompiler);

return $viewAutoLoader;

Jest to identyczny plik jak w przypadku wersji manualnej [instantiation-manual] z tym, że nie ma tutaj tworzenia instancji ClassAutoLoader.

Jak to działa?

Koncepcja jest dość prosta. Piszemy pliki *.nbml które to są interpretowane do plików *.php. Konkretnie - do klas php. Pliki *.nbml są zwykłymi plikami html z przeplatanym php, czyli standardowymi plikami szablonów php (często rozszerzeniem takich plików jest *.phtml). Następnie tworzona jest z nich klasa. Schematycznie można to pokazać w ten sposób:

Załóżmy, że mamy taki oto plik *.nbml:

<?php /**
 * @var $this \Nbml\Component
 */ ?>
 Example component

Umieśćmy go w folderze MyNamespace\Example i nazwijmy Example.nbml (plik będzie tutaj: /MyNamespace/Example/Example.nbml)

Teraz gdzieś w pliku *.php wykonajmy następujące polecenie:

<?php
$exampleComponent = new \MyNamespace\Example();
echo $exampleComponent

W momencie użycia klasy \MyNamespace\Example zostanie pobrany plik MyNamespace/Example/Example.nbml, skompilowany do klasy \MyNamespace\Example oraz dołączony (require_once).

Schematycznie treść wygenerowanego pliku php będzie następująca:

<?php
namespace MyNamespace;
class Example extends \Nbml\Component
{

  public function __toString()
  {
    ?>
    <?php /**
     * @var $this \Nbml\Component
     */ ?>
     Example component

 <?php
  }
}

Oczywiście jest to tylko schemat działania, jeśli jesteś zainteresowany konkretną kompilacją, po prostu zobacz jak plik Example.php wygląda w rzeczywistości.

Dlaczego warto używać nbml?

Php jest językiem, który sam w sobie jest systemem szablonów. Pisanie stron internetowych wykorzystując czysty html+js+css jest najlepszym rozwiązaniem. Problem polega na tym że gdy odseparujesz widok od kontrolera w php to widoki nie są konkretne, to znaczy nie wiadomo jakie zmienne może dany szablon przyjmować. IDE nie jest w stanie podpowiedzieć, co można w danym szablonie wykonać. Koder nie wie, jakie zmienne programista zostawił mu do dyspozycji.

Z pomocą przychodzi nbml. Dzięki niemu dalej tworzymy szablony w html+php jednak tym razem kontrakt pomiędzy koderem a programistą jest jasny. Koder wie co ma do dyspozycji w danymi widoku, a programista jest szczęśliwy bo ma obiekt:) Nikt nie pomyli się w nazewnictwie zmiennych, gdyż IDE bez problemu parsuje wygenerowane klasy i podpowiada dostępne opcje jak i typy zmiennych.

Dzięki temu rozwiązaniu posiadasz w pełni obiektowy widok. Tworzysz drzewo lekkich komponentów. Tworzysz je jednak w htmlu - skinownanie jest załatwione.

To rozwiązanie jest kompletnie nieinwazyjne i można bez żadnych przeciwwskazań korzystać z nbmla, równolegle z innymi bibliotekami.

Hello World example

Jak to zostało przyjęte, pierwszym krokiem w nowym języku powinno być utworzenie programu Hello World:) nbml oczywiście stanął na wysokości zadania i takowy program wam prezentuje.

Zakładam taką strukturę katalogów:

  • library

    • Nbml
  • public

    • HelloWorld

      • HelloWorld.nbml
    • index.php

Skorzystamy z dostarczonego sandboxa w celu zainicjalizowania interpretera.

plik index.php

<?php
$viewAutoLoader = include '../library/Nbml/sandbox_runtime.php';
echo new HelloWorld();

plik HelloWorld.nbml

<?php /**
 * @var $this \Nbml\Component\Application
 */ ?>
Hello World!

To wszystko co należy zakodować. Teraz postaje uruchomić skrypt. Twoim oczom ukaże się strona www o takim kodzie źródłowym:

wygenerowany html

<!DOCTYPE>
<html>
<head>
    <title></title>
    <meta charset="utf-8"/>
    <META http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body>
Hello World!
</body>
</html>

Zwróć uwagę, że w folderze HelloWorld został utworzony nowy plik: HelloWorld.php - jest to skompilowana wersja pliku HelloWorld.nbml. Oto treść tego pliku:

wygenerowany php

<?php

class HelloWorld extends \Nbml\Component\Application
{

    static public $PATH = '/var/www/hello_world/HelloWorld/HelloWorld.nbml';

    /**
     * @return \HelloWorld
     */
    static public function create()
    {
        return new \HelloWorld();
    }

    protected $options = array();

    public function __construct($options = array())
    {
        parent::__construct($options);
    }


    /**
     * @return \HelloWorld
     */
    public function __invoke()
    {
        ob_start();

        ?><?php /**
     * @var $this \Nbml\Component\Application
     */
        ?>

    Hello World!
    <?php
        $this->text = ob_get_clean();
        return $this;
    }
}

Metadata Tags

Nbml wykorzystuje PHPDoc do definiowania obiektów. Przykładowo taka konstrukcja:

<?php /**
 * @var $this \Nbml\Component
 * @var $message string
 */ ?>

utworzy klasę dziedziczącą po \Nbml\Component z prywatną zmienną $message o typie string. W ten sposób możemy definiować pewne aspekty budowanego komponentu. Nbml posiada również mechanizm definiowania własnych tagów, które to zmieniają właściwości definiowanej zmiennej. W bibliotece standardowej mamy do dyspozycji następujące tagi: [Public], [State], [OnState], [OnDemand], [Css] oraz [Js].

Atrybuty Metadata tagów

Metadata tagi mogą przyjmować pewne atrybuty. Jeżeli dany metatag przyjmuje atrybuty, może również mieć zdefiniowany atrybut domyślny. Dla przykładu atrybut [State] może być parametryzowany przez atrybut name, który to jest atrybutem domyślnym. Zatem zapis:

* [OnState(name='selected')]

jest równoznaczny z zapisem:

* [OnState('selected')]

W przypadku gdy metadata tag przyjmuje więcej atrybutów, ich nazwy powinny być oddzielone przecinkami w ten sposób:

* [Tag(attr1='value1', attr2='value2', attr3='value3')]

Interpreter umożliwia również proste tworzenie własnych Metadata tagów. Proces ten opisany jest w tym miejscu Własne Metadata tagi

Metadata Tags - Public

Rozpatrzmy poniższy przykład:

plik HelloWorld.nbml

<?php /**
 * @var $this \Nbml\Component
 *
 * [Public] @var $message string('Hello World!')
 */ ?>
 <div class="center bold">
  <?=$message?>
 </div>

A następnie jego użycie:

<?php
$helloComponent = new HelloWorld();
echo $helloComponent->message(); //Hello World!
$helloComponent->message('Hello Internet!');
echo $helloComponent->message(); //Hello Internet!
echo $helloComponent; //<div class..../>

Użycie Metadata tagu [Public] powoduje utworzenie publicznego gettera oraz settera dla zmiennej prywatnej $message.

Metadata Tags - State

Metadata tag [State] powoduje dodanie stanu do danego komponentu. Zmienne zdefiniowane jako [State] powinny być typu boolean. Włączenie jednego stanu powoduje wyłączenie innych.

Rozpatrzmy poniższy przykładowy komponent przycisku:

Button.nbml

<?php /**
 * @var $this \Nbml\Component
 *
 * [State] @var $normalState boolean(true)
 * [State] @var $selectedState boolean(false)
 */ ?>

 <a href="/example" label="Example" class="<?=$selectedState ? 'selected' : ''?>">
 Example
 </a>

a następnie jego użycie:

<?php

$button = new Button();
var_dump($button->normalState()); //true
$button->selectedState(true);
var_dump($button->normalState()); //false
echo $button;

ustawienie zmiennej selectedState na true powoduje ustawienie innych zmiennych stanowych na false. Często korzysta się z metatagu [State] we współpracy z metatagiem [OnState]

Metadata Tags - OnState

Ustawienie na pewnym komponencie metatagu [OnState] powoduje ustawienie warunku na danej zmiennej. Zmienna zostanie zainicjalizowana tylko w przypadku, gdy komponent znajdujować się będzie w zadanym stanie. W przeciwnym wypadku jej wartość będzie pustym łańcuchem znaków.

Metadata tag [OnState] przyjmuje jeden argument name, określający nazwę stanu w którym dana zmienna ma się zainicjalizować. Argument name jest argumentem domyślnym więc nie ma konieczności pisać [OnState(name='stateName')], można skorzystać ze skróconej formy: [OnState('stateName')].

Rozpatrzmy przypadek przycisku:

Button.nbml

<?php /**
 * @var $this \Nbml\Component
 *
 * [State] @var $normalState boolean(true)
 * [State] @var $selectedState boolean(false)
 *
 * [OnState('selectedState')] @var $selectedClass string('selected')
 */ ?>
 <a href="/example"
    title="Example"
    class="<?=$selectedClass?>">
    Example

Powyższy przycisk będzie miał klasę selected tylko w przypadku gdy komponent Button będzie w stanie selectedState.

Tę właściwość można łatwo połączyć z właściwością [Public] i stworzyć customizowalny przycisk w ten sposób:

<?php /**
 * @var $this \Nbml\Component
 *
 * [State] @var $normalState boolean(true)
 * [State] @var $selectedState boolean(false)
 *
 * [OnState('selectedState')]
 * [Public]
 * @var $selectedClass string('selected')
 *
 * [Public] @var $href string('#')
 * [Public] @var $label string
 */ ?>
<a href="<?=$href?>"
   title="<?=$label?>"
   class="<?=$selectedClass?>">
    <?=$label?>
</a>

W tym przypadku sami możemy określić jaka klasa powinna być użyta dla stanu selectedState.

Metadata Tags - OnDemand

Ten znacznik powoduje utworzenie w miejsce zadanej zmiennej obiektu \Nbml\MetadataTag\OnDemandMetadataTag\OnDemandFactory. Powyższa klasa tworzy zadaną zmienną w momencie jej użycia (w momencie wywołania metod: __toString(), __invoke(), __call(), __set() lub __get())

<?php /**
 * @var $this \Nbml\Component
 *
 * [OnDemand]
 * @var $subComponent \Nbml\Component
 */ ?>

 <div class="example">
  <?php if(rand(0, 9) > 4): ?>
    <?=$subComponent?>
  <?php endif; ?>
 </div>

W powyższym przykładzie w miejsce $subComponent zostaje utworzona klasa OnDemandFactory, a następnie (tylko gdy rand(0, 9) > 4) OnDemandFactory w momencie __toString() tworzy obiekt klasy \Nbml\Component jak to było zadane w PHPDoc.

Powyższy przykład ma za cel zaprezentowanie koncepcji działania znacznika [OnDemand], jest on jednak bezużyteczny. [OnDemand] sprawdza się idealnie przy “ciężkich” komponentach których inicjalizacja wiąże się z dużym obciążeniem systemu.

Metadata Tags - Css

Metadata tag [Css] służy do ładowania plików css. Nbml dostarcza podstawową wersję komponentu który jest dokumentem html: \Nbml\Component\Application. Można tę wersję rozbudować według własnych potrzeb. Należy zwrócić uwagę, że nbml nie umożliwia ręcznego umieszczania plików css i js (można to zrobić, jednak nie jest to wskazane). Zamiast tego zaleca się tworzenie głównego komponentu tak, aby dziedziczył po \Nbml\Component\Application. Komponent Application posiada zmienną styleSheets która to jest typu \Nbml\Collection. Można do niej dodawać pliki styli na przykład w ten sposób:

\Nbml\Component\Application::getInstance()->styleSheets()->add('/css/styles.css')

Dokładnie taką operację wykonuje znacznik [Css]. Jako, że Application::getInstance() zwraca zawsze ostatnio utworzoną instancję (za pomocą new) to w momencie tworzenia mamy właśnie do niej dostęp. W systemie nie powinna być tworzona kolejna instancja Application. Wysyłamy do klienta tylko jedną strone HTML! Oczywiście mogą się pojawić wyjątki (chociaż ciężko mi wymyśleć jakieś). W takim przypadku należy pamiętać o tym, że Application::getInstance() zwraca zawsze ostatnio utworzoną instancję.

Atrybuty

Znacznik [Css] posiada jeden atrybut: file. Jest on atrybutem domyślnym, zatem konstrukcja [Css(file="/css/styles.css")] jest jednoznaczna z [Css("/css/styles.css")]

Ścieżki względne

Znacznik [Css] umożliwia wprowadzanie ścieżek względnych. Gdy, dla przykładu, tworzymy plik podając ścieżke względną do pliku css:

public/Example/MyComponent/MyComponent.nbml

<?php /**
 * [Css('myComponent.css')]
 * @var $this \Nbml\Component
 */ ?>
Component content...

to ścieżka względna zostanie rozwinięta w ten sposób: <link href="/Example/MyComponent/myComponent.css" .../>

Gdy zdefiniujemy ścieżkę jako ścieżkę bezwzględną: [Css('/myComponent.css')] to pozostanie ona niezmieniona: <link href="/myComponent.css" .../>.

W przypadku pracy z plikami zdalnymi należy pamiętać o wstawieniu HTTP lub HTTPS przy definiowaniu lokalizacji pliku css tak, aby ścieżka nie została rozwinięta do ścieżki bezwzględnej - system musi wiedzieć, że chodzi o plik zdalny. Dla przykładu ścieżka tego typu: [Css('example.com/file.css')] użyta w powyższym komponencie zostanie rozwinięta do: /Example/MyComponent/example.com/file.css. Poprawną formą powinno być podanie http na początku w ten sposób: [Css('http://example.com/file.css')]

Ta sama zasada tyczy się znacznika [Js]

Przypisanie znacznika do zmiennej

Ten znacznik nie jest związany ze zmienną do której jest przypisany, jednak powinien być przypisany do jakiejś zmiennej.

Poniżej znajduje się błędna konstrukcja:

Błędny plik!!!

<?php /**
 * @var $this \Nbml\Component\Application
 *
 * [Css('/css/styles.css')]
 */ ?>
 Component content...

Poprawnie ten komponent powinien być zbudowany w ten sposób:

<?php /**
 * [Css('/css/styles.css')]
 * @var $this \Nbml\Component\Application
 */ ?>
 Component content...

Czyli metadata tag [Css] powinien być przypisany do jakiejś zmiennej. Najlepiej przypisać tego typu metadata tagi do zmiennej $this. Jest to metoda zalecana. Podobnie działa metadata tag [Js].

Metadata Tags - Js

Działanie znacznika [Js] jest identyczne z działaniem znacznika [Css] z tą różnicą, że odpowiada on za dodawanie plików JavaScript. Powoduje on dodanie następującej linijki w konstruktorze komponentu:

\Nbml\Component\Application::getInstance()->scripts()->add('file.js');

Custom Metadata tags

Silnik nbml umożliwia definiowanie jakie metadata tagi mają być brane pod uwagę podczas interpretowania komponentów. Inicjalizując kompilator dokonujesz tego wyboru. Przejdź do sekcji Tworzenie instancjiInstantiation aby zgłębić ten proces.

Klasa \Nbml\Compiler posiada metodę ->addTagProcessor($className) która to umożliwia dodawanie własnych znaczników. Przykładowo inicjalizacja Metadata tagów dołączonych z biblioteką standardową wygląda w ten sposób:

$viewCompiler = new Compiler();
$viewCompiler
      ->addTagProcessor('\Nbml\MetadataTag\PublicMetadataTag')
      ->addTagProcessor('\Nbml\MetadataTag\StateMetadataTag')
      ->addTagProcessor('\Nbml\MetadataTag\OnDemandMetadataTag')
      ->addTagProcessor('\Nbml\MetadataTag\OnStateMetadataTag')
      ->addTagProcessor('\Nbml\MetadataTag\CssMetadataTag')
      ->addTagProcessor('\Nbml\MetadataTag\JsMetadataTag');

Aby stworzyć własny znacznik musisz stworzyć klasę implementującą interfejs \Nbml\MetadataTag:

namespace Nbml;
use \Nbml\Reflector\Variable;
use \Nbml\Reflector\MetadataTagDefinition;
use \Nbml\Reflector;

interface MetadataTag
{
    static function getMetadataTagName();
    function __construct(Variable $variable, MetadataTagDefinition $definition, Reflector $classReflection);
    function hasGetter();
    function getGetterMethodDefinition();
    function hasSetter();
    function getSetterMethodDefinition();
    function hasInitializationCode();
    function getInitializationCode();
    function hasBeforeRenderRetrievalCode();
    function getBeforeRenderRetrieveCode();
    function getRequiredProperties();
    function hasDefaultProperty();
    function getDefaultPropertyName();
}

Z pomocą przychodzi klasa \Nbml\MetadataTag\AbstractMetadataTag która to implementuje większość metod interfejsu \Nbml\MetadataTag. Zaleca się rozszerzanie tej klasy w celu tworzenia nowych procesorów znaczników.

Przykładowy Metadata tag

Załóżmy, że chcemy zbudować własny metadata tag [Hello] który to dodawałby do zmiennej do której jest przypisany, łańcuch znaków: “Hello ”. Oto treść tego znacznika:

Treść takiego procesora metadata tagów była by następująca:

<?php

class HelloMetadataTag extends \Nbml\MetadataTag\AbstractMetadataTag
{
    static function getMetadataTagName()
    {
        return 'Hello';
    }

    public function getInitializationCode()
    {
        $nameUnderscored = $this->variable->getNameUnderscored();
        $default = $this->variable->getDefaultValue();
        return '$this->options[\'' . $nameUnderscored . '\'] = '
            . '\'Hello \' . ' .  $default . ';';
    }
}

Aby można było go używać należy go dodać do kompilatora w ten sposób:

$viewCompiler->addTagProcessor('\HelloMetadataTag')

Od tej pory można już korzystać z metadata tagu [Hello]

HelloComponent.nbml

<?php /**
 * @var $this \Nbml\Component
 *
 * [Hello] @var $miwa string('Miwa')
 */ ?>

 <?=$miwa?>

Wywołanie tego komponentu:

<?php
echo new HelloComponent(); //Hello Miwa

Pisząc komponenty mamy dostęp do zmiennej $this->variable oraz $this->definition. Zmienna $this->variable jest typu \Nbml\Reflector\Variable i jest w niej pełen opis zmiennej do kórej dany metadata tag jest przypisany.

Natomiast zmienna $this->definition jest definicją danego metadata tagu (w tym przypadku definicją meta tagu Hello). Jest ona typu: \Nbml\Reflector\MetadataTagDefinition. W tym obiekcie znajdziemy wszystkie dane o atrybutach (i ich wartościach) danego metadata tagu.

Czasem budując pewien metadata tag może powstać konieczność ingerencji w całą budowaną klasę komponentu. Z pomocą przychodzi nam właściwość $this->classReflection która to jest typu \Nbml\Reflector.

Zwróć jeszcze uwagę na tę linię budowanego metadata tagu [Hello]:

return '$this->options[\'' . $nameUnderscored . '\'] = '
            . '\'Hello \' . ' .  $default . ';';

Możesz się zastanawiać skąd wiadomo w jaki sposób taką linię napisać? Otóż to zależy od ciebie. Od ciebie zależy po czym twoje komponenty będą dziedziczyć. Domyślnie jest to klasa \Nbml\Component, jej znajomośc jest tutaj kluczowa. Rzućmy okiem na jej treść:

namespace Nbml;

class Component
{
    protected $text = '';
    protected $options = array();
    public function __construct($options = array())
    {
        $this->options = array_merge($this->options, $options);
    }

    public function __toString()
    {
        try {
            $this->__invoke();
        } catch(\Exception $e) {
            trigger_error((string)$e);
        }
        return $this->text;
    }

    public function __invoke()
    {
        return '';
    }
}

Jak widać jest to bardzo krótka, lekka klasa. Gotowa do rozbudowywania. Pisząc komponenty interpreter nadpisuje funkcję __invoke(), a rolą funkcji __invoke() jest zapełnienie zmiennej $this->text.

Pozostaje mi już tylko pozostawić ciebie twojej wyobraźni:)