Alternatywne podejście do bazy danych – MongoDB

Ostatnio wiele mówi się o bazach danych NoSQL (NotOnlySQL). Jako, że nie zauważyłem nic w polskim światku PHP na ten temat, stwierdziłem, że pora wypełnić tę lukę i zagłębić się w temat.

Czym są bazy NoSQL ?

Jak podpowiada nam ciotka wikipedia. Bazy NoSQL są to bazy danych, które nie są klasycznymi relacyjnymi bazami danych jakie znamy (MySQL,PostgreSQL etc). Termin NoSQL nie odpowiada żadnym wspólnym cechom tych baz, a raczej temu, że na pewno nie są one RDBMS-ami.

Popularne bazy NoSQL są magazynami danych. W przeciwieństwie do klasycznych odpowiedników nie posiadają stałego schematu, a dane przechowują jako pary klucz – wartość.

Jako przykład dla naszych rozważań wybrałem bazę MongoDB. Dlaczego akurat tą? Istnieją dwa główne powody. Pierwszym z nich jest to, że twórcy projektu Doctrine, stworzyli również ORM w wersji dla tej bazy. Drugim natomiast to, że baza jest szczególnie przyjazna dla webdeveloperów.

Jej przyjazność objawia się tym, że baza żyje i oddycha Javascriptem. Shell przyjmuje komendy tylko w tym języku. Natomiast odpowiedź na zapytania zwracana jest w formacie JSON. Ciekawą rzeczą, może mniej dla webdeveloperów a bardziej dla zwolenników funkcyjnego podejścia do programowania, jest to, że baza wspiera rozproszony model obliczeń oparty na paradygmacie map-reduce. Na roztrząsanie tego tematu czas przyjdzie później. Natomiast teraz przejdźmy do części praktycznej.

Instalacja

  1. Na stronie projektu, wyszukujemy odpowiednią dla nas paczkę i ściągamy
  2. Rozpakowujemy ją w dowolnym katalogu
  3. W głównym katalogu dysku tworzymy folder data/db
  4. W katalogu gdzie rozpakowaliśmy paczkę z bazą odpalamy plik bin/mongod.exe
  5. Z sourceforge ściągamy driver do php w postaci rozszerzenia (w archiwum są dll-e dla odpowiednich wersji php)

W tej chwili mamy już działającą bazę MongoDB. Kolejnym krokiem będzie zaprzęgnięcie Doctrine’a do pracy.

Struktura projektu:

|--application
|---model
|----MailBox
|------Message.php
|-----User.php
|--lib
|---vendor
|----Doctrine
|--proxies
|--index.php

Do katalogu Doctrine ściągamy ODM (Object Document Mapper).

Plik index.php:


require 'lib/vendor/Doctrine/Common/ClassLoader.php';

use Doctrine\Common\ClassLoader,
 Doctrine\Common\Annotations\AnnotationReader,
 Doctrine\ODM\MongoDB\DocumentManager,
 Doctrine\ODM\MongoDB\Mongo,
 Doctrine\ODM\MongoDB\Configuration,
 Doctrine\ODM\MongoDB\Mapping\Driver\AnnotationDriver;

// ODM Classes
$classLoader = new ClassLoader('Doctrine\ODM', 'lib/vendor/');
$classLoader->register();

// Common Classes
$classLoader = new ClassLoader('Doctrine\Common', 'lib/vendor/');
$classLoader->register();

// Document classes
$classLoader = new ClassLoader('Model', 'application');
$classLoader->register();

$config = new Configuration();
$config->setProxyDir('proxies');
$config->setProxyNamespace('Proxies');

$reader = new AnnotationReader();
$reader->setDefaultAnnotationNamespace('Doctrine\ODM\MongoDB\Mapping\\');
$config->setMetadataDriverImpl(new AnnotationDriver($reader, __DIR__ . '/model'));

$dm = DocumentManager::create(new Mongo(), $config);

Jak widać nie ma tutaj dużo kodu, głównie autoloadery. Następnie mamy obiekt konfiguracji, gdzie ustawiany jest katalog na klasy proxy potrzebne przy leniwym ładowaniu obiektów. Jest też obiekt czytnika adnotacji, potrzebnego do wyciągania metadanych z klas. Na końcu tworzony jest obiekt managera dokumentów – czyli odpowiednik obiektu połączenia dla klasycznych baz danych.

Żeby korzystać z bazy danych przydałoby się coś utrwalić. Naszym utrwalanym modelem będzie wstępny prototyp do systemu prywatnych wiadomości. Składający się z dwóch klas.

User:

namespace Model;

/** @Document */
class User {

 /** @Id */
 private $__id;
 /** @Field */
 private $_firstName;
 /** @Field */
 private $_lastName;
 /** @Field */
 private $_email;

 public function __get($name) {
 $sName = '_' . $name;
 return $this->$sName;
 }

 public function __set($name, $value) {
 $sName = '_' . $name;
 $this->$sName = $value;
 }

}

Klasa ta posiada trzy pola prywatne, które są tak naprawdę publiczne ponieważ istnieje do nich nieograniczony dostęp przy użyciu magii (zaraz mnie pewnie Zyx skrytykuje), schowane są one pod jej płaszczykiem aby nie używać klasycznych getterów i setterów i aby w przypadku przyszłego refaktoringu trzymać się zasady zunifikowanego dostępu. Klasa zawiera również pole ID, które będzie w zasadzie używane tylko przez mapper, nie ma więc potrzeby go upubliczniać w żaden sposób.

Message:


namespace Model\Mailbox;

/** @Document */
class Message {

 /** @Id */
 private $__id;
 /** @boolean */
 private $__isSent = false;
 /**
 * @ReferenceOne(targetDocument="Model\User")
 */
 private $__sender;
 /**
 * @ReferenceMany(targetDocument="Model\User")
 */
 private $_receivers = array();
 /** @Field */
 private $_title;
 /** @Field */
 private $_content;

 public function __construct($sender) {
 $this->__sender = $sender;
 }

 public function addReceiver(\Model\User $user) {
 $this->_receivers[] = $user;
 }

 public function __get($name) {
 $sName = '_' . $name;
 return $this->$sName;
 }

 public function __set($name, $value) {
 $sName = '_' . $name;
 $this->$sName = $value;
 }

 public function sender() {
 return $this->__sender;
 }

 public function isSent() {
 return $this->__isSent;
 }

 public function markAsSent() {
 $this->__isSent = true;
 }

}

Klasa wiadomości wygląda podobnie jak poprzednia, ma dodatkowo kilka prostych metod. Rzeczami, które nas najbardziej interesują są adnotacje. Dzięki nim engine ODB-a jest w stanie przełożyć PHP-owe obiekty na dokumenty w bazie MongoDB. Każda utrwalana klasa musi mieć adnotacje „Document” oraz na polu identyfikatora adnotację „Id”. Każde zapisywane pole powinno mieć adnotację „Field”, domyślnie będzie ono zapisane jako „string”. Jeżeli chcemy je zapisać jako jakiś inny typ np. logiczny wpisujemy adnotację w stylu „boolean”. Jeżeli chcemy aby pole w zapisywanym obiekcie było referencją do jakiegoś innego zapisanego obiektu używamy adnotacji „ReferenceOne(targetDocument=”Nazwa\Klasy”)” oraz „ReferenceMany(targetDocument=”Nazwa\Klasy”)”.

Teraz gdy wiemy już jak adnotować nasze klasy, czas wykorzystać tą wiedzę w praktyce i wykonać kilka operacji na bazie, poniższy kod należy wstawić do „index.php”:

function testCreate($dm) {
    $oUserSender = new \Model\User();
    $oUserSender->firstName = 'Jan';
    $oUserSender->lastName = 'Kowalski';
    $oUserSender->email = 'jan@kowalski.pl';

    $oUserReceiver1 = new \Model\User();
    $oUserReceiver1->firstName = 'Krystyna';
    $oUserReceiver1->lastName = 'Kowalska';
    $oUserReceiver1->email = 'krystyna@kowalski.pl';

    $oUserReceiver2 = new \Model\User();
    $oUserReceiver2->firstName = 'Zofia';
    $oUserReceiver2->lastName = 'Kowalska';
    $oUserReceiver2->email = 'zofia@kowalski.pl';

    $oMessage = new \Model\Mailbox\Message($oUserSender);
    $oMessage->title = 'Some title';
    $oMessage->content = '<p>Dear xxx </p>';
    $oMessage->addReceiver($oUserReceiver1);
    $oMessage->addReceiver($oUserReceiver2);

    $dm->persist($oUserSender);
    $dm->persist($oUserReceiver1);
    $dm->persist($oUserReceiver2);
    $dm->persist($oMessage);
    $dm->flush();
}

function testRetrieve($dm) {
    $oRepo = $dm->getRepository('Model\MailBox\Message');
    $aResult = $oRepo->findAll();
    var_dump($aResult->getResults());
}

function testRemove($dm) {
    $oRepo = $dm->getRepository('Model\MailBox\Message');
    $oResult = $oRepo->findAll();
    foreach ($oResult as $oMessage) {
        $oRepo->remove($oMessage);
    }
    $dm->flush();
}

Nazwy funkcji raczej mówią za siebie także nie będę się wgłębiał. Kilka uwag:

  1. aby zmiana w bazie doszła do skutku po każdej serii operacji należy wywołać metodę „flush”
  2. metoda „getRepository” z DocumentManagera zwraca nam repozytorium obiektów jednego typu, wszystkie wykonane na nim operacje będą się tyczyły obiektów jednej klasy
  3. metody „find*” zwracają kolekcję po której można iterować za pomocą zwykłej pętli foreach, jeżeli natomiast wolimy zabawy z klasycznym arrayem, powinniśmy na wyniku metod „find*” zastosować metodę „getResults”

To by było na tyle z tego krótkiego wpisu wprowadzającego do baz NoSQL. Chciałbym jeszcze napisać coś o map-reduce ale godzina już późna więc pozostawię sobie to na kolejny post. Żądnych wiedzy odsyłam do stron obu projektów. Miłej zabawy 😉

  1. batman pisze:

    NoSQL zainteresowałem się podczas zgłębiania Windows Azure, w którym jednym z mechanizmów przechowywania danych jest Azure Table będący nierelacyjna bazą danych właśnie. Mogę śmiało powiedzieć, że sporą część projektów byłbym w stanie oprzeć o ten właśnie model.

    Wracając do MongoDB, robiłeś jakieś testy wydajnościowe dla dużych zbiorów danych?

  2. Wiem, widziałem twój wpis :). Jeszcze nie robiłem żadnych testów, bo szczerze mówiąc zastanawia mnie jakie kryteria w nich zastosować ? Pytanie jak powinien wyglądać testowy zbiór danych, jakie operacje na nim przeprowadzać, żeby było to miarodajne w stosunku do klasycznych DB ? Jak na razie na myśl przychodzi mi tylko przykładowa tabela „Artykuły” z polami „id”, „tytuł”, „treść” i testy w rodzaju pobierania zestawu tysiąca rekordów spośród miliona, czy wybieranie losowych rekordów po „id”, to są dość typowe rzeczy ale nie wiem czy miarodajne…

  3. batman pisze:

    Najlepszym przykładem będzie projekt blog. Dwie tabele – wpisy oraz komentarze. W tabeli wpisy przechowujesz dodatkowo ilość komentarzy oraz ewentualnie ostatni wpis (nie wiem czy w MongoDB możne przechowywać obiekty). Do tego tablica podsumowująca – łączna ilość wpisów, łączna ilość komentarzy i inne informacje, które zazwyczaj się oblicza się funkcjami sum, max, min, itd. Taka tablica jest o tyle ważna, że nie trzeba będzie mielić pozostałych tabel dla kilku prostych informacji. Ilość danych – 1000 wpisów i po kilkanaście komentarzy na wpis.
    Testy to typowe operacje dla takiej aplikacji, czyli wyciąganie listy wpisów z zakresu od – do, pobieranie komentarzy dla jednego wpisu, generowanie rss, itd.

  4. Ok, zobaczę co da się zrobić 😉

  5. Kamil pisze:

    NoSQL jst niewątpliwie ciekawym ruchem i bardzo mi się podoba takie podejście do baz danych.

    Jak na razie czytam same zalety i wszyscy zachwalają, więc dla odmiany mógłby ktoś w końcu napisać o wadach NoSQL – bo na pewno takowe by się znalazły.

    Z tego co zaobserwowałem to najgorsza jest obsługa takich baz na serwerach (a mało kto potrzebuje dedyka). Także co nam po aplikacji opartej na Mango, jeśli nie znajdziemy pod niej serwera :-) przynajmniej jeszcze nie teraz.

  6. Widzę, że już powoli się lista życzeń do następnego wpisu tworzy. Co do dostępności baz to zauważyłem już, że na zagranicznych hostingach powoli MongoDb się przebija. No a dedyki w sumie są już tak tanie, że każdy może sobie na nie pozwolić – za 20 zł na miesiąc to nawet przeciętny gimnazjalista odmawiający sobie kilku piw w tygodniu 😛

  7. Mirek pisze:

    Testy? Przede wszystkim skomplikowane zapytania z wieloma zagnieżdżonymi warunkami, podzapytaniami + partycjonowanie danych (w celu optymalizacji). Należy także śledzić plany wykonania, aby dowiedzieć się, jak nasze zapytanie przekłada się na operacje wykonywane przez silnik. Można wziąć dla przykładu złożony model obiektowy (kilkanaście-kilkadziesiąt klas; dla systemów klasy enterprise – kilkaset), zmapować to na model relacyjny i porównać zapytania w rodzaju
    SELECT (… subSELECT)
    FROM subSELECT
    WHERE x – (subSELECT (subSELECT…))

    i tak dalej. Baza winna być wypełniona losowymi danymi na poziomie średnio 10 tys. rekordów na tabelę. To da rzeczywiste porównanie wydajności.

  8. @mirek Hm obawiam się, że w MongoDB nie ma żadnych planów zapytania, nie ma też żadnego języka zapytań podobnego do SQL. W shellu możesz sobie zrobić po prostu finda po jakiś kryteriach albo napisać jakąś funkcję w Javascriptcie, która Ci odfiltruje wyniki wg własnego widzimisię. Można by ewentualnie porównać jak działa Doctrine 2 na MongoDb i na bazach relacyjnych…

    Co do twojej propozycji testu to obawiam się, że nie mam wystarczających zasobów czasu i chęci, żeby robić aż tak skomplikowane benchmarki.

  9. batman pisze:

    @Mirek
    Szeroko rozumiany NoSQL powstał po to by uwolnić się z piekła zależności. Każdy „wiersz” w „tabeli” posiada własną, przez nic nie narzuconą, strukturę, która nie daje możliwości tworzenia relacji znanych z relacyjnych baz danych. „Relacje” zależą tylko i wyłącznie od ułańskiej fantazji twórcy projektu i nie da się ich obiektywnie przetestować.

  10. lunereaper pisze:

    Nie wspomniałeś, albo przeoczyłem (ale nie wydaję mi się) że Mongo tak jak większość noSQL korzysta z mmap. Idzie za tym ważny problem, ilość danych jakie chcesz przechowywać równa się ilości ramu na maszynie! Jeżeli przekroczysz wartość ramu to dane idą w powietrze- całkiem przyjazne rozwiązanie dla developera. Dane na dysku także zajmują w porównaniu do SQL magiczną przestrzeń, importujesz SQL 10MB a w folderze z danymi noSQL pojawia się magiczne 512MB- fakt ten wynika z alokowania plików o tymże rozmiarze, ale jeżeli zaimportujesz np 300MB to już masz ponad 2GB – ciekawe. Także w momencie shut-downu jest duże prawdopodobieństwo stracenia danych.

    Kolejna sprawa, ORM jest bardziej uciążliwy niż korzystanie z rozszerzenia dawanego przez samych developerów MongoDB.

    Ja zostaję przy SQLu, noSQL nadaję się jako magazyn do przechowywania sesji i cache do zapytań SQLowych, nie ryzykowałbym opierać cały projekt tylko i wyłącznie na MongoDB

  11. @lunereaper nie wchodziłem aż tak głęboko w szczegóły implementacji także będę musiał sprawdzić fakty, które przytaczasz. Co do ORM-a to ja muszę przyznać, że ten Doctrine’owy jest całkiem spoko, dla mnie jest ok. Co do opierania projektów to nie mogę się wypowiedzieć za bardzo również bo jeszcze nic nie oparłem, ale jak coś to zawsze można zapytać gości z Facebooka, Yahoo czy Googla, którzy oparli poważne produkty na tym…

  12. lunereaper pisze:

    Tak facebook stworzył własnego noSQL’a cassandrę i zopensursował go i używają go nie jako jedyny storage do danych ale jako mechanizm do inbox searcha. Twitter też nie przechowuje twitów w bazach typu noSQL tylko mysql.

    Także tak jak pisałem noSQL- tak ale do określonych rzeczy, a nie jako jedyny storage danych w projekcie.

  13. Crozin pisze:

    Na początek trochę na temat samych listingów.
    1. Wszystkie „private” powinny być zamienione w tych przykładach na „protected”.
    2. Format „__xxx” jest raczej zarezerwowany dla metod/właściwości deweloperów PHP.
    3. Po co używasz tego durnego __get()/__set()? Kompletnie nic Ci to tutaj nie daje, a jedynie przyczynia się do popularyzowania błędnego użycia metod magicznych. Albo zrób je publiczne (w przyszłości zawsze będziesz mógł dodać __get()/__set()) albo niepubliczne i dodaj gettery/setter (by skrócić wpis możesz zawsze dodać komentarz „// tutaj gettery/settery” – ich wygląd jest przecież oczywisty w takich przykładach).
    4. Za to „global” w testach powinni Cię zlinczować. Przecież dużo logiczniejsze jest przekazanie tego jako… argument funkcji.

    Przykłady w artykule są trochę źle dobrane, bo… tutaj dużo lepszym rozwiązaniem jest RDBMS – w końcu wszystko ma sztywną, relacyjną formę. Mógłbyś przykładowo pokazać realizację modelu EAV albo „niesztywnych” relacji (tak popularna Facebookowa ściana).

    PS. Przydałaby się informacja o sposobie formatowania treści w komentarzach. Chyba, że formatowania w ogóle nie ma?

  14. @crozin
    to jest mocno poglądowy kod także nie jest jakoś szczególnie dopieszczony

    1. obojętne czy będzie private czy protected, z klas encji i tak nigdy nie powinno się dziedziczyć, można było obie klasy równie dobrze zrobić final

    2. wiem, ale to jest taki skrót dla potrzeb __get i __set

    3. __get i __set nie są durne. Dlaczego je dałem napisałem -> „Klasa ta posiada trzy pola prywatne, które są tak naprawdę publiczne ponieważ istnieje do nich nieograniczony dostęp przy użyciu magii (zaraz mnie pewnie Zyx skrytykuje), schowane są one pod jej płaszczykiem aby nie używać klasycznych getterów i setterów i aby w przypadku przyszłego refaktoringu trzymać się zasady zunifikowanego dostępu. ”

    4. oczywiście, że jest i w kodzie produkcyjnym nigdy bym nie użył „global” ale to jest tylko szybki przykład

    Przykłady nie są po to, by pokazać, że akurat noSQL jest w tym przypadku lepszym rozwiązaniem od klasycznych baz. Przykłady są tutaj w celu pokazania jak przy pomocy ORM (a raczej ODM) korzystać z bazy MongoDB. Ten artykuł to tutorial/quickstart a nie porównanie RDBMS vs noSQL.

    Co do formatowania to możesz standardowo używać podstawowych tagów html-a. Natomiast kod trzeba otoczyć

    
    '[sourcecode lang="język"]'
    
    kod
    
    '

    [/sourcecode]

  15. Crozin pisze:

    1. Czemu niby nie miałbym dziedziczyć? Co jeżeli w bazie danych mam strukturę hierarchiczną (bazy danych takie coś obsługują) i taką strukturę chciałbym odzwierciedlić w PHP? Samo Doctrine nawet wspiera dziedziczenie w parserze adnotacji.

    2. Czy tylko mi fragment Klasa ta posiada trzy pola prywatne, które są tak naprawdę publiczne […] wygląda na z góry zrypany? Albo utwórz gettery/settery, albo utwórz get/set (z nazwą pola jako argumentem). Nie używaj __get/__set, które nagle sprawi, że zwykle $obj->abc = 'test'; wyrzuci nie wiadomo skąd jakiś wyjątek albo przypisze wartość inną niż „test”. Albo podaj konkretne argumenty za __get/__set – bo ja tu takowych nie widzę.

    4. Wybacz, ale functionName(EntityManager $em) i functionName() gloabl $em; raczej zapisuje się w jednakowym czasie i żadne nie komplikuje kodu. Może lepiej poświęć 10 minut przed publikacją wpisu na sprawdzenie go pod kątem takich właśnie niespodzianek?

  16. 1. Dobre praktyki obiektowe -> kompozycja ponad dziedziczenie == czym mniej bezpośrednich zależności w kodzie tym lepiej

    2. Wiesz może po co stosuje się gettery i settery ? Stosuje się je po to, że jeżeli mamy pole publiczne w klasie i w którymś momencie rozwoju kodu musimy dodać np. walidacje tego pola przy ustawieniu, to musi to nastąpić poprzez jakąś metodę – klasycznie przy użyciu settera, getter jest jego uzupełnieniem. Jako, że w Javie nie ma magicznych metod __get i __set to gettery i settery trzeba zdefiniować zawczas, żeby potem nie szukać po całym kodzie w przypadku zmiany. Dzięki PHP-owemu __get i __set możemy dostawać się do takiego pola bez zmiany zewnętrznego interfejsu klasy tak jak by te pola były publiczne.

    4. Owszem zapisuje się w tym samym czasie i co z tego ? W tym przypadku nie zaciemnia to w żaden sposób kodu. I owszem przyznaje, że globale, tak jak każdy globalny stan (metody i pola statyczne np.) z gruntu rzeczy złe. Napisałem jak napisałem bo to jest szybki tutorial, chodzi o ogólny obraz stanu rzeczy. Jak pisałem ten kod to wszystko było wrzucone w głównym pliku index i dla wygody czytelnika, zrobiłem funkcję test*, żeby nie trzeba było za każdym razem zakomentowywać jakiegoś fragmentu kodu, żeby sprawdzić jakiś „ficzer”

  17. Kamil pisze:

    Z tymi globalami zgodzę się z Crozin, brzydko to wygląda i nie wypada stosować, by nie uczyć innych – przekazując przez argument funkcji można by to równie szybko i prosto załatwić.

    Co do reszty zgadzam się z Wojciechem.

  18. @kamil: no jak z resztą się zgadzasz to dla ciebie wywalę te globale 😛

  19. Crozin pisze:

    Dobrą praktyką jest używanie odpowiednich narzędzi do odpowiednich celów. Dziedziczenie jest jednym z narzędzi i poprawnie użyte jest ponad niepoprawnie użytą kompozycją.

    Doskonale wiem, do czego służą gettery i settery. Ale spójrz jaki kod utworzyłeś: http://ideone.com/E92mW
    Nagle zwykła operacja przypisania zostaje zamieniona na potajemne wywołanie metody. W tym przypadku nie jest jeszcze tak źle bo nie robimy nic ponad zwykłe przypisanie wartości. Ale zmieńmy trochę ten kod: http://ideone.com/pyzps
    Już zaczął robić się burdel w metodzie __set(), a kod stał się nielogiczny ponieważ zwykła operacja przypisania stała się czymś innym.
    $msg->title = 'Hello world!';
    echo $msg->title; // Nagle może zwrócić mi przykładowo "hello-world"

    Ale przecież nigdzie nie zmieniałem tej wartości, nic z nią nie robiłem. Nie było też możliwości by jakaś inna metoda jako efekt uboczny zmodyfikowała wartość bo obie instrukcje były wykonane jedna po drugiej. WTF?! Aaa… przeglądając źródła dowiadujemy się, że tak na prawdę to nie wykonaliśmy, żadnej operacji przypisania tylko wywołaliśmy jakieś Message::__get().

    Jeżeli chociaż skorzystałbyś z __call() i skończył z czymś pokroju tego: http://ideone.com/R8HcS
    Kod nie byłby już aż tak WTF-owy, bo widać w użyciu, że wywołujemy jakąś metodę, a ona (co logiczne) może wykonać jakieś poboczne akcje jak zmiana wartości argumentu.
    Pomijamy już fakt, że kod ten jest i tak bezsensu, bo pierwsze lepsze IDE wygeneruje nam podstawowe gettery/settery.

    >no jak z resztą się zgadzasz to dla ciebie wywalę te globale

    Jakbyś jeszcze dodał informację o typie $dm -> Doctrine\ODM\DocumentManager $dm byłoby cudownie. 😉

  20. Uważam, że twój przykład z ‚Hello world’ -> ‚hello-world’ jest nieadekwatny. Dlaczego ? Ponieważ jeżeli nastąpiła zmiana tego stringa w międzyczasie, to widocznie była potrzebna by zachować poprawny wewnętrzny stan obiektu. W OOP najważniejsze jest zachowanie obiektu a nie jego stan wewnętrzny. Jeżeli będziemy pobierali od niego „title” to mamy ufność, że jest to poprawna wartość, mimo, że inna została do niego przekazana. Co więcej wchodząc głębiej w korzenie OOP, wiemy, że polega ono na przekazywaniu komunikatów między obiektami, czyli tak naprawdę nawet operacja typu $foo->bar = ‚baz’ będzie przekazaniem komunikatu, czyli nawet w przypadku obiektu, który nie ma jawnie zadeklarowanego w swoim kodzie __set jest ona wywoływana (przynajmniej w teorii nie wiem jak to jest zrealizowane pod maską). Co do użycia tutaj, to na pewno __set zostanie użyte do zablokowania zmiany title i content, gdy status wiadomości zostanie zmeniony na wysłany, kiedy przejdzie ona w tryb readonly (będzie wyrzucone exception).

    Co do typowania $dm to nie dodam tego bo nie ma to tutaj żadnego zdarzenia -> nadgorliwość gorsza od faszyzmu // jak coś to pasuje tu jedynie interfejs.

  1. There are no trackbacks for this post yet.

Leave a Reply

Informuj mnie o odpowiedziach poprzez e-mail. Możesz również subskrybować wpis bez zostawiania komentarza.