Tag Archives: wzorce projektowe

webmastering

Wzorzec projektowy memento w PHP

To pierw­szy wpis w tym roku, mam nadzieję, że wyda się Wam na pozio­mie. Czytam co nieco na temat wzorca memento i trochę ubole­wam, że nie znam C++, gdyż wszyst­kie przy­kłady we Wzor­cach projek­to­wych1 napi­sane są właśnie w tym języku (część gwiaz­dek i innych dwukrop­ków w kodzie jest dla mnie trochę niezrozumiała).

Na warsz­tat wzią­łem dzisiaj wzorzec memento (wg mnie tłuma­cze­nie jako „pamiątka” jest trochę śmieszne; memento to memento i koniec). Za pomocą tego wzorca możemy sporzą­dzić coś na kształt kopii przy­wra­ca­nia stanu obiektu. Ma to zasto­so­wa­nie głów­nie jako mecha­nizm wstecz/cofnij w stan­dar­do­wych apli­ka­cjach. Jak ktoś się uprze, to na pewno w PHP również znaj­dzie zastosowanie.

Napi­sa­nie kodu obiektu trzy­ma­ją­cego stan innego obiektu jest dosyć proste, dlatego podnio­słem trochę poprzeczkę o spraw­dza­nie kto wywo­łuje dany obiekt i czy dane memento jest na pewno dla niego prze­zna­czone. W/w mądra książka mówi do mnie, że tworzyć i przy­wra­cać stan ma obiekt, którego stan doty­czy. Za to prze­cho­wy­wa­niem stanów zajmo­wać się odrębny obiekt — CareTaker (u mnie PrzechowywaczMemento :-) ).

Najpierw idzie obiekt zain­te­re­so­wany prze­cho­wy­wa­niem swojego stanu. Stan każdego obiektu to przy­pi­sane warto­ści do jego zmien­nych. U mnie będzie to tylko zmienna $komunikat.

class JakisObiekt {
 
    /**
     * @var string komunikat do wyswietlenia
     */
    protected $komunikat;
 
    /**
     * @return string komunikat
     */
    public function getKomunikat() {
        return $this->komunikat;
    }
 
    /**
     * @param string $k komunikat do ustawienia
     */
    public function setKomunikat($k) {
        $this->komunikat = $k;
    }
 
    /**
     * @return Memento memento z aktualnym komunikatem obiektu
     */
    public function getMemento() {
        return new Memento($this);
    }
 
    /**
     * @param Memento $memento zawierajace poprzedni komunikat
     */
    public function setMemento(Memento $memento) {
        try {
            $this->komunikat = $memento->getKomunikat(spl_object_hash($this)) . ' (przywrocony z Memento)';
        }
        catch (Exception $e) {
            echo 'Nie udalo sie przywrocic poprzedniego stanu: ' . $e->getMessage();
        }
    }
}

Zain­te­re­so­wać może was funk­cja spl_object_hash. Zwraca unikalny iden­ty­fi­ka­tor (hash) danego obiektu. Memento zapi­suje sobie iden­ty­fi­ka­tor przy tworze­niu, a przy chęci wywo­ła­nia getKomunikat() wymaga poda­nie hasha w celu spraw­dze­nia czy dane memento jest na pewno dla niego.

Dalej idzie kod tytu­ło­wego memento, czyli obiekt prze­cho­wu­jący stan obiektu. Jest ściśle powią­zane z obiek­tem JakisObiekt (lub jego potomkami).

class Memento {
 
    /**
     * @var string hash obiektu tworzacego memento
     */
    private $hash;
 
    /**
     * @var string przechowywany komunikat
     */
    private $komunikat;
 
    public function __construct(JakisObiekt $ob) {
        if ($this->czyLegalnyWywolujacy() === true) {
            $this->hash = spl_object_hash($ob);
            $this->komunikat = $ob->getKomunikat();
        }
        else {
            throw new Exception('Tylko obiekt z rodziny JakisObiekt moze tworzyc klase Memento');
        }
    }
 
    public function getKomunikat($hash) {
        if ($this->czyLegalnyWywolujacy() !== true) {
            throw new Exception('Tylko obiekt z rodziny JakisObiekt moze wywolac funkcje getKomunikat()');
        }
        if ($this->hash !== $hash) {
            throw new Exception('Hash obiektu tworzacego Memento i wywolujacego getKomunikat() nie zgadza sie');
        }
        return $this->komunikat;
    }
 
    private function czyLegalnyWywolujacy() {
        $trace = debug_backtrace(true);
        if (!empty($trace[2]['object']) && is_a($trace[2]['object'],'JakisObiekt')) {
            return true;
        }
        else {
            return false;
        }
    }
}

Tu z kolei powinna Was zain­te­re­so­wać funk­cja czyLegalnyWywolujacy(). To cudo wywo­łuje (praw­do­po­dob­nie) zaso­bo­żerną funk­cję debug_backtrace i spraw­dza kto 2 kroki wcze­śniej popro­sił o wywo­ła­nie funk­cji. Jeżeli jest to kto inny niż obiekt typu JakisObiekt to się obraża i daje sygnał do wywa­le­nia wyjątku. Fajne, co?

Dla spraw­dze­nia czy memento jest w stanie przyj­mo­wać żąda­nia od obiek­tów dzie­dzi­czo­nych po JakisObiekt tworzymy sobie obiekt Dziedziczony.

class Dziedziczony extends JakisObiekt { }

Na koniec podaję kod prze­cho­wy­wa­cza, który zajmuje się zapi­sem kolej­nych wersji i odtwa­rza­niem ich. Po niewiel­kich prze­rób­kach kod może stać się w zasa­dzie uniwersalny.

class PrzechowywaczMemento {
 
    /**
     * @var array tablica asocjacyjna o konstrukcji hash => memento
     */
    private $stany;
 
    public function __construct() {
        $this->stany = array();
    }
 
    public function zapiszStan(JakisObiekt $jo) {
        $this->stany[spl_object_hash($jo)][] = $jo->getMemento();
    }
 
    public function przywrocStan(JakisObiekt $jo) {
        $hash = spl_object_hash($jo);
        $znalezionyStan = (!empty($this->stany[$hash])) ?
                array_pop($this->stany[$hash]) :
                null;
        if ($znalezionyStan !== null) {
            $jo->setMemento($znalezionyStan);
        }
        else {
            echo 'Brak zapisanego wczesniejszego stanu dla tego obiektu <br />';
        }
    }
 
    public function oczyscRejestrMemento() {
        foreach ($this->stany as $key => $val) {
            if (count($val) == 0) {
                unset($this->stany[$key]);
            }
        }
    }
}

Metoda przywrocStan() może być trochę zawiła. Najpierw spraw­dza czy klucz z haszem poda­nego w para­me­trze obiektu w ogóle istnieje. Jeżeli tak to przy­wraca stan, a jeżeli nie to wypi­suje komu­ni­kat. Na koniec mamy bezpa­ra­me­trową metodę oczyscRejestrMemento(). Jeżeli przez prze­cho­wy­wa­cza prze­szło wiele obiek­tów, robi się śmiet­nik kluczy nieza­wie­ra­ją­cych stanów do przywrócenia.

Czas na testy!

$przechowywacz = new PrzechowywaczMemento();
 
$ob = new JakisObiekt();
 
$ob->setKomunikat('pierwszy komunikat');
echo $ob->getKomunikat() . '<br />';
$przechowywacz->zapiszStan($ob);
 
$ob->setKomunikat('drugi komunikat');
echo $ob->getKomunikat() . '<br />';
$przechowywacz->zapiszStan($ob);
 
$ob->setKomunikat('trzeci komunikat (tego nie zapiszemy)');
echo $ob->getKomunikat() . '<br />';
 
// tworzymy drugi obiekt, tym razem klasy Dziedziczony
$ob2 = new Dziedziczony();
 
$ob2->setKomunikat('pierwszy dziedziczony');
echo $ob2->getKomunikat() . '<br />';
$przechowywacz->zapiszStan($ob2);
 
$ob2->setKomunikat('drugi dziedziczony');
echo $ob2->getKomunikat() . '<br />';
$przechowywacz->zapiszStan($ob2);
 
echo '<br />zaczynamy przywracanie<br />------------------------------<br />';
 
// przywracamy stan pierwszego
$przechowywacz->przywrocStan($ob);
echo $ob->getKomunikat() . '<br />';
 
// jeszcze wczesniejsza wersja pierwszego
$przechowywacz->przywrocStan($ob);
echo $ob->getKomunikat() . '<br />';
 
// kolejna proba przywrocenia
$przechowywacz->przywrocStan($ob);
echo $ob->getKomunikat() . '<br />';
echo '------------------------------<br />';
 
// teraz dla drugiego
$przechowywacz->przywrocStan($ob2);
echo $ob2->getKomunikat() . '<br />';
 
// druga proba
$przechowywacz->przywrocStan($ob2);
echo $ob2->getKomunikat() . '<br />';
 
// oczyszczamy rejestr
$przechowywacz->oczyscRejestrMemento();
 
// proba stworzenia obiektu typu memento zakonczona wyjatkiem
$memento = new Memento($ob);

Wyniki testu zgode z przewidywaniem:

pierw­szy komu­ni­kat
drugi komu­ni­kat
trzeci komu­ni­kat (tego nie zapi­szemy)
pierw­szy dzie­dzi­czony
drugi dziedziczony

zaczy­namy przy­wra­ca­nie
——————————
drugi komu­ni­kat (przy­wro­cony z Memento)
pierw­szy komu­ni­kat (przy­wro­cony z Memento)
Brak zapi­sa­nego wcze­sniej­szego stanu dla tego obiektu
pierw­szy komu­ni­kat (przy­wro­cony z Memento)
——————————
drugi dzie­dzi­czony (przy­wro­cony z Memento)
pierw­szy dzie­dzi­czony (przy­wro­cony z Memento)

Fatal error: Uncau­ght excep­tion ‚Excep­tion’ with message ‚Tylko obiekt z rodziny Jaki­sO­biekt moze tworzyc klase Memento’ in

Trochę przy­dłu­gawy wyszedł ten post. Mam nadzieję, że komuś udało się dotrwać aż tutaj :-)

  1. E. Gamma (i in.) : Wzorce projek­towe. Elementy opro­gra­mo­wa­nia obiek­to­wego wielo­krot­nego użytku. Gliwice : Helion, 2010, s. 294–301
Inne

Nowa stara książka i interesująca zniżka 25% w Matrasie

Ostat­nimi czasy wyro­bi­łem sobie takie dziwne przy­zwy­cza­je­nie, że czeka­jąc na Spodek 2.0 (o którym również mam w planie parę zdań napi­sać chyba nie ma sensu już pisać — na stro­nie S2.0 udostęp­nione są filmy) wstę­puję do jakiejś księ­garni (zazwy­czaj Empik) i prze­glą­dam co tam nowego z infor­ma­tyki. Tym razem padło na księ­gar­nię Matras przy ul. Stawo­wej w Katowicach.

Miło się zasko­czy­łem widząc, że do 14 listo­pada jest na prawie wszystko 25% zniżki. Mówię „prawie” dlatego, że zniżka nie obej­muje podręcz­ni­ków i wybra­nych tytu­łów. Za to inte­re­su­jący mnie i mam nadzieję Was dział infor­ma­tyka (czyli w głów­nej mierze Helion) owa zniżka obej­muje. Co prawda w porów­na­niu z Empi­kiem w SCC wybór jest sporo mniej­szy, ale zawsze można coś cieka­wego wybrać.

Rzadko zdarza się żeby można było kupić coś taniej ze sporą zniżką poza skle­pem inter­ne­to­wym Helionu (mam na myśli promo­cję „książka dnia 30% taniej”).

Tyle zachwa­la­nia. Tym razem kupi­łem polską edycję kulto­wej książki na temat wzor­ców projek­to­wych autor­stwa Ericha Gamma, Richarda Helma, Ralpha John­sona, Johna M. Vlis­si­desa (znani również jako Gang of Four – GoF) Wzorce projek­towe. Elementy opro­gra­mo­wa­nia obiek­to­wego wielo­krot­nego użytku. Orygi­nał powstał w 1994 – jednak treść jest w dalszym ciągu aktualna.

Same wzorce zostały podzie­lone na 3 części: konstruk­cyjne, struk­tu­ralne i opera­cyjne. Przy­kła­dowy kod mamy w C++, ale za to sche­maty i opisy są na tyle zrozu­miałe, że śred­nio zaawan­so­wani progra­mi­ści dadzą radę. Zasta­na­wiam się czy nie napi­sać małego cyklu z przy­kła­do­wym kodem konkret­nego wzorca w PHP — tak w ramach sportu :-)

webmastering

Weź mi zrób procesor tekstu cz. 2 / 2

Zgod­nie z zapo­wie­dzią, dzisiaj będziemy imple­men­to­wać moduły, które coś z poda­nym tekstem robią. Do tego celu głów­nie używać będziemy wyra­żeń regularnych.

1. Na pierw­szy ogień pójdzie popra­wiacz spacji. Jak często trafiają się wam doku­menty forma­to­wane za pomocą 20 spacji? Za pomocą tego cuda wywa­limy wszystko co ma powy­żej dwóch za koleją.

class SpaceFixer implements TextJob {
   public function process($string) {
      $string = trim($string);
      $string = preg_replace('/\s{2,}/' , ' ' , $string);
      return $string;
   }
}

Zasada dzia­ła­nia prosta: najpierw wyrzuć wszel­kie spacje przed i za strin­giem, a następ­nie poszu­kaj dwie lub więcej spacji za koleją i zamień je na pojedyncze.

2. Teraz coś sporo trud­niej­szego: doda­wacz twar­dych spacji do spój­ni­ków. Zasada dzia­ła­nia taka: znajdź mi spację, poje­dyn­czy znak, znowu spację i jeden lub więcej znaków (np. jedze­nie_i_picie).

class AddNbsps implements TextJob {
   public function process($string) {
      $output = ''; // inicjalizujemy pustą zmienną, zostało mi to po Javie
      foreach ($this->splitStringsByTagChar($string) as $split) {
         $output .= substr($split , 0 , 1) == '<' ? $split : $this->fixLine($split);
      }
      return $output;
   }
   private function splitStringsByTagChar($string) {
      return preg_split('/(<.+?>)/', $string, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
   }
   private function fixLine($line) {
      return preg_replace('/(\s{1}[-\("\w]{1})\s{1}([-\)"\w]+)/' , '$1&nbsp;$2' , $line);
   }
}

Jak widać trochę się pokom­pli­ko­wało. Po co nam te dodat­kowe funk­cje? Ano po to żeby nie popsuły się nasze HTMLowe tagi. Funk­cja splitStringsByTagChar nieza­chłan­nie szuka czegoś co przy­po­mina sekwen­cję <coś> i szat­kuje nam ciąg do tablicy.

Opera­tor trój­ar­gu­men­towy po fore­ach spraw­dza czy element tablicy zaczyna się od znaku < . Jeżeli tak, to zosta­wia go w spokoju, a jak nie, to kieruje do prze­róbki do fixLine. Można zauwa­żyć, że oprócz klasy znaków \w (alfa­nu­me­ryczne i _) doda­łem również ” ( ). Po prze­ana­li­zo­wa­niu kilku tekstów stwier­dzi­łem, że tak lepiej działa (a mogłoby pewnie lepiej gdybym poświę­cił na to jesz­cze trochę czasu).

3. Wyszu­ki­wacz linków.

class AddLinks implements TextJob {
   public function process($string) {
      return preg_replace('`(?<!href="|src=")((https?://|www\.)[\w{1,}./?=#&]+)`i' , '<a href="$1">$1</a>' , $string);
   }
}

Tutaj można zauwa­żyć dziwną konstruk­cję (?<! Jest to aser­cja wsteczna. Działa tak: znajdź mi ciąg znaków zaczy­na­jący się od http:// lub www., a jak znaj­dziesz, to sprawdź czy czasami przed znale­zio­nym ciągiem nie ma konstruk­cji z aser­cji. Po co to? Do wykry­cia, czy link znaj­duje się już w tagu <a href=” lub <img src=”, a więc czy nie popsu­jemy kodu html. Do tego mógł­bym równie dobrze użyć funk­cji szat­ku­ją­cej z poprzed­niego modułu, ale chcia­łem żeby było trudniej :-)

Jak tego wszyst­kiego używać? Dosyć prosto: w atry­bu­tach poda­jemy moduły, które nas inte­re­sują i poda­jemy tekst:

$jakisTekst = 'Ala ma kota, a kot ma HIV';
$txtProcesser = TextProcesser::getInstance(array('SpaceFixer','AddNbsps','AddLinks'));
$processedTxt = $txtProcesser->processJobs($jakisTekst);

Tekst będzie prze­twa­rzany w poda­nej kolej­no­ści. Fajne, co?
[czeka na oklaski]