Martin Fowler, w Patterns of Enterprise Application Architecture odwalił kawał dobrej roboty obmawiając generalne wejścia i wyjścia Kontrolera, oto jak go podsumował:
"Kontroler przechwytuje wszystkie żądania strony WWW i przeważnie składa się z dwóch części: Uchwytu Strony oraz Hierarchii Komend."
Mówiąc "Uchwyt Strony" ma na myśli kod, który bada nadchodzące żądania HTTP w celu zebrania wystarczającej ilość informacji, by wiedzieć jaką podjąć akcję. "Hierarchia komend" to natomiast rodzaj zorganizowanej struktury, do której odwołuje się Kontroler, aby zadecydować jakie operacje wykonać, bazując na informacjach zawartych w "Uchwycie Strony".
Wrócimy do tego wkrótce, jednak najpierw rzućmy okiem na popularny (jest wiele odmian) oraz prosty przykład implementacji tego wzorca:
<?php // index.php switch ( @$_GET['action'] ) { case 'edit': include ('actions/edit.php'); break; case 'post': include ('actions/post.php'); break; case 'delete': include ('actions/delete.php'); break; case 'default': include ('actions/view.php'); break; } ?>
Uwaga: Jeśli kodujesz jakikolwiek kontroler tego typu, pamiętaj o sprawdzaniu wartości wprowadzanych przez użytkownika. Jeśli w swoim kodzie będziesz posiadał coś podobnego do: include ($_GET [ 'page' ]); - istnieje duże prawdopodobieństwo wykorzystania tego błędu, co może prowadzić do katastrofy.
Proponuję również wykorzystanie innego wzorca nie wykorzystującego dołączania plików: Kontrolera Strony, określonego przez Martina Fowlera jako:
"Obiekt obsługujący żądanie dla określonej strony lub jej akcji."
Nie pozwól, aby zmyliło Cię słowo "wzorzec" - każdy kto spędził kilka godzin w PHP stworzył swój Kontroler Strony- jako prostą strukture if/else, np. do obsługi formularza. Podobną funkcję w artykule pełni plik "actions/post.php".
Powyższy przykład wydaje się bardzo logicznym i prostym sposobem radzenia sobie z wykonywaniem zapytań. Dane "Uchwytu Strony" są dostępne w zmiennej globalnej $_GET, natomiast struktura switch odpowiada za "Hierarchię Komend".
Skrypt 'index.php' wykonuje pod-moduły na twojej stronie bazując na URLu: http://www.example.com/index.php?action=post
Wszystko gra, prawda?
Jednak słowo 'wykonywać' powinno uruchomić twój osobisty alarm. PHP jest jedynie dodatkiem do serwera- tak naprawdę całość obsługiwana jest przez serwer HTTP- np. Apache.
Istnieją spore różnice w środowisku aplikacji pomiędzy PHP a serwletami Javy lub natywnym Serwerem Aplikacji, gdzie Java zachowuje dane pomiędzy zapytaniami.
Powyższy kod, może nie pokazywać na czym polega problem, jednak wyobraź sobie, że posiadasz setki Kontrolerów Strony. Skończy się to ogromną konstrukcją warunkową, być może zawartą w tablicy lub dokumencie XML- źródło jest nieistotne. Przy każdym żądaniu, PHP będzie zmuszony do przeładowywania ogromnej ilości danych niemających związku z naszym zapytaniem.
Innym problemem związanym z wykonywaniem wszystkiego w jednym pliku, takim jak index.php, jest narzucenie poważnego ograniczenia elastyczności jaką oferuje PHP, domyślnie pozwalając na umieszczenie skryptu gdziekolwiek w katalogu głównym serwera. Zaakceptowanie takiego podejścia przysporzy ci wiele problemów w przyszłości, szczególnie z praktycznej strony- będziesz zmuszony za każdym razem, gdy dodasz nowy plik, zmieniać "Hierarchię Komend".
Wracając do definicji Martina Flowera, powiedziałem, że Kontroler jest już dostępny w PHP i serwerze internetowym. Tworzą one gotową implementacje "Uchwytu Strony", zasilanego przez nadchodzące żądania w zmiennych globalnych takich jak $_POST czy $_GET. Natomiast struktura plików w katalogu głównym serwera jest "Hierarchią Komend". Tak więc pisanie tej funkcjonalności w PHP jest ponownym odkryciem koła.
Szczerze mówiąc większość frameworków napisanych w PHP posiada nadzień dzisiejszy właśnie ten "niewłaściwy" Kontroler. I owszem ta strona jest winna tym wszystkim "przestępstwom"... Ja również musiałem troszkę oszukać kiedy implementowałem PDFową wersję artykułów, aby zabezpieczyć index.php przed wysyłaniem "globalnego" wyniku HTML.
Oczywiście `niewłaściwy` jest używany w znaczeniu względnym, ta strona (http://www.phppatterns.com/ - od tłumacza) została oparta o eZ publish 2.x, który rozprowadza dużą cześć "Hierarchii Komend" dla specyficznych modułów aplikacji i po prostu trzyma je w konstrukcji switch. EZ publish upraszcza lwią część mojego życia, więc jestem przygotowany do pracy z nim.
Patrząc na kilka z frameworków inspirowanych na Jakarta Struts, takich jak php.MVC, phrame, ambivalence czy eocene , możemy zauważyć, że wszystkie używają centralnego pliku konfiguracyjnego, który zawiera informacje o całości strony. Należałoby teraz opowiedzieć historyjkę co się stanie gdy spróbujesz zaaplikować restrykcyjną Jave to PHP... Innym interesującym przykładem, z grupy tzw. "middle ground" ( środkowy grunt ), jest ISMO, dystrybujący większość "Hierarchii Komend" w systemie plików, na takiej samej zasadzie jak to się dzieje w ez Publish, jednak używa metod klas zamiast konstrukcji switch do trzymania końcowych akcji.
Wszystkie z nich przekazują żądania poprzez pojedynczy skrypt, co często przysparza kłopotów gdy chcemy później wprowadzić do naszej strony znaczące zmiany.
Można to również argumentować to tym, że starając się zrozumieć zawiłe wymagania stawiane Kontrolerowi w PHP, robimy wiele, aby biegle się nim posługiwać, co równoznaczne jest z brakiem przekonania w finalny produkt danej firmy.
Jednak co z...?
Jednak pogląd, że przekazywanie wszystkich żądań przez jeden plik ( np. index.php ) jest złe, moim zdaniem również jest niewłaściwy.
Wielki plusem takiego podejścia jest łatwość w radzeniu sobie z "globalnymi operacjami", które zostają wykonane na każdej stronie serwisu, np. logowania, autoryzacji / sprawdzania sesji czy choćby cachowania.
Wracając do Martina Flowera, w swojej dyskusji o Kontrolerze wypowiedział takie słowa:
"Szczególnie przydatnym wzorcem w połączeniu z Kontrolerem jest Filtr Przechwytujący [...], który obsługuje takie zagadnienia jak autoryzacja, logowanie i lokalna identyfikacja."
Mówiąc prosto: wiedząc, że posiadasz pewne czynności, które zostają wykonane przy każdym żądaniu- umieszczenie ich w pliku index.php bardzo ułatwia całą sprawę.
Naszym celem jest osiągniecie zdolności wykonywania globalnych operacji przy każdym rządaniu, nie korzystając z górnolotnej implementacji Struts-podobnej.
Musimy przezwyciężyć dwa podstawowe problemy:
Rozwiązanie jest bardzo proste- używaj Kontrolera Strony, ładuj Filtr Przechwytujący i zapomnij o Kontrolerze, którego rolę spełnia Apache i silnik PHP.
Oczywiście od strony praktycznej istnieje szereg rozwiązań. Najprostsze jest podejście proceduralne- zwyczajne dołączanie "przed" i "po" za każdym razem do skryptu.
Bardzo ciekawymi ustawieniami serwera w pliku php.ini są: auto_prepand_file oraz auto_append_file, które mówią silnikowi PHP jakie pliki należy automatycznie dołączyć. Jedynym minusem tego rozwiązania jest zmiana ustawień w pliku php.ini lub dostęp do .htaccess ( zakładając, że konfiguracja Apache pozwala ci na to ). Jeśli piszesz aplikacje dla wielu ludzi, może to stanowić pewien rodzaj problemu.
Trzecim wyjściem jest zrobienie czegoś podobnego do twojej klasy Kontrolera Strony ( zakładając, że takową posiadasz ). Mam na myśli rozszerzenie superklasy o współdziałanie z operacjami globalnymi w momencie gdy Kontroler Strony otrzyma instrukcje, aby renderować stronę. Często wygląda to mniej więcej tak:
$pageController -> execute ( );
Zadaniem klasy rodzica, której częścią jest metoda execute ( ), jest operowanie na wynikach "operacji globalnych". Może podobać ci się takie podejście jeśli lubisz OOP, jednakże może ono przysporzyć ci wielu problemów, gdy będziesz chciał, aby twój skrypt współpracował z innymi aplikacjami. Zależnie od tego jak daną rzecz łatwo wymienić - dla przykładu, zamień swój system autoryzacji na rozwiązanie dostarczane z zewnątrz.
Co się tyczy zagadnienia danych konfiguracyjnych powiązanych z daną akcją to bardzo prosto umiejscowić je w Kontrolerze Strony. Możesz również zdefiniować pewien rodzaj standardowego schematu pliku, który będzie ładowany przez twój Kontroler Strony.
Podsumowywując wcześniejsze rozważania, możemy napisać taki skrypt:
<?php //edit.php require ( 'header.php' ); #ładowanie pre-filtrów // Kod Kontrolera Strony require ( 'footer.php' ); #ładowanie post-filtrów ?>
Dość ciekawe podsumowanie, nie sądzisz?
Oczywiście mogę się mylić. Przynajmniej raz przerabiałem to od nowa i już zdrowo namieszało mi się w głowie.
Istnieją odpowiednie wskazówki, jeśli chciałbyś oddzielić Kontrolera Strony od filtrów globalnych. Nic nie stoi na przeszkodzie, aby tak zrobić.
Na liście jest prawdopodobnie: "unikanie zdalnego odwoływanie się do zmiennej w Kontrolerze Strony, stworzonej na użytek Filtru, jako części globalnej operacji". Zamiast Fabryki, spróbuj użyć wzorca Adapter- pozwoli ci to później dowolnie zmieniać schemat autoryzacji.
Również kontrola wyjścia, może być warta zaimplementowania. Zmieniając bufor wyjścia w skrypcie początkowym (prepaned), a następnie przekazując go skryptowi wykonywanemu na koniec (append), dajesz sobie możliwość wysyłania jakichkolwiek nagłówków pomiędzy nimi.
Zgadzam się z przedmówcą, trafił w samo sedno. Jeśli wystawiać tekst na tak szacownym portalu jak php.pl, to albo to robić dobrze, albo wcale. Niestety ten sam mankament dotyczy wielu tłumaczeń na wortalu.
drogi panie Wasilewski,
czytając ten artykuł musiałem sobie tłumaczyć w głowie słowo-w-słowo na angielski a później łapać kontekst z angielskiej konstrukcji zdań. Przeczytam to jeszcze raz po angielsku żeby coś zrozumieć. Na tym polega poziom tłumaczenia, że nie "konwertuje" się słów angielskich na polskie, ale tłumaczy się całe zdania czasem zmieniając szyk, czasem coś dodając, czasem ujmując. i dopiero wtedy to jest tekst po polsku. ten artukuł powyżej naprawdę ciężko się czyta... przepraszam za taką bezczelną opinię, ale cieszyłem się że ktoś odwalił "brudną robotę" za mnie i będe mógł skupić się na temacie który ostatnio bardzo dotyczy moich aplikacji. a ostatecznie tylko się zamieszałęm i muszę i tak czytać to jeszcze raz w oryginale...
Nie chcę przymarudzać, bo rozumiem, że takie tłumaczenie to i tak kawał dobrej roboty, ale w przyszłości może warto 2-3 razy przeczytać oryginał a następnie opisać tamat swoimi słowami. a nóż wyjdzie po polsku i do czytania ;)
p.s. proszę nie brać tego tak strasznie do siebie :)
Przydało by sie podawać angielskie nazwy omawianych wzorców, jako że są powszechnie używane i łatwiej jest znaleźć dodatkowe informacje posługując się właśnie angielskimi nazwami.