Home / Brudnopis / Wzorce w praktyce: Adapter

Wzorce w praktyce: Adapter 28.12.2009

tagi: php symfony ZF wzorce projektowe

Mój pierwszy wpis, a zarazem pierwszy wpis (mam nadzieję) z serii "Wzorce w praktyce". W tym artykule przybliżę praktyczne zastosowanie wzorca projektowego adapter na przykładzie wykorzystania klas walidacji z Zend Framework w symfony. Być może nie jest to w 100% trafiony przykład praktycznego zastosowania wzorca, gdyż symfony ma odpowiedniki walidatorów z ZF, pozatym w sf walidatory pełnią jeszcze funkcję filtrów.

Nie będe się rozpisywał na temat teorii związanej z tym wzorcem, ale opiszę w dwóch zdaniach jego ideę. Adapter służy do zaadaptowania (przystosowania) klas wykorzystujące określone API do współdziałania z klasami wykorzystujące inne API. W świecie rzeczywistym adapter to przejściówka, np. przejściówka do wtyczki gniazdka angielskiego na polskie. W tym artykuje będe budował / pisał przejściówkę z klas wykorzystujących API Zend_Validate_Abstract na klasy wykorzystujące API sfValidatorBase. Więcej o adapterze znajdziecie w google.

Pierwszym krokiem jaki trzeba wykonać to prównanie API.

Na początek API do którego dążymy, czyli to z symfony.
[PHP]
  1. //skrócone api sfValidatorBase
  2. class sfValidatorBase
  3. {
  4. public function __construct($options = array(), $messages = array());
  5.  
  6. //metody do zarządzania opcjami walidatora
  7. public function getOption($name);
  8. public function setOption($name, $value);
  9. public function addOption($name, $value);
  10. public function hasOption($name);
  11. public function getOptions();
  12. public function setOptions($values);
  13.  
  14. //metoda walidująca
  15. public function clean($value);
  16.  
  17. //metody do zarządzania komunikatami błędów
  18. public function getMessage($name);
  19. public function addMessage($name, $value);
  20. public function setMessage($name, $value);
  21. public function getMessages();
  22. public function setMessages();
  23. public function getDefaultMessages();
  24. protected function setDefaultMessages($values);
  25. }


API które chcemy przystosować do tego wyżej zaprezentowanego.
[PHP]
  1. class Zend_Validate_Abstract
  2. {
  3. public function __constructor($arg1, $arg2, $arg3, ...);
  4.  
  5. //metoda walidująca
  6. public function isValid($value);
  7.  
  8. //metody do zarządzania komunikatami błędów
  9. public function getMessages();
  10. public function getMessageVariables();
  11. public function getMessageTemplates();
  12. public function setMessage($value, $key = null);
  13. public function setMessages(array $messages);
  14.  
  15. //metody do zarządzania opcjami (Xxx - nazwa opcji)
  16. public function getXxx();
  17. public function setXxx($value);
  18. }


Zastosowanie klasy, którą będziemy tworzyć, ma wyglądać następująco:
[PHP]
  1. //w metodzie configure() formularza sfForm
  2. $this->setValidators(array(
  3. 'name' => new sfValidatorZFAdapter('Zend_Validate_StringLength', array(
  4. 'min' => 2,
  5. 'max' => 12,
  6. 'required' => true
  7. ), array(
  8. 'stringLengthTooShort' => 'Wprowadzona nazwa jest za krótka',
  9. 'stringLengthTooLong' => 'Wprowadzona nazwa jest za długa',
  10. )),
  11. 'email' => new sfValidatorZFAdapter('Zend_Validate_EmailAddress', array('required' => false)),
  12. ));


Pierwszą różnicą w API tych obydwóch interfejsów to sposób przekazywania argumentów do konstruktora. W symfony pierwszy argument to tablica opcji, zaś drugi to komunikaty błędów. W ZF opcje są przekazywane pojedyńczo jako kolejne argumenty konstruktora. Rozwiązaniem tego probelmu to:

a) Wykorzystanie refleksji do utworzenia obiektu walidatora ZF z opcjami podanymi do konstruktora adaptera, jako argumenty konstruktora
b) Wywołanie domyślnego konstruktora, opcje ustawiać pojedyńczo: w ZF każda opcja ma metody dostępowe getXxx oraz setXxx, możemy je wykorzystać do ustawienia opcji po zainicjowaniu obiektu.

Ja osobiście wybrałem wariant b), gdyż zaprzeganie refleksji do tak prostej czynności mija się z celem.

Kod konstruktora:
[PHP]
  1. //ciach...
  2. public function __construct($cls, $options = array(), $messages = array())
  3. {
  4. //obiekt Zendowskiego walidatora, można również sprawdzić typ tej klasy czy oby
  5. //na pewno jest to klasa Zend_Validate_Abstract
  6. $this->validator = new $cls();
  7. $this->validator->setMessages($messages);
  8.  
  9. //W ZF inaczej jest zrealizowane sprawdzanie czy dane pole jest wymagane,
  10. //dzieje się to na wysokości formularza, a nie walidatora, więc trzeba
  11. //w adapterze dodać tą funkcjonalność
  12. if(isset($options['required']))
  13. {
  14. $this->required = (bool) $options['required'];
  15. unset($options['required']);
  16. }
  17.  
  18. foreach($options as $name => $value)
  19. {
  20. $this->setOption($name, $value);
  21. }
  22. }
  23.  
  24. public function setOption($name, $value)
  25. {
  26. //camelize to metoda formatująca nazwę metody, zamienia np.
  27. //xxx_xxx na XxxXxx
  28. $method = 'set'.self::camelize($name);
  29.  
  30. method_exists($this->validator, $method) ? $this->validator->$method($value) : null;
  31. }
  32.  
  33. public function setMessage($name, $value)
  34. {
  35. $this->validator->setMessage($value, $name);
  36. }
  37. //ciach...


Kolejnym krokiem jest obsługa walidacji. W symfony metoda wykonująca walidację nazywa się "clean", w ZF zaś "isValid". Argumenty mają takie same, jednak różnią się zwracaną wartością oraz sposobem sygnalizacji niepowodzenia w procesie walidacji. Klasy sfValidatorBase zwracają (przefiltrowany) ciąg wejściowy, jeśli walidacja zakończyła się sukcesem, w przeciwnym wypadku wyrzuca wyjątek sfValidatorError. Klasy Zend_Validate_Abstract zwracają true lub false odpowienio w przypadku powodzenia lub błędu. Pozatym (patrz: komentarz w poprzednim listingu) symfony i ZF różnią się miejscem sprawdzania, czy dane pole jest wymagane. Przy pisaniu adaptera trzeba mieć to na uwadze.

[PHP]
  1. //nie wykorzystujemy metody doClean(), unieważniamy ją tak aby wyrzucała wyjątek złego użycia
  2. public function clean($value)
  3. {
  4. //dodanie obsługi sprawdzania tego, czy dane pole jest wymagane
  5. if ($this->isEmpty($value))
  6. {
  7. if ($this->required)
  8. {
  9. throw new sfValidatorError($this, 'required');
  10. }
  11.  
  12. return $value;
  13. }
  14.  
  15. $result = $this->validator->isValid($value);
  16.  
  17. //podano niepoprawną wartość, utwórz i wyrzuć wyjątek
  18. if(!$result)
  19. {
  20. $errors = $this->validator->getMessages();
  21. $errorValue = 'invalid';
  22. if(count($errors) > 0)
  23. {
  24. $errorValue = current($errors);
  25. }
  26.  
  27. throw new sfValidatorError($this, $errorValue);
  28. }
  29.  
  30. return $value;
  31. }


Podane listingi wystarczą aby podstawowe funkcjonalności walidatorów ZF przenieść do symfony, jednakże aby adapter był kompletny trzeba nadpisać również szereg innych metod:
a) do zarządzania (ustawiania, pobierania) komunikatów błędów
b) reszta metod do zarządzania opcjami

Kod źródłowy: pobierz

Podsumowanie
Adapter stosujemy gdy klasę lub zbiór klas obcego pochodzenia chcemy wykorzystać w naszym projekcie, w którym do realizowania tej funkcjonalności co ten zewnętrzy kod, wykorzystywane jest inne api. Przykład przedstawiony w tym wpisie można uogólnić na większość frameworków. Jeśli masz swój własny framework i brakuje ci niektórych walidatorów, to zamiast pisać kilka klas, wystarczy że napiszesz jedną (adapter) i wykorzystasz już ogólnie dostępny kod. Taka idea oczywiście nie ogranicza się tylko na walidatory, to jest tylko luźny przykład aby zrozumieć praktyczne zastosowanie tego wzorca.

Komentarze (0)

Brak komentarzy.

Dodaj komentarz