OPT 1.x wprawdzie umożliwiał korzystanie z obiektów po stronie szablonów, jednak temat ten był potraktowany nieco po macoszemu. Właściwie jedyne, co można było robić, to odwoływać się do metod i pól już istniejących i dodanych do parsera jako bloki obiektów. Otrzymałem kilka pytań od użytkowników, czy nie dałoby się tego rozszerzyć. Szczególne zainteresowanie padło na możliwość wykorzystania statycznych elementów i dlatego w OPT2 będzie to udostępnione.
Pierwsza istotna rzecz to zupełna zmiana funkcji stosowanych dotąd operatorów. Wszyscy pewnie pamiętają, że dotąd :: służył do łączenia ciągów, zaś obiektówka korzystała na wzór PHP z ->. Z powodu przejścia na XML, jego obecność stanęła jednak pod znakiem zapytania z powodu występowania w nim symbolu >, który trzeba by było zapisywać encją. Z drugiej strony, ciągi przejęła tylda, tak więc zwolnił się paamayim nekudotayim (hebr. podwójny dwukropek), a żeby było ciekawiej, okazało się, że może on służyć jednocześnie obiektom, jak i statycznym elementom klas - zbiory kontekstów użycia są rozłączne. Przyjrzyjmy się więc bliżej.
$object::field
Jest to odwołanie do zwykłego pola w obiekcie $object.
$object::method()
Jest to wywołanie metody w obiekcie $object.
$object::method()::field
Jest to odwołanie do pola z obiektu zwróconego przez metodę w obiekcie $object. Analogicznie można zrobić z polami, czyli mamy tutaj identyczną swobodę, jak w PHP.
class::field class::method()
Tutaj z kolei odwołujemy się do elementów statycznych. Ważna informacja: klasa musi być uprzednio zarejestrowana w OPT przez programistę, aby kompilator dopuścił jej użycie. Dalsze wywołania idą jako odwołania do obiektów.
Wspomniałem wcześniej, że zbiory kontekstów użycia są rozłączne. Jest to prawda, ale tylko do pewnego czasu. Już niebawem w PHP pojawią się przestrzenie nazw i całe to rozumowanie, mówiąc obrazowo, trafi szlag. Pierwsze rozwiązanie problemu jest dość proste i korzysta z faktu istnienia obowiązku rejestracji tego typu rzeczy. Załóżmy, że mamy klasę c w przestrzeni a::b. Rejestrujemy ją jako a::b::c. Kompilator idzie sobie takim ciągiem tokenów i każdy napotkany identyfikator wpycha do bufora. Gdy dojdzie do miejsca, które pozwala bezwzględnie stwierdzić, że to już koniec (np. wywołanie metody, albo operator), funkcja implode() łączy bufor w ciąg i sprawdza w rejestrze, czy taki ciąg został zarejestrowany. Jeżeli tak, mamy odwołanie do klasy statycznej w jakiejś przestrzeni i możemy to pchnąć na wyjście, jak leci. Dla wyniku negatywnego sprawdzamy jedynie obecność pierwszego elementu (klasa statyczna?). Gdy istnieje, w przejście między pierwszym, a drugim dajemy ::, w pozostałe -> (pola obiektów). Gdyby i tu coś poszło nie tak, wyrzucamy wyjątek. Można to też rozszerzyć na funkcje.
Tak czy siak, sprawy przestrzeni nazw jeszcze nie rozwiązałem, ale myślę nad problemem i na pewno wersja finalna będzie potrafiła je obsługiwać.
W OPT2 pojawi się jeszcze możliwość tworzenia oraz klonowania obiektów, aczkolwiek programista odpowiednią dyrektywą będzie mógł to wyłączyć, gdyby z jakiegoś powodu uznał to za niebezpieczne. O ile klonowanie nie różni się specjalnie od tego, co znamy z PHP, o tyle dla new wprowadziłem dwa ograniczenia.
- Klasa musi być zarejestrowana.
- Nie można pobierać nazwy klasy ze zmiennych, np. new $klasa.
Podyktowane jest to wieloma względami. Zespół programistów może tak elegancko kontrolować poczynania osób odpowiedzialnych za warstwę wizualną, aby nie bawiły się w bogów i nie generowały błędów poprzez korzystanie z klas, z których nie powinny korzystać. Ponadto załóżmy, że ktoś zrobił np. serwis blogowy i dał użytkownikom możliwość edycji szablonów. Dzięki rejestracji może wyraźnie powiedzieć, co jest dozwolone, a co nie. Same operatory jeszcze nikomu nie zaszkodziły, a z mądrze dobranym zestawem klas/funkcji amator hax0rstwa raczej za wiele nie zdziała. Jest to też jeden z argumentów, dla których OPT w ogóle bawi się w sprawdzanie składni wyrażeń. Oprócz możliwości rozumienia kontekstu wystąpienia, ułatwia też wyszukiwanie błędów i chroni przed użyciem niedozwolonych struktur (pod warunkiem, że nie będzie w nim jakiejś dziury). Aktualnie kod odwalający tę robotę stanowi ponad 1/3 pliku kompilatora i ma znacznie większe możliwości w porównaniu do poprzedniej wersji OPT. Znacząco poprawiłem obsługę wyrażeń z nawiasami, które teraz przetwarzane są rekurencyjnie jako podwyrażenia. Daje to gwarancję, że poprawne wyrażenie po umieszczeniu jako np. parametr funkcji będzie działać dokładnie tak samo i nie będzie jakichś tajemniczych oddziaływań z tego powodu.
OPT posiada już także zakodowaną pierwszą instrukcję, właśnie wspomniany na początku opt:if. Poniżej zamieszczam demonstrację składni.
<opt:if test="$rand == 1"> <p>The random is true</p> <opt:elseif test="$rand eq 0" /> <p>The random is false</p> <opt:else/> <p>The random is super-true</p> </opt:if>
elseif oraz else są wprawdzie rozpoznawane jako samodzielne znaczniki, ale bez obaw. Do procesora instrukcji dodałem sprawdzanie rodzica i szablon po prostu się nie skompiluje, jeśli w hierarchii pomiędzy if, a else pojawi się jakiś inny znacznik. Po prostu oba muszą być bezpośrednimi dziećmi ifa i koniec, inaczej nie zadziałają :). Ponieważ kod jest krótki, demonstruję także źródła procesora (GNU LGPL 2 dla jasności):
class optIf extends optInstruction { public function configure() { $this -> addInstructions(array('opt:if', 'opt:elseif', 'opt:else')); $this -> addAttributes('opt:if'); } // end configure(); public function processNode(optNode $node) { $config = array( 'test' => array(0 => OPT_REQUIRED, OPT_EXPRESSION) ); switch($node -> getName()) { case 'if': $this -> extractAttributes($node, $config); $node -> addCode(TAG_BEFORE, ' if('.$config['test'].'){ '); $node -> addCode(TAG_AFTER, ' } '); $this -> processNodeContent($node); break; case 'elseif': if($node->getParent()->getName() == 'if') { $this -> extractAttributes($node, $config); $node -> addCode(TAG_BEFORE, ' } elseif('.$config['test'].'){ '); } else { $this -> tpl -> error(E_USER_ERROR, 'Invalid use of "'.$node->getXmlName().'". The parent must be "opt:if".', OPT_E_INVALID_USE); } break; case 'else': if($node->getParent()->getName() == 'if') { $node -> addCode(TAG_BEFORE, '}else{ '); } else { $this -> tpl -> error(E_USER_ERROR, 'Invalid use of "'.$node->getXmlName().'". The parent must be "opt:if".', OPT_E_INVALID_USE); } break; } } // end processNode(); public function processAttribute(optNode $node, optAttribute $attr) { $expr = $this -> compiler -> compileExpression((string)$attr, false); $node -> addCode(TAG_BEFORE, ' if('.$expr[0].'){ '); $node -> addCode(TAG_AFTER, ' } '); $this -> processNodeContent($node); } // end processNode(); } // end optIf;
Nie wiem, jak Wam, ale mi nowe API podoba się bardziej niż stare i w sumie wiem, co mówię - w końcu zakodowałem w nim 40 KB instrukcji dla OPT 1.x :).
Tak przy okazji, rodzina bibliotek Open Power będzie miała nowe loga. Są one już gotowe, ale zostaną opublikowane dopiero za jakiś czas - najprawdopodobniej wraz z uruchomieniem zupełnie nowej strony, do której także mam już szatę graficzną. Start będzie po sesji, czyli gdzieś w lutym.














