Toffiak blog

PHP predefiniowane interfejsy

W tym artykule przedstawię wam dostępne w PHP gotowe interfejsy. Interfejsy te umożliwiają iterowania po dowolnych stworzonych obiektach, dostęp do stworzonych obiektów w sposób "tablicowy" oraz możliwość serializacji każdego z nich.

Wstęp

Na potrzeby tego artykułu utworzymy klasę koszyka ( Cart ) oraz klasę produktu ( Product ), każda klasa będzie umieszczona w osobnym pliku. Klasy będą ładowane przy pomocy jednego z dostępnych, gotowych autoloaderów: http://gist.github.com/221634, podany autoloader można zastąpić dowolnym innym np.: autoloaderem Symfony https://github.com/symfony/symfony/tree/master/src/Symfony/Component/ClassLoader.

Klasa produktu

Klasa produktu jest bardzo prosta, składa się tylko z dwóch właściwości: name ( nazwy produktu ) i price ( ceny ), kontruktora oraz metody __toString.

        <?php

        namespace Toffiak\PredefinedInterfaces;

        class Product
        {

            public $name;
            public $price;

            public function __construct($name, $price)
            {
                $this->name = $name;
                $this->price = $price;
            }

            public function __toString()
            {
                return $this->name;
            }           

        }

    

Klasa koszyka

Klasa koszyka zawiera jedynie tablicę przechowującą produkty: products oraz metodę dodającą produkty do koszyka.

        <?php

        namespace Toffiak\PredefinedInterfaces;

        use Toffiak\PredefinedInterfaces\Product;

        class Cart
        {

            private $products = array();

            public function addProduct(Product $product)
            {
                $this->products[] = $product;
            }

        }

    

Interfejs ArrayAccess

Interfejs ArrayAccess pozwala na korzystanie z obiektu klasy Product tak jakby był on tablicą. Aby nasz obiekt zachowywał się w ten sposób należy zaimplementować metody:

  • offsetExists - sprawdzanie czy istnieje właściwość klasy Product o podanej nazwie
  • offsetGet - pobiera właściwość klasy Product o podanej nazwie
  • offsetSet - ustawia właściwość klasy Product o podanej nazwie
  • offsetUnset - usuwa właściwość klasy Product o podanej nazwie

W oficjalnej dokumentacji interfejsu podano przykład w którym manipulowano prywatną właściwością klasy będącą tablicą. W naszym przypadku będziemy jednak manipulować właściwościami produktu.


Zmodyfikowana klasa Product wraz z zaimplementwanym intefejsem ArrayAccess wygląda następująco:

        <?php
        
        namespace Toffiak\PredefinedInterfaces;

        class Product implements \ArrayAccess
        {

            public $name;
            public $price;

            public function __construct($name, $price)
            {
                $this->name = $name;
                $this->price = $price;
            }

            public function __toString()
            {
                return $this->name;
            }

            public function offsetExists($offset)
            {
                return isset($this->{$offset});
            }

            public function offsetGet($offset)
            {
                if (!$this->offsetExists($offset)) {
                    throw new \Exception(\sprintf('Property "%s" does not exists in "' . __CLASS__ . '" class', $offset));
                }

                return $this->{$offset};
            }

            public function offsetSet($offset, $value)
            {
                $this->{$offset} = $value;
            }

            public function offsetUnset($offset)
            {
                unset($this->{$offset});
            }

        }
    

Mając zaimplementowany interfejs ArrayAccess w klasie Product możemy go przetestować, w pierwszej kolejności tworzymy obiekt klasy wraz z dodaniem jego właściwości a następnie odczytujemy je tak jakby nasz obiekt był tablicą, w dalszej kolejności usuwamy wszystkie jego właściwości i próbujemy odczytać jedną z nich. Odczyt oczywiście się nie powiedzie ponieważ chwilę wcześniej usunęliśmy wszystkie właściwości i zgodnie z naszymi oczekiwaniami zostanie zgłoszony błąd.

Na samym końcu ponownie ustawiamy właściwości produktu: name oraz price i odczytujemy je.

        <?php
        
        \error_reporting(E_ALL ^ E_STRICT);

        require_once __DIR__ . DIRECTORY_SEPARATOR . 'SplClassLoader.php';
        $loader = new SplClassLoader(null, 'src');
        $loader->register();

        use Toffiak\PredefinedInterfaces\Cart;
        use Toffiak\PredefinedInterfaces\Product;

        print 'Tworzenie produktu i pobieranie jego właściwości' . PHP_EOL;
        $monitor = new Product('Benq G2222HD', 700);
        print $monitor['name'] . PHP_EOL;
        print $monitor['price'] . PHP_EOL;

        print 'Usuwanie właściwości' . PHP_EOL;
        unset($monitor['name']);
        unset($monitor['price']);
        try {
            print $monitor['name'] . PHP_EOL;
        } catch (\Exception $e) {
            print $e->getMessage() . PHP_EOL;
        }
        print 'Ustawianie właściwości produktu' . PHP_EOL;
        $monitor['name'] = 'BenQ G2222HDL';
        $monitor['price'] = 750;
        print $monitor['name'] . PHP_EOL;
        print $monitor['price'] . PHP_EOL;
    
        Tworzenie produktu i pobieranie jego właściwości
        Benq G2222HD
        700
        Usuwanie właściwości
        Property "name" does not exists in "Toffiak\PredefinedInterfaces\Product" class
        Ustawianie właściwości produktu
        BenQ G2222HDL
        750
    

Interfejs Iterator

Na początek zacznijmy od tego czym jest iterator ?. Iterator to obiekt służący do przemieszczania się po wybranej kolekcji, kolekcją może być np: tablica, lista rekordów z bazy danych a w naszym przypadku będzie to koszyk z produktami.

Interfejs Iterator posiada 5 prostych metod które musimy zaimplementować w naszej klasie Cart aby można było po nim swobodnie iterować, są to kolejno:

  • rewind - przesuwa iterator na początek
  • current - zwraca aktualny element
  • key - zwraca klucz aktualnego elementu
  • next - przesuwa iterator do następnego elementu
  • valid - sprawdza czy aktualna pozycja jest poprawna

Iterując po naszym koszyku oczekujemy aby kolejno zwracane były produkty z naszego koszyka, iterować możemy wywołując metody iteratora lub za pomocą pętli foreach, na poniższym przykładzie pokazany jest ten drugi sposób.

        <?php

        namespace Toffiak\PredefinedInterfaces;

        use Toffiak\PredefinedInterfaces\Product;

        class Cart implements \Iterator
        {

            private $products = array();

            public function addProduct(Product $product)
            {
                $this->products[] = $product;
            }

            function rewind()
            {
                return reset($this->products);
            }

            function current()
            {
                return current($this->products);
            }

            function key()
            {
                return key($this->products);
            }

            function next()
            {
                return next($this->products);
            }

            function valid()
            {
                return key($this->products) !== null;
            }

        }
    

Po dodaniu powyższych metod do naszej klasy, testujemy całość.

Tworzymy 3 produkty i umieszczamy je w koszyku a następnie przy pomocy pętli foreach iterujemy po nim za każdym otrzymując kolejny produkt. Każdy z otrzymanych produktów jest na końcu wyświetlany.

        <?php

        \error_reporting(E_ALL ^ E_STRICT);

        require_once __DIR__ . DIRECTORY_SEPARATOR . 'SplClassLoader.php';
        $loader = new SplClassLoader(null, 'src');
        $loader->register();        

        use Toffiak\PredefinedInterfaces\Cart;
        use Toffiak\PredefinedInterfaces\Product;

        print 'Tworzymy 3 produkty i umieszczamy je w koszyku' . PHP_EOL;
        $graphicCard = new Product('Radeon 7750', 350);
        $soundCard = new Product('Sound blaster Live', 50);
        $monitor = new Product('BenQ G2222HDL', 750);
        $cart = new Cart();
        $cart->addProduct($graphicCard);
        $cart->addProduct($soundCard);
        $cart->addProduct($monitor);
        foreach ($cart as $product) {
            print $product . PHP_EOL;
        }

    
        Tworzymy 3 produkty i umieszczamy je w koszyku
        Radeon 7750
        Sound blaster Live
        BenQ G2222HDL
    

Interfejs IteratorAggregate

W poprzednim przykładzie stworzyliśmy własny iterator aby móc "przemieszczać" się po naszym koszyku. Interfejs IteratorAggregate umożliwa nam wybranie który iterator zostanie użyty podczas iteracji po kolekcji.

Dzięki temu możemy stworzyć zewnętrzny iterator o nazwie CartIterator i użyć go do iterowania po koszyku. CartIterator także implementuje interfejs Iterator więc metody są takie same, jedyną różnicą w porównaniu z poprzednim przykładem jest to że produkty przekazujemy mu bezpośrednio.

        <?php

        namespace Toffiak\PredefinedInterfaces;

        class CartIterator implements \Iterator
        {

            private $products;

            function __construct($products)
            {
                $this->products = $products;
            }

            function rewind()
            {
                return reset($this->products);
            }

            function current()
            {
                return current($this->products);
            }

            function key()
            {
                return key($this->products);
            }

            function next()
            {
                return next($this->products);
            }

            function valid()
            {
                return key($this->products) !== null;
            }

        }

    

Interfejs IteratorAggregate posiada tylko jedną metodę którą należy zaimplementować mianowicie getIterator, metoda ta zwraca obiekt iteratora który zostanie użyty do iteracji naszego koszyka.

        <?php
        
        namespace Toffiak\PredefinedInterfaces;

        use Toffiak\PredefinedInterfaces\Product;
        use Toffiak\PredefinedInterfaces\CartIterator;

        class Cart implements \IteratorAggregate
        {

            private $products = array();

            public function addProduct(Product $product)
            {
                $this->products[] = $product;
            }

            public function getIterator()
            {
                return new CartIterator($this->products);
            }

        }

    

Podobnie jak poprzednio tworzymy 3 produkty i wstawiamy je do koszyka a następnie za pomocą pętli foreach wyświetlamy je.

        <?php

        \error_reporting(E_ALL ^ E_STRICT);

        require_once __DIR__ . DIRECTORY_SEPARATOR . 'SplClassLoader.php';
        $loader = new SplClassLoader(null, 'src');
        $loader->register();        

        use Toffiak\PredefinedInterfaces\Cart;
        use Toffiak\PredefinedInterfaces\Product;

        print 'Tworzymy 3 produkty i umieszczamy je w koszyku' . PHP_EOL;
        $smartphoneNokia = new Product('Nokia Lumia 820', 700);
        $smartphoneGalaxy = new Product('Samsung Galaxy S5', 2350);
        $smartphoneIphone = new Product('iPhone 5S', 3200);
        $cart = new Cart();
        $cart->addProduct($smartphoneNokia);
        $cart->addProduct($smartphoneGalaxy);
        $cart->addProduct($smartphoneIphone);
        foreach ($cart as $product) {
            print $product . PHP_EOL;
        }

    
        Tworzymy 3 produkty i umieszczamy je w koszyku
        Nokia Lumia 820
        Samsung Galaxy S5
        iPhone 5S
    

Dlaczego więc warto korzystać z IteratorAggregate zamiast za każdym razem implementować własny iterator ? Otóż iteracja po tablicach jest tak często spotykana że istnieje już gotowy iterator służący do tego: ArrayIterator i robi on dokładnie to samo co nasz CartIterator, oprócz tego istnieje dużo więcej gotowych iteratorów http://www.php.net/manual/en/spl.iterators.php które można dowolnie wykorzystać.

Interfejs Serializable

Serializacja jest procesem zamiany danej wartości na taką która może być przechowywana. Łańcuchy znaków czy liczby bardzo łatwo można zapisać w pliku lub umieścić w bazie, tablicę czy obiekty są dużo bardziej skomplikowane, aby móc jej zapisać a następnie poprawnie odczytać należy zapisać całą ich wewnętrzną strukturę.


Interfejs Serializable posiada 2 metody: serialize i unserialize, pierwsza służy do serializacji danych natomiast druga do deserializacji. W naszym przypadku serializować będziemy cały koszyk, nasz koszyk składa się jedynie z produktów więc to tablicę z produktami będziemy serializować i deserializować.

        <?php

        namespace Toffiak\PredefinedInterfaces;

        use Toffiak\PredefinedInterfaces\Product;
        use Toffiak\PredefinedInterfaces\CartIterator;

        class Cart implements \IteratorAggregate, \Serializable
        {

            private $products = array();

            public function addProduct(Product $product)
            {
                $this->products[] = $product;
            }

            public function getIterator()
            {
                return new CartIterator($this->products);
            }

            public function serialize()
            {
                return \serialize($this->products);
            }

            public function unserialize($serialized)
            {
                $this->products = \unserialize($serialized);
            }

        }

    

Aby przetestować koszyk z zaimplementowanym interfejsem Serializable dodajemy do niego 3 dowolne produkty a cały koszyk poddajemy procesowi serializacji i wyświetlamy.

Zserializowany koszyk jest następnie poddawany deserializacji dzięki czemu otrzymujemy nasz oryginalny koszyk który ponownie wyświetlamy.

        <?php

        \error_reporting(E_ALL ^ E_STRICT);

        require_once __DIR__ . DIRECTORY_SEPARATOR . 'SplClassLoader.php';
        $loader = new SplClassLoader(null, 'src');
        $loader->register();        

        use Toffiak\PredefinedInterfaces\Cart;
        use Toffiak\PredefinedInterfaces\Product;

        print 'Tworzymy 3 produkty i umieszczamy je w koszyku' . PHP_EOL;
        $ford = new Product('Ford Focus MK II', 13500);
        $opel = new Product('Opel Vectra', 5000);
        $mercedes = new Product('Mercedes C200', 22300);
        $cart = new Cart();
        $cart->addProduct($ford);
        $cart->addProduct($opel);
        $cart->addProduct($mercedes);
        $serializedCart = \serialize($cart);
        print 'Zserializowany koszyk' . PHP_EOL;
        print $serializedCart;
        print PHP_EOL . 'Odserializowanie koszyka' . PHP_EOL;
        $unserializedCart = \unserialize($serializedCart);
        print 'Wyświetlanie produktów koszyka' . PHP_EOL;
        foreach ($unserializedCart as $product) {
            print $product->name . ' ' . $product->price . PHP_EOL;
        }

    
        Tworzymy 3 produkty i umieszczamy je w koszyku
        Zserializowany koszyk
        C:33:"Toffiak\PredefinedInterfaces\Cart":318:{a:3:{i:0;O:36:"Toffiak\PredefinedInterfaces\Product":2:{s:4:"name";s:16:"Ford Focus MK II";s:5:"price";i:13500;}i:1;O:36:"Toffiak\PredefinedInterfaces\Product":2:{s:4:"name";s:11:"Opel Vectra";s:5:"price";i:5000;}i:2;O:36:"Toffiak\PredefinedInterfaces\Product":2:{s:4:"name";s:13:"Mercedes C200";s:5:"price";i:22300;}}}
        Odserializowanie koszyka
        Wyświetlanie produktów koszyka
        Ford Focus MK II 13500
        Opel Vectra 5000
        Mercedes C200 22300
    

Interfejs Traversable

Interfejs Traversable może być wykorzystywany tylko przez wbudowane klasy, klasy tworzone przez programistów mogą implementować jedynie interfejsy IteratorAggreagte lub Iterator, które to dziedzicą z interfejsu Traversable. Próba implementacji tego interfejsu w tworzonej klasie zakończy się zgłoszeniem wyjątku. Za pomocą tego interfejsu można jednak sprawdzić czy obiekty klasy można użyć w pętli foreach.

Poniżej sprawdzimy to dla naszego koszyka oraz produktu, jak wiemy tylko koszyk implementuje interfejs IteratorAggregate więc tylko on umożliwia iterowanie po nim.

        <?php

        \error_reporting(E_ALL ^ E_STRICT);

        require_once __DIR__ . DIRECTORY_SEPARATOR . 'SplClassLoader.php';
        $loader = new SplClassLoader(null, 'src');
        $loader->register();        

        use Toffiak\PredefinedInterfaces\Cart;
        use Toffiak\PredefinedInterfaces\Product;

        print 'Tworzymy pusty koszyk oraz przykładowy produkt' . PHP_EOL;
        $cart = new Cart();
        $smartphone = new Product('Nokia Lumia 520', 300);
        print 'Sprawdzamy czy można iterować po obiekcie klasy Cart' . PHP_EOL;
        print ( $cart instanceof \Traversable) ? 'tak' : 'nie';
        print PHP_EOL;
        print 'Sprawdzamy czy można iterować po obiekcie klasy Product' . PHP_EOL;
        print ( $smartphone instanceof \Traversable) ? 'tak' : 'nie';
        print PHP_EOL;
    
        Tworzymy pusty koszyk oraz przykładowy produkt
        Sprawdzamy czy można iterować po obiekcie klasy Cart
        tak
        Sprawdzamy czy można iterować po obiekcie klasy Product
        nie
    

Komentarze

blog comments powered by Disqus