DTO – wprowadzenie i charakterystyka

Podziel się z innymi!

    Wprowadzenie

    Data Transfer Object w skrócie DTO jest wzorcem projektowym należącym do grupy wzorców dystrybucji. Podstawowym zadaniem DTO jest transfer danych pomiędzy systemami, aplikacjami lub też w ramach aplikacji pomiędzy warstwami aplikacji, modułami lub też w każdej dowolnej sytuacji, w której transfer danych jest konieczny. DTO – najprościej rzecz ujmując – jest bardziej rozbudowanym kontenerem na dane wzbogaconym o dodatkowe możliwości ułatwiające dostęp, ochronę oraz przesyłanie danych.

    Dobrze skonstuowany DTO staje się użytecznym narzędziem jako taki lub też może stać się podstawą konstrukcyjną wielu elementów składających się na większy framework.

    Dzięki klasie ArrayObject ze standardowej biblioteki SPL dostępnej w PHP w wersji 5 oraz metodom magicznym możemy zdefiniować klasę umożliwiającą powoływanie do życia obiektów mających zarówno wiele właściwości tablic, jak i obiektów.

    <?php
    class bigWeb_Dto extends ArrayObject implements Serializable {
        public function __construct($properties = null){
            parent::__construct(array(), ArrayObject::STD_PROP_LIST);
            if ($properties) {
                $this->append($properties);
            }
        }
     
        public function __get($property){
            return $this->offsetGet($property);
        }
     
        public function __set($property, $value) {
            return $this->offsetSet($property, $value);
        }
     
        public function __isset($property) {
            return parent::offsetExists($property);
        }
     
        public function __unset($property) {
            return $this->offsetUnset($property);
        }
     
        public function offsetGet($property) {
            return (parent::offsetExists($property)) 
                ? parent::offsetGet($property) : null;
        }
     
        public function offsetSet($property, $value) {
            parent::offsetSet($property, $value);
            return $this;
        }
     
        public function offsetUnset($property)
        {
            if (parent::offsetExists($property)) {
                parent::offsetUnset($property);
            }
            return $this;
        }
     
        public function append($properties) {
            if ((is_array($properties) || $properties instanceOf ArrayObject) 
                && !empty($properties))
            {
                foreach($properties as $_name => $_value) {
                    $this->offsetSet($_name, $_value);
                }
            }
        }
     
        public function getArrayCopy() {
            return $this->_getRecursiveArrayCopy(parent::getArrayCopy());
        }
     
        protected function _getRecursiveArrayCopy($array) {
            $_array = array();
            foreach($array as $_key=>$_value) {
                if ($_value instanceOf ArrayObject) {
                    $array[$_key] = $this->_getRecursiveArrayCopy(
                        $_value->getArrayCopy());
                }
            }
            return $array;
        }
     
        public function serialize() {
            return serialize($this->getArrayCopy());
        }
     
        public function unserialize($data) {
            $this->exchangeArray(unserialize($data));
        }
     
        public function __toString() {
            return sprintf('%s%s', 
                print_r($this, true), 
                print_r($this->getArrayCopy(), true));
        }
    }

    Klasa ta w swojej podstawowej formie jest bardziej rozbudowaną wersją tablicy, a jej użyteczność – nie licząc możliwości serializacji – polega głównie na stworzeniu kilku metod dostępu do danych. Jednakże, jest ona jedynie punktem wyjścia do budowy bardziej zaawansowanych rozwiązań daleko wykraczających poza możliwości zwykłych tablic. Zanim przedstawię potencjalne pola zastosowań stworzonego przeze mnie narzędzia chciałbym omówić kilka jego cech i właściwości.

    Podstawowa charakterystyka

    Utworzenie objektu i dodanie danych

    Nową instancję można zainicjalizować przekazując do konstruktora istniejącą tablicę, ale można też utworzyć „pusty” objekt.

    $dto = new bigWeb_Dto(array("a" => 1));

    Dane w kontenerze można umieszczać wykorzystując składnię obiektów lub tablic. Każdej porcja danych ma swój indeks, dzięki któremu będzie można później te dane wyciągnąć z „opakowania”

    $dto->b = 2;
    $dto["c"] = 3;

    Możliwe jest też obiema metodami ustawianie zmiennych o indeksach liczbowych, jednak chcąc użyć składni charakterystycznej dla obiektów konieczne jest w tym wypadku użycie nawiasów klamrowych.

    $dto->{0} = 4;
    $dto[1] = 5;

    Analogicznie do tablic mamy też możliwość przypisania określonej wartości do pierwszego wolnego indeksu liczbowego.

    $dto[] = 6;

    Wyciąganie danych z obiektu

    Mając już utworzony obiekt możemy wyciągnąć wartości przypisane do poszczególnych zmiennych. Także w tym wypadku mamy do dyspozycji zarówno sposób obiektowy, jak i tablicowy.

    $dto->b;
    // int 2
     
    $dto["c"];
    // int 3
     
    $dto->{0};
    // int 4
     
    $dto[1];
    //int 5

    Interfejs obiektu ArrayObject przewiduje metodę „getArrayCopy” umożliwiającą sprowadzenie obiektu do postaci czystej tablicy. Dzięki czemu jeśli w jakimkolwiek momęcie użycie tablicy stanie się bardziej wygodne lub też użyteczne mamy możliwość operowania na tablicy, którą po przekształceniach można oczywiście znów zapakować do objektu DTO

    $dto->getArrayCopy();
    /*
    array
      'a' => int 1
      'b' => int 2
      'c' => int 3
      0 => int 4
      1 => int 5
      2 => int 6
    */

    Sprawdzanie czy pod danym indeksem kryją się jakieś dane

    Do sprawdzenia istnienia pewnych zmiennych standardowo używa się funkcji „isset”. Taką możliwość mamy równierz w tym przypadku.

    isset($dto->a);
    // boolean true
     
    isset($dto["b"]);
    // boolean true
     
    isset($dto->{0});
    // boolean true
     
    isset($dto[1]);
    // boolean true
     
    isset($dto->e);
    // boolean false
     
    isset($dto[3]);
    // boolean false

    Należy zwrócić uwagę na działanie funkcji „isset” w sytuacji przypisania do zmiennej wartości NULL. W przypadku standardowych tablic, jeżeli do jakiegoś klucza przpiszemy wartość null to użycie funkcji „isset” zwróci wynik negatywny.

    $a = array("foo" => null);
    isset($a["foo"]);
    // boolean false

    Jeśli to samo zrobimy z obiektem ArrayObject otrzymamy wynik zgoła inny. Funkcja „isset” na obiekcie ArrayObject działa tak jak funkcja „array_key_exists” na standardowej tablicy czyli tak naprawdę sprawdza istnienie klucza bez uwzględniania wartości.

    $aO = new ArrayObject(array("foo" => null));
    isset($aO["foo"]);
    // boolean true

    Moglibyśmy oczywiście sprawić aby działanie funkcji „isset” na naszym obiekcie DTO było identyczne jak na klasycznej tablicy przedefiniowując metodę offsetExists należącą do interfejsu ArrayObject. Ja jednak zdecydowałem się tego nie zmieniać i dlatego też:

    $dto->d = null;
    isset($dto->d);
    // boolean true
     
    $dtoArr = $dto->getArrayCopy();
    isset($dtoArr["d"]);
    // boolean false

    Usuwanie danych z tablicy i ich zliczanie

    Jak należało się domyślać usuwanie danych z obiektu DTO przeprowadza się przy pomocy funkcji „unset” z kolei policzyć je można przy pomocy „count”

    count($dto);
    // int 7

    Dla porównania…

    count($dto->getArrayCopy());
    // int 7

    Czas na usunięcie kilku elementów.

    unset($dto->c);
    unset($dto["d"]);
    unset($dto->{0});
    unset($dto[1]);
     
    count($dto);
    // int 3
    /*
    array
      'a' => int 1
      'b' => int 2
      2 => int 6
    */

    Iteracja po elementach tablicy

    Dzięki temu, że ArrayObject implementuje interface IteratorAggregate, oparty o niego obiekt DTO można użyć bezpośrednio w pętli foreach

    foreach ($dto as $key => $value) {
    	$dto[$key] = $value + 10;
    }
    /*
    array
      'a' => int 11
      'b' => int 12
      2 => int 16
    */

    Pewnym ograniczeniem jest możliwość usuwania elementów z obiektu w czasie iteracji. Poniższy kod spowoduje wyświetlenie ostrzeżenia „ArrayIterator::next(): Array was modified outside object and internal position is no longer valid …”, ale uzyskanie poprawnego wyniku.

    foreach ($dto as $key => $value) {
    	if ($value % 2 != 0) {
    		unset($dto[$key]);
    	}
    }
    /*
    array
      'b' => int 12
      2 => int 16
    */

    Objeściem problemu jest iteracja po tablicy i działanie na obiekcie.

    foreach ($dto->getArrayCopy() as $key => $value) {
    	if ($value % 2 != 0) {
    		unset($dto[$key]);
    	}
    }
    /*
    array
      'b' => int 12
      2 => int 16
    */

    Sortowanie

    Zmienne w naszym obiekcie DTO możemy posortować przy użciu kilku metod sortujących („asort”, „ksort”, „natsort”, „natcasesort”, „uasort”, „ukasort”). W tym przypadku nie możemy jednak użyć obiektu jak tablicy gdyż natywne funkcje sortujące nie przyjmą obiektu zamiast tablicy.

    $aO = new bigWeb_Dto(array("Zofia", "Joanna", "Krystyna", "Maria"));
    $bO = new bigWeb_Dto(array(1, 3, 5, 4, 2));
     
    $aO->natsort();
    /*
    array
      1 => string 'Joanna' (length=6)
      2 => string 'Krystyna' (length=8)
      3 => string 'Maria' (length=5)
      0 => string 'Zofia' (length=5)
    */
     
    $bO->natsort();
    /*
    array
      0 => int 1
      4 => int 2
      1 => int 3
      3 => int 4
      2 => int 5
    */
     
    $aO->ksort();
    /*
    array
      0 => string 'Zofia' (length=5)
      1 => string 'Joanna' (length=6)
      2 => string 'Krystyna' (length=8)
      3 => string 'Maria' (length=5)
    */
     
    $bO->ksort();
    /*
    array
      0 => int 1
      1 => int 3
      2 => int 5
      3 => int 4
      4 => int 2
    */

    Istnieje sporo sytuacji, kiedy zwykłe tablice okazują się być niewygodne w użyciu i kiedy trzeba sięgnąć po bardziej wyrafinowane środki. Okazuje się, że aby skorzystać z zalet obiektu DTO nie trzeba od razu projektować systemów rozproszonych, i że można go użyć w wielu sytuacjach – często w połączeniu z innymi wzorcami projektowymi.

    O zastosowaniach w kolejnym wpisie 🙂

    Podziel się z innymi!

      Dodaj komentarz

      Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *