Autor wpisu: batman, dodany: 31.07.2010 19:37, tagi: php
Dzisiaj prawdziwa gratka dla programistów PHP – PHP5. Zaawansowane programowanie. Nie owijając w bawełnę – książkę otrzyma osoba, która jako pierwsza poda datę, moich imienin. Powodzenia.
Dzisiaj prawdziwa gratka dla programistów PHP – PHP5. Zaawansowane programowanie. Nie owijając w bawełnę – książkę otrzyma osoba, która jako pierwsza poda datę, moich imienin. Powodzenia.
W jednym z niedawnych wpisów na internalsach, Felipe Pena przedstawił propozycję wprowadzenia typowania zwracanych zmiennych przez funkcje. Jest to funkcjonalność którą znamy z języków statycznie typowanych, gdzie każda funkcja musi mieć zdefiniowany typ zwracanej zmiennej. Propozycja opublikowana jako RFC zawiera patch, który każdy może przetestować u siebie, jeżeli wie jak kompilować php ze źródeł. Patch jest kompatybilny ze znajdującym się w trunku patchem dotyczącym typowania argumentów funkcji (dostępne są pseudotypy scalar i self bodajże).
Przykład (skopiowany z RFC):
function scalar abc($x = NULL) { return $x; } var_dump(abc(1)); // int(1) var_dump(abc(1.)); // float(1) var_dump(abc()); /* PHP Catchable fatal error: The returned value must be of the type scalar, called in ... on line 9 and returning in ... on line 4 */
Jak dla mnie sam feature może być, natomiast składnia nie do końca mi się podoba, wg mnie jest mało czytelna, wolałbym coś a’la ActionScript3:
function foo($arg):bar { return $arg+1; }
A Wy co sądzicie o tym ficzerze ?
W poprzednim artykule z serii Domain Driven Design przedstawiłem pole zastosowań metodologii DDD, jej otoczkę w postaci zarządzania projektem oraz język domeny (DSL). W dzisiejszym wpisie chciałbym natomiast przedstawić metodologię DDD w sposób bliższy implementacji. Tak jak może już wspomniałem w poprzednim wpisie, metodologia DDD odnosi się do warstwy modelu w paradygmacie MVC. Natomiast wewnątrz tej warstwy modelu w MVC, DDD wprowadza rozdział na warstwę domenową (właściwy model) i warstwę infrastruktury. Dodatkowo istnieje kilka rodzajów obiektów, które mogą występować w obu warstwach:
Warstwa domenowa odpowiada naszemu modelowanemu zagadnieniu a więc opierając się na poprzednim przykładzie hurtowni internetowej będą to obiekty takie jak Kontrahent, Przedstawiciel Handlowy, Faktura, Zamówienie, Oferta Handlowa etc.
Warstwa infrastruktury odpowiada za wszelkie „techniczne” działania jakie są potrzebne by warstwa domenowa mogła dobrze funkcjonować i współpracować z resztą aplikacji. A więc rzeczy typu utrwalanie (zapis do bazy), operacje na systemie plików, komunikacja z webservice’ami etc.
Typowym przedstawicielem warstwy infrastruktury są mappery relacyjno-obiektowe np. Doctrine. Jednak nie wszystkie z nich nadają się by być częścią warstwy infrastruktury, Doctrine w wersji 2 się nadaje, natomiast w wersji 1 nie. Dlaczego ? Metodologia DDD zakłada całkowity rozdział warstwy infrastruktury od warstwy domenowej, niestety chcąc utrwalać obiekty domenowe w Doctrine 1 musiały by dziedziczyć one z klasy Doctrine_Row co tworzy niepożądaną zależność. Dzięki temu, że warstwa domeny jest niezależna od warstwy infrastruktury możemy np. zmienić w którymś momencie ORM-a bez konieczności zmiany naszych klas w warstwie domeny.
Wspomniałem wcześniej o kilku rodzajach obiektów, jak tworzyć więc klasy odpowiednich rodzajów ?
Klasy encji są to klasy które w konkretnym modelu domeny mają szczególne znaczenie, takie by stać się rozróżnialnymi (potocznie mówiąc mają własne ID). Klasy wartości natomiast różnią się od klas encji tym, że nie są rozróżnialne (potocznie mówiąc nie mają własnego ID, zawsze są przypisane do jakiejś encji). To czy dana klasa stanie się klasa encji czy wartości zależy od wielu czynników.
Załóżmy, że mamy aplikację do obsługi Urzędu Pocztowego, z modelowania wyszły nam dwie klasy Adres i Człowiek, w przypadku UP dla poczty najważniejszy jest adres i on też będzie encją, natomiast kto mieszka pod tym adresem jest w pewnym sensie sprawą drugorzędną (przynajmniej dla listów, które nie są polecone) będzie więc klasą wartości. Dla odmiany kiedy tworzymy aplikacje dla Hurtowni Internetowej to ważniejszy dla nas będzie Człowiek/Klient (on w końcu płaci za towar) i to on będzie encją, natomiast adres będzie wartością.
Trzecim rodzajem klas, które występują w DDD są klasy usług. Są to klasy, do których „wrzuca” się wszystkie metody, które nie pasują by je dołożyć do klas encji. Definicja ta niewiele mówi, nasuwa się więc pytanie – co powinno być w klasach encji ?
Metody w klasach encji powinny odpowiadać wszelkim (rzeczywistym) działaniom jakie wykonujemy na pojedynczym jej egzemplarzu. Przykładem niech będzie klasa Zamówienia z hurtowni internetowej. Będzie ona zawierać metody:
class Zamowienie { public function dodajTowar($towar); public function usunTowar($towar); public function obliczCene(); public function pobierzPodsumowanie(); public function dodajOdbiorce($odbiorca); public function dodajRabat($rabat); public function wybierzSposobPlatnosci(); public function wybierzSposobTransportu(); }
Co potrzebowalibyśmy jeszcze w kontekście zamówień ? Na pewno klient w swojej aplikacji chciałby mieć możliwość wyszukiwania Zamówień wg jakiś kryteriów, dotyczy to wszystkich zamówień, stworzymy więc klasę usługową, która będzie repozytorium zamówień. Jeżeli korzystamy z Doctrine’a 2, zostanie ona automatycznie wygenerowana przez ORM-a i będzie zawierała metody w rodzaju findBy(/*...*/)
, dzięki którym wyszukamy interesujące nas zamówienia. Repozytorium takie umożliwi również zapisywanie tych obiektów.
Rozmawiając ze znajomymi, którzy również są webdeveloperami, zauważyłem, że JavaScript jest jednym z najbardziej znienawidzonych języków. Jest kilka tego powodów, najważniejsze z nich to brak jednolitej obsługi w w przeglądarkach internetowych oraz brak klas. Pierwszy powód jest bezdyskusyjny, chociaż można zauważyć, że z upływem czasu jest coraz lepiej i minęły już te dni gdy pisało się kod osobno dla każdego browsera. Obsługa JavaScriptu jest coraz lepsza wśród przeglądarek i nawet Microsoft wziął się na poważnie do roboty z IE9. Natomiast argument o braku klas w Javascripcie uważam za trochę chybiony i wynikający z braku wiedzy o modelu obiektowym jaki został zaimplementowany w tym języku. Javascript jest językiem obiektowo-funkcyjnym. Wszystko w Javascripcie jest obiektem łącznie z funkcjami. Fakt, że w Javascripcie nawet funkcje są obiektami czyni z niego język funkcyjny. Z elementów języka funkcyjnego posiada on również domknięcia i funkcje anonimowe co wynika bezpośrednio z tego, że funkcje są obiektami. Kilka linijek kodu by to dowieść:
//funkcja nazwana function foo(){ return 1; } console.log(foo); //wyswietli 'foo()' //funkcja anonimowa przypisana do stalej - stala zmienna ktorej wartosci nie mozna zmienic(immutable) const bar = function(){ return 1; } console.log(bar); //zwraca 'function()' const b = 1; //zwroci exception redeclaration of const b //funkcja ktora zwraca obiekt funkcji function baz(){ return function(){ return 1; }; }
Wracając do naszych klas, należałoby się najpierw zastanowić czym jest klasa ? W językach o „tradycyjnym” modelu obiektowym (php, java, c++), klasa jest szablonem z którego tworzone są egzemplarze obiektów (instancje). Obiekty te mają ten sam sposób zachowania (współdzielą metody), różnią się jedynie wewnętrznym stanem (właściwości). Tak naprawdę są to języki zorientowane na klasy a nie na obiekty, żeby stworzyć jakikolwiek obiekt, trzeba najpierw zdeklarować klasę. W Javascripcie, dwa obiekty są tej samej klasy jeżeli mają ten sam konstruktor (prototyp).
//konstruktor klasy foo function foo(){ this.a = 'a' this.b = 'b' } var a = new foo(); var b = new foo(); console.log(a instanceof foo); //zwroci true console.log(b instanceof foo); //zwroci true
Fakt ten wiążę się również z mechanizmem dziedziczenia. Jeżeli chcemy by klasa bar dziedziczyła z klasy foo to znaczy to nie mniej nie więcej tyle, że konstruktor klasy bar powinien być wywołany po konstruktorze klasy foo. Wszystko wyjaśni poniższy przykład:
//konstruktor klasy foo function foo(){ this.a = 'a' this.b = 'b' } //konstruktor klasy bar function bar(){ this.c = 'c' this.d = 'd' } //konstruktor klasy baz function baz(){ this.e = 'e' this.f = 'f' } //bar dziedziczy z foo bar.prototype = new foo(); //baz dziedziczy z bar baz.prototype = new bar(); x = new baz(); console.log(x); console.log(x instanceof foo);
Jest to mechanizm pojedynczego dziedziczenia, jak wiadomo wielodziedziczenie jest problematyczne i w większości języków zrezygnowano z niego. Natomiast Javascript dzięki swojej funkcyjności pozwala na elastyczne komponowanie obiektów z różnych funkcji. Można np. wsiąść metodę z jednego obiektu, bądź globalnego konstekstu i przypisać ją do jakiejś klasy bądź obiektu. W Javascripcie zmienna „this” zawsze odnosi się do aktualnego kontekstu. Przykład:
//funkcja foofoo var foofoo = function(){ return this.a; }; //konstruktor klasy foo function foo(){ this.a = 'a' this.b = 'b' this.foofoo = foofoo; } //konstruktor klasy bar function bar(){ this.c = 'c' this.d = 'd' this.a = 'g'; this.foofoo = foofoo; } x = new bar() console.log(x.foofoo()); //zwroci 'g' z = new foo() console.log(z.foofoo()); //zwroci 'a'
Jedną z rzeczy, którą zarzuca się JavaScriptowi jest brak enkapsulacji. Jest to oczywiście nieprawda – JS posiada enkapsulacje ale realizowaną w inny sposób. Jak już wcześniej wspomniałem między wierszami, istnieje coś takiego jak kontekst wywołania funkcji. Każda funkcja anonimowa jest też domknięciem (closure) i pamięta kontekst swojego utworzenia, więc jeżeli w konstruktorze zadeklarujemy zmienną słowem kluczowym var, to metoda obiektu ją zapamięta. W ten sposób realizuje się enkapsulacje. Przykład:
//konstruktor klasy foo function foo(){ this.a = 'a' this.b = 'b' var privVar = 'a'; this.bar = function(){ return privVar; } } z = new foo() console.log(z.privVar); //zwroci undefined console.log(z.bar()); //zwroci 'a'
Ostatnią sprawą jaką chciałbym poruszyć są Singletony. W językach zorientowanych na klasy trzeba uzywać metod statycznych i innych trików. Natomiast w JS wystarczy zadeklarować anonimowy obiekt przypisany do globalnej zmiennej:
Singleton = { a: 'a', b: 'b', c: function(){ return this.a; } } console.log(Singleton.c());
Mam nadzieje, że ten artykuł przyczyni się chociażby w jakimś stopniu do zrozumienia JS, który jest przez wielu wyklęty tak naprawdę tylko ze względu na swoją odmienność.
Wszystkie przykłady testowałem na Firefoxie 3.6.6 + Firebug. Jak zwykle czekam na feedback i wszelkie pytania .
PFAf (PHP for Android framework) jest pierwszym (i na pewno nie ostatnim) frameworkiem ułatwiającym pracę wszystkim tym, którzy chcieliby pisać aplikacje w PHP na telefony z Androidem. Framework znajduje się w bardzo wczesniej fazie, mimo to działa zaskakująco dobrze.
PFAf składa się z dwóch klas – Application.php oraz ScriptAbstract.php. Pierwsza z nich odpowiada za załadowanie wszystkich wymaganych klas oraz wywołanie kolejnych, zdefiniowanych przez progrmistę akcji. Druga klasa jest nadrzędną klasą dla wszystkich skryptów i wspomaga Application.php w wywoływaniu kolejnych akcji.
Struktura katalogów PFAf wygląda następująco:
Dwa niewymienione wcześniej pliki, czyli example.php oraz Horoscope.php składają się na przykładową aplikację. Klasa Horoskop.php została napisana na podstawie skryptu horoscope.php autorstwa Javier Infante.
Rzeczy jakie pozostały do zrobienia w PFAf (TODO):
Pomysłów na rozwój jest więcej, jednak w chwili obecnej te wypisane powyżej są najważniejsze. Jeśli macie uwagi do kodu (sam mam kilka), piszcie śmiało. Jeśli okaże się, że są trafne na pewno je uwzględnię podczas tworzenia kolejnej wersji.