Niezalogowany [ logowanie ]
Subskrybuj kanał ATOM Kanał ATOM

Autor wpisu: Śpiechu, dodany: 02.01.2011 21:47, tagi: php

To pierwszy wpis w tym roku, mam nadzieję, że wyda się Wam na poziomie. Czytam co nieco na temat wzorca memento i trochę ubolewam, że nie znam C++, gdyż wszystkie przykłady we Wzorcach projektowych1 napisane są właśnie w tym języku (część gwiazdek i innych dwukropków w kodzie jest dla mnie trochę niezrozumiała).

Na warsztat wziąłem dzisiaj wzorzec memento (wg mnie tłumaczenie jako „pamiątka” jest trochę śmieszne; memento to memento i koniec). Za pomocą tego wzorca możemy sporządzić coś na kształt kopii przywracania stanu obiektu. Ma to zastosowanie głównie jako mechanizm wstecz/cofnij w standardowych aplikacjach. Jak ktoś się uprze, to na pewno w PHP również znajdzie zastosowanie.

Napisanie kodu obiektu trzymającego stan innego obiektu jest dosyć proste, dlatego podniosłem trochę poprzeczkę o sprawdzanie kto wywołuje dany obiekt i czy dane memento jest na pewno dla niego przeznaczone. W/w mądra książka mówi do mnie, że tworzyć i przywracać stan ma obiekt, którego stan dotyczy. Za to przechowywaniem stanów zajmować się odrębny obiekt — CareTaker (u mnie PrzechowywaczMemento :-) ).

Najpierw idzie obiekt zainteresowany przechowywaniem swojego stanu. Stan każdego obiektu to przypisane wartości do jego zmiennych. 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();
        }
    }
}

Zainteresować może was funkcja spl_object_hash. Zwraca unikalny identyfikator (hash) danego obiektu. Memento zapisuje sobie identyfikator przy tworzeniu, a przy chęci wywołania getKomunikat() wymaga podanie hasha w celu sprawdzenia czy dane memento jest na pewno dla niego.

Dalej idzie kod tytułowego memento, czyli obiekt przechowujący stan obiektu. Jest ściśle powiązane z obiektem 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 zainteresować funkcja czyLegalnyWywolujacy(). To cudo wywołuje (prawdopodobnie) zasobożerną funkcję debug_backtrace i sprawdza kto 2 kroki wcześniej poprosił o wywołanie funkcji. Jeżeli jest to kto inny niż obiekt typu JakisObiekt to się obraża i daje sygnał do wywalenia wyjątku. Fajne, co?

Dla sprawdzenia czy memento jest w stanie przyjmować żądania od obiektów dziedziczonych po JakisObiekt tworzymy sobie obiekt Dziedziczony.

class Dziedziczony extends JakisObiekt { }

Na koniec podaję kod przechowywacza, który zajmuje się zapisem kolejnych wersji i odtwarzaniem ich. Po niewielkich przeróbkach kod może stać się w zasadzie 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 sprawdza czy klucz z haszem podanego w parametrze obiektu w ogóle istnieje. Jeżeli tak to przywraca stan, a jeżeli nie to wypisuje komunikat. Na koniec mamy bezparametrową metodę oczyscRejestrMemento(). Jeżeli przez przechowywacza przeszło wiele obiektów, robi się śmietnik kluczy niezawierających stanów do przywrócenia.

Czytaj dalej tutaj (rozwija treść wpisu)
Czytaj dalej na blogu autora...

Autor wpisu: Athlan, dodany: 02.01.2011 12:19, tagi: mysql, sql

Składowanie danych w kilku tabelach połączonych relacyjnie to bardzo dobry pomysł. Chyba najprostszym przykładem jest forum dyskusyjne: struktura oraz content postów mogą spokojnie być trzymane w osobnych tabelach. To samo tyczy się danych użytkowników. Rekordy rozbite na kilka tabel stają się mniej rozbudowane, o ile w ogóle występują - istnienie zawartości pola nie jest wtedy wymagane (użytkownik nie podał danych = nie ma rekordu).

Zaznaczając JOIN‘ujemy tabele w zależności od potrzeb, co zdarza się bardzo często. O UPDATE JOIN już wspominałem, też bardzo wygodna operacja, natomiast, co w przypadku, gdy musimy usunąć rekord uzależniony od wartości pola w innej tabeli? Sprawa jest banalnie prosta.

Na początek kilka technicznych uwag, na które łatwo można się nadziać:

  • Najczęściej będziemy mieli przypadek, w którym wartość pola musi być znana = rekord w tabeli obok musi istnieć. Nie zapomnijmy o pełnym złączeniu tabel INNER JOIN.
  • Gdy czyścimy śmieci w bazie danych, chcemy, aby wartość pola była konkretna lub niezdefiniowana, tabele możemy złączyć lewostronnie LEFT JOIN.
  • Składnia DELETE FROM jest bardzo podobna do SELECT. Zaznaczamy alias_tabeli.* jako wybór rekordu do usunięcia. Możemy usuwać rekordy z kilku tabel oddzielając zaznaczenia przecinkami. Przy JOIN’ach wymagane jest zdefiniowanie aliasów i sprecyzowanie, co chcemy usunąć alias_tabeli.* (edit: nie jest wymagane definiowanie aliasów, przykłady niżej)

Przykłady.

Dane userów mam składowane w dwóch tabelach – w jednej podstawowe dane (id, name, pass, pass_salt, mail, status_active), w kolejnej dane (data [jako handler user -> user_data], data_* [* - jakieśdane]). Chcę usunąć wszystkich użytkowników, którzy zarejestrowali się przed 48-godzinami i nie aktywowali swoich kont, aby zwolnić unikalne nazwy użytkowników i adresy email. Jednym kryterium jest user_status_active z tabeli users, kolejnym jest data user_data_join z tabeli users_data. Jako, że mam założony kaskadowy foregin key na pole user_data w tabeli users_data, przy usunięciu rekordu z tabeli users pozbędę się również jego danych, o co dbać nie muszę przy wypisywaniu alias_tabeli.*. W przypadku, kiedy nie miałbym założonego foregin key, musiałbym obsłużyć usunięcie rekordu z users_data wypisując po przecinku tabelę. Zatem:

DELETE item.* FROM `cms_members` AS `item`
INNER JOIN `cms_members_data` AS `item_data` ON (item.user_id = item_data.user_data)
WHERE item.user_state_active = 0 AND item_data.user_data_join &lt; NOW()

~Tiraeth przesłał rozwiązanie beż użycia aliasów i słowa kluczowego JOIN, odwołujemy się po nazwie tabeli:

DELETE cms_members.* FROM `cms_members`, `cms_members_data`
WHERE cms_members.user_id = cms_members_data.user_data AND user_state_active = 0 AND user_data_join &lt; NOW()

Podzapytanie oraz INNER JOIN generuje nam iloczyn kartezjański:

INNER JOIN and , (comma) are semantically equivalent in the absence of a join condition: both produce a Cartesian product between the specified tables (that is, each and every row in the first table is joined to each and every row in the second table).

Mam nadzieję, że komuś się przyda.

Wszystkie wpisy należą do ich twórców. PHP.pl nie ponosi odpowiedzialności za treść wpisów.