Rozwiazywanie problemow przekrojowych z uzyciem IoC

Transkrypt

Rozwiazywanie problemow przekrojowych z uzyciem IoC
Dla zaawansowanych
Rozwiązywanie problemów
przekrojowych z użyciem IoC
Stopień trudności: lll
Piotr Szarwas
W każdej aplikacji podzielonej na warstwy
występują elementy, których nie można przypisać do żadnej z warstw i które stanowią twardy
orzech do zgryzienia nawet dla dużego zespołu programistów. Istnieje jednak w miarę prosty
sposób poradzenia sobie z nimi: użycie kontenera IoC...
K
W SIECI
XXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXX
XXXXX
2
ażdą aplikację można podzielić
na logiczne warstwy. Najczęściej
występują trzy warstwy: warstwa danych, logiki biznesowej i prezentacji. Wszystkie spośród nich powinny być
nałożone na siebie tak, aby wyższa wiedziała tylko o istnieniu jednej warstwy niższej; wszystko, co znajduje się poniżej,
powinno być dla niej niedostępne. Z drugiej strony, warstwa niższa nie wymaga
dla swojego działania istnienia żadnej z
warstw wyższych.
Podział ten ilustrujemy na Rysunku 1. Można jeszcze bardziej dopracować ten schemat mówiąc, że aplikacja
powinna posiadać aż pięć warstw: warstwę danych, dostępu do danych, logiki biznesowej/usług, kontrolerów i prezentacji. Ten drugi podział wynika bezpośrednio ze stosowania wzorców programistycznych i architektonicznych. Przykładowo, kontrolery i prezentacja stanowią element wzorca architektonicznego
www.phpsolmag.org
MVC. Pojawienie się warstwy dostępu
do danych wynika z zastosowania wzorca DAO, który uniezależnia logikę biznesową od warstwy danych. Dzięki temu
można łatwo wymieniać źródło danych.
Schemat tego podziału pokazujemy na
Rysunku 2.
Rozbicie systemu na warstwy zapewnia szereg korzyści:
Każdej z warstw można wymienić implementację, nie zmieniając jednocześnie implementacji pozostałych warstw.
Jak już wspomnieliśmy, można na przykład wymienić warstwę DAO (dostępu do
Co należy wiedzieć...
Należy znać zasady programowania
obiektowego w PHP5 z wykorzystaniem
wzorców projektowych.
Co obiecujemy...
Pokażemy, jak rozwiązać niektóre spośród problemów przekrojowych (ang.
crosscutting concerns).
PHP Solutions Nr 6/2006
Crosscutting concerns
�������������������
�������������������������
��������������
Niestety, praktyka bywa czasem bardziej skomplikowana i nie wszystkie zagadnienia programistyczne możemy
umieścić na jednej wybranej warstwie.
Te, których nie da się w ten sposób potraktować, nazywamy problemami przekrojowymi (ang. Crosscutting concerns).
Najpopularniejsze z nich to:
l
l
Rysunek 1. Podział aplikacji na trzy
logiczne warstwy. Warstwa wyższa wie
tylko o istnieniu warstwy znajdującej się
bezpośrednio pod nią. Żadna z warstw
niższych nie wie nic o wyższych
danych) tak, aby obsługiwała inne źródło
danych, nie zmieniając jednocześnie logiki biznesowej.
Każda warstwa może być traktowana
jako spójna całość. Pozwala to m.in. na
tworzenie każdej warstwy przez osobny
zespół programistów, który nie musi znać
implementacji pozostałych warstw.
Jeśli zostanie dobrze zaprojektowana, każda warwstwa(warstwa) może być
wykorzystywana wielokrotnie, w różnych
projektach lub miejscach tego samego
projektu.
logowanie błędów i debugowanie,
bezpieczeństwo i uwierzytelnianie,
transakcje.
�������������������
�������������������
�������������������������
�������������������������
Problem logowania
błędów
Rozważmy problem logowania błędów.
Praktycznie każda aplikacja zawiera elementy kodu odpowiadające za logowanie
i debugowanie. Czy jednak możemy je
przydzielić do którejś warstwy? Niestety
nie. Ich kod jest rozproszony po całej aplikacji i występuje w każdej warstwie. Pamiętajmy, że z punktu widzenia logicznego podziału na warstwy, logowanie błędów nie jest ani częścią logiki biznesowej,
ani innej warstwy. Bezpośrednie zakodowanie logiki logowania w każdej z warstw
powoduje, że niezależność, przenaszalność i elastyczność tej warstwy drastycznie maleje. W przypadku logowania problem nie jest tak wielki, ale już w sytuacji,
Listing 1. Implementacja klasy MailSender służącej do wysyłania maili. Kod klasy
pozbawiony jest reguł problemów przekrojowych: logowania i bezpieczeństwa.
Reguły te będą implementowane przez odpowiednie dekoratory
interface MailSender {
public function send($to, $from, $subject, $message);
}
class MailSenderImpl implements MailSender {
public function send($to, $from, $subject, $message) {
return mail($to,$subject,$message,'From: '.$from);
}
}
Listing 2. Przykładowa klasa dekorująca dowolną klasę implementującą interface
MailSender
class MailSenderDecorator implements MailSender {
private $mailSender;
public function __construct(MailSender $mailSender ){
$this->mailSender = $mailSender;
}
public function send($to, $from, $subject, $message) {
// Kod dekoratora
$result = $this->send($to,$subject,$message,'From: '.$from);
// Kod dekoratora
return $result;
}
}
PHP Solutions Nr 6/2006
Dla zaawansowanych
www.phpsolmag.org
��������������
Rysunek 2. Podział aplikacji na pięć
logicznych warstw. Warstwa wyższa wie
tylko o istnieniu warstwy znajdującej się
bezpośrednio pod nią. Żadna z warstw
niższych nie wie nic o wyższych
gdy w aplikacji zaszyjemy reguły bezpieczeństwa, czy też zdefiniujemy miejsca, w których rozpoczynają się i kończą
transakcje, cały kod przestaje w zasadzie
być przenaszalny i staje się mocno związany z regułami biznesowymi danej aplikacji. Rysunek 3 pokazuje schematycznie
nasz problem.
Skoro problemy przekrojowe mogę sprawić takie trudności, czy da się
je wydzielić do osobnych modułów, które nie będą w ogóle powiązane z żadną
z warstw? Okazuje się, że tak. Rozwiązania są dwa. Pierwsze, którym się zajmiemy w tym artykule, będzie polegało
na wykorzystaniu wzorca Dekorator i kontenera IoC, który zbudowaliśmy wspólnie w poprzednim artykule. Drugi sposób
opiera się na zastosowaniu programowania aspektowego (ang. aspect-oriented programming - AOP). Ten drugi sposób byłby dużo lepszym podejściem; niestety, obecnie dla PHP nie istnieje żaden
framework AOP, który w wystarczającym
stopniu implementowałby wszystkie założenia programowania aspektowego. Tak
jak poprzednio, cały kod zaprezentowany
w artykule zostanie umieszczony na stronie http://flexi.sf.net/.
Wzorzec Dekorator
Zgodnie z definicją GoF (ang. Gang of
Four – termin, który określa czwórkę pro-
3
Dla zaawansowanych
Crosscutting concerns
gramistów odpowiedzialnych za zapoczątkowanie idei wzorców projektowych),
zadaniem wzorca Dekorator jest wzbogacanie funkcjonalności obiektów wybranych klas w sposób dynamiczny, bez konieczności modyfikowania oryginalnego kodu. Ponieważ ta definicja może być
mało przejrzysta, posłużymy się przykładem. Zbudujemy prostą klasę MailSenderImpl, której zadaniem będzie wysyłanie maili (Listing 1). Wiemy, że klasa ta
będzie na tyle uniwersalna, że będziemy
chcieli wykorzystać ją w kilku innych, już
istniejących projektach. Niestety, każdy z
tych projektów ma inne wymagania biznesowe związane z regułami logowania
i bezpieczeństwa. Dlatego nie wyposażymy klasy MailSenderImpl w logikę związaną z tymi aspektami, lecz zaimplementujemy tę ostatnią w dekoratorach (Listing
2). Za każdym razem, gdy w którymś z
projektów zaistnieje potrzeba wysłania
maila, wykorzystamy do tego odpowiedni dekorator, przykładowo:
new MailSenderDecorator(
new MailSenderImpl())
Stosując wzorzec Dekorator udało się
nam osiągnąć dwie rzeczy. Po pierw-
�������������������
�������������������
�������������������������
�������������������������
���������������������
��������������
Rysunek 3. Problemy przekrojowe znajdują się zawsze z boku każdego podziału na
warstwy, a ich kod jest rozproszony pomiędzy tymi ostatnimi
sze, uniwersalna klasa MailSender pozostała niezmieniona – cała logika związana z wysyłaniem maili jest w jednym miejscu. Po drugie, kod charakterystyczny dla
każdej z aplikacji znajduje się w osobnych
klasach.
Wykorzystując wzorzec Dekorator
warto również pamiętać, że jest on bar-
Listing 3. Implementacja nowej wersji kontenera IoC obsługującej dekorowanie
obiektów
class IoCContainerWithDecoratorSupport extends
DefaultIoCContainter {
private $decoratorsSupport = array();
public function create($className) {
$classObj = parent::create($className);
if (!$classObj instanceof IoCDecoratorSupport&&!$classObj
instanceof IoCDecorator){
foreach( $this->getDecoratorsSupport() as $decoratorSupport ) {
if ( $decoratorSupport->match($className,$classObj) ) {
$decoratorObj = parent::create( $decoratorSupport->
getDecoratorName() );
$decoratorObj->setObject($classObj);
$classObj = $decoratorObj;
}
}
}
return $classObj;
}
public function setDecoratorsSupport( array $decoratorsSupport ) {
foreach($decoratorsSupport as $decorator) {
if ( !$decorator instanceof IoCDecoratorSupport ) {
throw new Exception('Class '.get_class($decorator).
' does not implement IoCDecoratorSupport interface');
}
$this->decoratorsSupport[] = $decorator;
}
}
private function getDecoratorsSupport(){ return $this->decoratorsSupport; }
}
4
����������������
www.phpsolmag.org
dzo użyteczny, gdy kod źródłowy klas nie
jest dostępny: jego użycie stanowi wtedy jedyną możliwość zmiany zachowania obiektu.
Wzorzec Dekorator
w połączeniu z
kontenerem IoC
Wróćmy teraz do przedstawionego w poprzednim artykule kontenera IoC. Jak zapewne pamiętamy, kontener IoC to zwyczajnie konfigurowalna fabryka obiektów
potrafiąca powołać do życia całe drzewa
tych ostatnich. Pokażemy teraz, jak można wykorzystać kontener IoC tak, aby poza tworzeniem obiektów potrafił je także
dekorować.
Wyobraźmy sobie następujący problem: klient, który zlecił nam projekt, zażyczył sobie, aby każda operacja modyfikacji danych (kto, kiedy i co zmieniał)
była logowana. Typowy programista zapewne rozwiązałby ten problem dokonując modyfikacji każdej metody zmieniającej dane. To podejście wydaje się
najbardziej oczywiste, ale niestety jest
najgorszym z możliwych. Zmodyfikowane zostałyby bowiem wszystkie kluczowe metody aplikacji, co oznacza potrzebę przetestowania całego projektu od
nowa. Dodatkowym problemem byłaby
konieczność przekazania do każdej z
warstw aplikacji informacji o tym, kto aktualnie wykonuje daną metodę. Ponadto,
jeżeli modyfikowany kod był wykorzystywany w innych projektach, programista
musiałby przestać go współdzielić. Jak
PHP Solutions Nr 6/2006
Crosscutting concerns
więc widać, podejście, które wydawało
się najbardziej oczywiste, spowodowałoby całą lawinę problemów.
My na szczęście będziemy mądrzejsi
i do rozwiązania problemu wykorzystamy
kontener IoC. Nasze założenie jest następujące: nie możemy dokonać żadnej modyfikacji kodu zapisującego i modyfikującego dane. Dlatego skonstruujemy kon-
tener, który na podstawie odpowiedniego wzorca składającego się z nazwy klasy oraz implementowanego przez nią interfejsu będzie umiał udekorować tworzony obiekt.
Zabierzmy się więc do pracy. Pierwszym krokiem będzie modyfikacja kontenera IoC tak, aby na podstawie zadanego wzorca mógł połączyć dekorowany
Listing 4. Implementacja klasy dopasowującej dekoratory do klas na podstawie
nazw klas
obiekt nie implementuje jednego z interfejsów klas pomocniczych dekoratorów,
aby uniknąć dekoracji tych ostatnich. Następnie kontener sprawdza przy pomocy klas pomocniczych, czy nazwa świeżo powołanej klasy pasuje do wzorca któregoś z dekoratorów. Jeżeli tak, to klasa
pomocnicza zwraca nazwę dekoratora.
Każda klasa pomocnicza musi implementować interfejs IoCDecoratorSupport, który z kolei składa się z dwóch metod:
l
class ClassNameIoCDecoratorSupport implements IoCDecoratorSupport {
private $decoratorName;
private $classNameToDecorator = array();
public function __construct($decoratorName,array $classNameToDecorator){
$this->decoratorName = $decoratorName;
$this->classNameToDecorator = $classNameToDecorator;
}
public function match($className,$classObject) {
return (in_array($className,$this->classNameToDecorator))?true:false;
}
obiekt$this->decoratorName;
z dekoratorem. W tym
public function getDecoratorName() { return
} celu rozsze}
rzymy klasę DefaultIoCContainer i zmo-
dyfikujemy jej metodę create(). W nowej
Listing 5. Implementacja klasy dopasowującej
dekoratory
na podstawie
wersji
tej klasy,do
jejklas
metoda
create() najdowolnego wyrażenia regularnego
pierw wywołuje create() klasy nadrzędNastępnie sprawdza,
czy powołany
class RegExpIoCDecoratorSupport implements nej.
IoCDecoratorSupport
{
}
private $decoratorName;
private $pattern;
public function __construct($decoratorName,$pattern){
$this->decoratorName = $decoratorName;
$this->pattern = $pattern;
}
public function match($className,$classObject) {
return (preg_match($this->pattern,get_class($classObject)))?true:false;
}
public function getDecoratorName() { return $this->decoratorName; }
Listing 6. Implementacja klasy dopasowującej dekoratory do klas na podstawie
interfejsu
class InterfaceIoCDecoratorSupport
implements IoCDecoratorSupport {
private $decoratorName;
private $interfaceName;
public function __construct(
$decoratorName,$interfaceName){
$this->decoratorName=
$decoratorName;
$this->interfaceName=
$interfaceName;
}
public function match($className,
$classObject) {
return ($classObject instanceof
$this->interfaceName)?
true:false;
}
public function
getDecoratorName(){
return $this->decoratorName;
}
}
PHP Solutions Nr 6/2006
www.phpsolmag.org
Dla zaawansowanych
l
– zwraca true, jeżeli wzorzec
dekoratora pasuje do wzorca klasy,
getDecoratorName() – zwraca nazwę
dekoratora z pliku konfiguracyjnego
IoC.
match()
Klasy pomocnicze dostarczane są do
kontenera poprzez metodę setDecorators(). Następnie kontener IoC powołuje do życia tenże dekorator i umieszcza
w nim dekorowany obiekt. Dekorator musi implementować interfejs IoCDecorator, który posiada jedną metodę – setObject(). Zauważmy, że w ten sposób możemy każdą z klas udekorować wieloma
dekoratorami. Warto jeszcze zapamiętać, że zgodnie z implementacją naszego
kontenera, dekorator nie może być singletonem. To znaczy: dla każdej dekorowanej klasy musi być powoływany do życia
nowy dekorator. Na Listingu 3 przedstawiamy kod nowej wersji kontenera, a na
Listingach 4, 5 i 6 – kilka przykładowych
klas pomocniczych implementujących interfejs IoCDecoratorSupport. Pierwsza
klasa odnajduje klasy według pełnej nazwy, druga wg wyrażenia regularnego, a
trzecia wg konkretnego interfejsu, który
implementuje dekorowana klasa.
Wróćmy teraz do naszego przykładu.
Załóżmy dla uproszczenia, że aplikacja,
w której trzeba dokonać zmian, ma trzy
klasy modyfikujące dane: UserDAO, OrderDAO oraz ItemDAO. Wszystkie one implementują jeden wspólny interfejs DAO zawierający metody setConnection(), findById(), save(), update(), delete(). Metoda setConnection() ustawia połączenie do źródła danych, findById() zwraca
obiekt na podstawie jego id, save() zapisuje obiekt, update() go zmienia, a delete() kasuje. Dane są modyfikowane jedynie przez metody save(), update() i delete(). Szczegóły implementacji klas *DAO
nie mają znaczenia w naszym przykładzie. Na Listingu 7 prezentujemy przykła-
5
Dla zaawansowanych
Crosscutting concerns
dową konfigurację kontenera bez dekoratorów. Jak widzimy, składa się ona z czterech wpisów, z których jeden dotyczy połączenia z bazą danych, a pozostałe trzy
zawierają ustawienia klas DAO.
Zmodyfikujemy teraz przykład tak,
aby spełniał on wymagania biznesowe
naszego klienta. Nic prostszego: zadanie rozpoczynamy od utworzenia klasy
DAOLoggerDecorator. Klasa ta implementuje dwa interfejsy: IoCDecorator i DAO (Li-
sting 8). Oczywiście, jej docelowa implementacja powinna zawierać bardziej wyrafinowany kod logujący. Teraz wykorzystując klasę z Listingu 6 modyfikujemy
konfigurację kontenera. Nową konfigurację pokazujemy na Listingu 9. Zauważmy,
że dodaliśmy tylko dwa wpisy i zgodnie
z założeniami, kod klas UserDAO, OrderDAO oraz ItemDAO nie został zmieniony. W
przykładzie pominęliśmy problem przekazywania informacji o tym, kto modyfikuje
Listing 7. Plik konfiguracji kontenera IoC dla opisanego w
tekście przykładu bez wsparcia dekoratorów
$currentDir = dirname(__FILE__);
$frameworkPath = realpath( $currentDir.'/../../flexi' );
ini_set( 'include_path', ini_get('include_path').
PATH_SEPARATOR.$frameworkPath.'/' );
require_once 'ioc/IoCContainerWithDecoratorSupport.
class.php';
require_once 'ioc/MappingBuilderFromArray.
class.php';
$iocMap = array(
"connection" => array(
"className" => "Connection",
"file" => $currentDir."/Connection.class.php",
"singleton" => true,
"properties" => array(),
"constructorParams" => array()
),
"userDAO" => array(
"className" => "UserDAO",
"file" => $currentDir."/UserDAO.class.php",
"singleton" => true,
"properties"=>array(
"connection"=>"&connection"),
"constructorParams" => array()
),
"orderDAO" => array(
"className" => "OrderDAO",
"file" => $currentDir."/OrderDAO.class.php",
"singleton" => true,
"properties" => array(
"connection"=>"&connection"),
"constructorParams" => array()
),
"itemDAO" => array(
"className" => "ItemDAO",
"file" => $currentDir."/ItemDAO.class.php",
"singleton" => true,
"properties" => array(
"connection"=>"&connection"),
"constructorParams" => array()
),
);
$mappingBuilder = new
MappingBuilderFromArray($iocMap);
$iocContainer=new IoCContainerWithDecoratorSupport(
$mappingBuilder->getApplicationMap());
$userDAO = $iocContainer->create("userDAO");
$orderDAO = $iocContainer->create("orderDAO");
$itemDAO = $iocContainer->create("itemDAO");
var_dump($userDAO);
var_dump($orderDAO);
var_dump($itemDAO);
6
dane. Gdyby kod (zupełnie inny niż w naszym przykładzie) nie miał tak jasno zdefiniowanego interfejsu, jak nasze klasy
DAO, do tworzenia dekoratorów moglibyśmy wykorzystać metodę _ _ call(), której działanie zostało opisane w dokumentacji PHP.
W podobny sposób, jak reguły związane z logowaniem, moglibyśmy dodać
do aplikacji kod odnoszący się do reguł
bezpieczeństwa i praw dostępu użytkow-
Listing 8. Klasa DAOLoggerDecorator jest dekoratorem
klas UserDAO, OrderDAO i ItemDAO
class DAOLoggerDecorator implements IoCDecorator, DAO {
private $dao;
public function findById($id){
// Kod dekoratora
$result = $this->dao->findById($id);
// Kod dekoratora
...
}
return $result;
public function save($object){
// Kod dekoratora
$result = $this->dao->save($object);
// Kod dekoratora
return $result;
}
public function update($object){
// Kod dekoratora
$result = $this->dao->update($object);
// Kod dekoratora
...
}
return $result;
public function delete($object){
// Kod dekoratora
$result = $this->dao->delete($object);
// Kod dekoratora
...
}
return $result;
public function setConnection($connection){
// Kod dekoratora
$result = $this->dao->
setConnection($connection);
// Kod dekoratora
return $result;
}
public function setObject($object){
if ( !$object instanceof DAO ){
throw new Exception('Class '.get_class($object).
' must implement DAO interface');
}
$this->dao = $object;
}
}
www.phpsolmag.org
PHP Solutions Nr 6/2006
Crosscutting concerns
Listing 9. Nowa wersja pliku konfiguracyjnego kontenera
$currentDir = dirname(__FILE__);
$frameworkPath = realpath( $currentDir.'/../../flexi' );
ini_set( 'include_path', ini_get('include_path').PATH_SEPARATOR.
$frameworkPath.'/' );
require_once 'ioc/IoCContainerWithDecoratorSupport.class.php';
require_once 'ioc/MappingBuilderFromArray.class.php';
$iocMap = array(
);
"connection" => array(
"className" => "Connection",
"file" => $currentDir."/Connection.class.php",
"singleton" => true,
"properties" => array(),
"constructorParams" => array()
),
"userDAO" => array(
"className" => "UserDAO",
"file" => $currentDir."/UserDAO.class.php",
"singleton" => true,
"properties" => array("connection"=>"&connection"),
"constructorParams" => array()
),
"orderDAO" => array(
"className" => "OrderDAO",
"file" => $currentDir."/OrderDAO.class.php",
"singleton" => true,
"properties" => array("connection"=>"&connection"),
"constructorParams" => array()
),
"itemDAO" => array(
"className" => "ItemDAO",
"file" => $currentDir."/ItemDAO.class.php",
"singleton" => true,
"properties" => array("connection"=>"&connection"),
"constructorParams" => array()
),
"interfaceIoCDecoratorSupport" => array(
"className" => "InterfaceIoCDecoratorSupport",
"file" => $frameworkPath."/ioc/decorators/InterfaceIoCDecoratorSupport.
class.php",
"singleton" => true,
"properties" => array(),
"constructorParams" => array("daoDecorator","DAO")
),
"daoDecorator" => array(
"className" => "DAOLoggerDecorator",
"file" => $currentDir."/DAOLoggerDecorator.class.php",
"singleton" => false,
"properties" => array(),
"constructorParams" => array()
),
$mappingBuilder = new MappingBuilderFromArray( $iocMap );
$iocContainer = new IoCContainerWithDecoratorSupport(
$mappingBuilder->getApplicationMap() );
$iocContainer->setDecoratorsSupport( array(
$iocContainer->create("interfaceIoCDecoratorSupport")));
$userDAO = $iocContainer->create("userDAO");
$orderDAO = $iocContainer->create("orderDAO");
$itemDAO = $iocContainer->create("itemDAO");
var_dump($userDAO);
var_dump($orderDAO);
var_dump($itemDAO);
PHP Solutions Nr 6/2006
www.phpsolmag.org
Dla zaawansowanych
ników. Wyobraźmy sobie, że klient definiuje nowy wymóg: chce, aby modyfikacji danych mogli dokonywać tylko administratorzy aplikacji. Możemy tu zastosować podobne podejście, jak przy logowaniu. Utworzymy więc klasę DAOSecutiryDecorator, która przy wywoływaniu każdej metody save(), update() czy delete()
będzie sprawdzała, czy wykonujący ją
użytkownik ma prawa do jej użycia: jeżeli
nie, aplikacja zgłosi wyjątek.
Podsumowanie
Stosowanie kontenera IoC i wzorca Dekorator nie jest remedium na wszelkie problemy przekrojowe. Aby wprowadzenie
do kodu rzeczywistej aplikacji aspektów
logowania i bezpieczeństwa było możliwe od samego początku, aplikacja ta musi mieć jasny podział na warstwy. Należy również ją tworzyć zgodnie z dobrymi
praktykami programowania obiektowego,
w szczególności kładąc nacisk na interfejsy i kompozycję obiektów. Warto też pamiętać, że reguły bezpieczeństwa i logowania nie są jedynymi problemami przekrojowymi: zaliczają się do nich również
transakcje, profilowanie, keszowanie (caching) czy walidacja. Na szczęście i te
problemy można rozwiązać przy pomocy kontenera IoC i odpowiednich dekoratorów. n
O autorze
Piotr Szarwas ma wieloletnie doświadczenie w programowaniu i tworzeniu
aplikacji WWW (PHP, Java). Jest konsultantem w jednej z największych polskich firm IT, a także doktorantem na
Wydziale Fizyki Politechniki Warszawskiej. Od dawna pisze artykuły dla PHP
Solutions.
Kontakt z autorem:
[email protected]
7

Podobne dokumenty