Używam wzorca MVC w mojej aplikacji internetowej zbudowanej z PHP.
Zawsze staram się ustalić, czy potrzebuję nowego dedykowanego kontrolera do zestawu działań, czy też powinienem umieścić je w już istniejącym kontrolerze.
Czy podczas tworzenia kontrolerów obowiązują dobre zasady?
Na przykład mogę mieć:
AuthenticationController
z działaniami:
index()
aby wyświetlić formularz logowania.submit()
do obsługi przesyłania formularzy.logout()
, oczywiste.
LUB
LoginController
z działaniami:
index()
aby wyświetlić formularz logowania.submit()
do obsługi przesyłania formularzy.
LogoutController
z działaniem:
index()
do obsługi wylogowania.
LUB
AccountController
z działaniami:
loginGet()
aby wyświetlić formularz logowania.loginPost()
do obsługi przesyłania formularza logowania.logoutGet()
do obsługi wylogowania.registerGet()
wyświetlić formularz rejestracyjny.registerPost()
do obsługi przesyłania formularzy.I wszelkie inne działania związane z kontem.
Odpowiedzi:
Aby znaleźć odpowiednią grupę dla kontrolerów, pomyśl o testowaniu .
(Nawet jeśli tak naprawdę nie przeprowadzasz żadnych testów, myślenie o tym, jak przejść do testowania kontrolerów, da ci bardzo dobre informacje na temat ich strukturyzacji).
AuthenticationController
Nie jest sprawdzalne przez siebie, ponieważ zawiera tylko funkcjonalność do logowania i wylogowania, ale Twój kod badania będą musiały jakoś tworzyć fałszywe rachunki do celów testowych, zanim będzie można go przetestować udanego logowania. Możesz ominąć testowany podsystem i przejść bezpośrednio do swojego modelu w celu utworzenia kont testowych, ale wtedy będziesz miał delikatny test w swoich rękach: jeśli model się zmieni, będziesz musiał zmodyfikować nie tylko kod, który testuje model, ale także kod testujący kontroler, mimo że interfejs i zachowanie kontrolera pozostały niezmienione. To nieracjonalne.A
LoginController
jest nieodpowiedni z tych samych powodów: nie można go przetestować bez uprzedniego utworzenia konta, a jest jeszcze więcej rzeczy, których nie można przetestować, na przykład zapobieganie duplikowaniu loginów, a następnie umożliwienie użytkownikowi zalogowania się po wylogowaniu. (Ponieważ ten kontroler nie ma funkcji wylogowania).AccountController
Daje wszystko, czego potrzeba, aby zrobić swoje badania: można utworzyć konta testowego, a następnie spróbuj logowania, można usunąć konto, a następnie upewnij się, że nie może już login, można zmienić hasło i upewnij się, że należy użyć właściwego hasła do logowania itp.Podsumowując: aby napisać nawet najmniejszy pakiet testowy, musisz udostępnić mu całą funkcjonalność
AccountController
. Dzielenie go na mniejsze kontrolery wydaje się dawać niesprawne kontrolery o niewystarczającej funkcjonalności do prawidłowego testu. Jest to bardzo dobra wskazówka, że funkcjonalnośćAccountController
jest najmniejszym poddziałem, który ma sens.Mówiąc ogólnie, podejście „pomyśl o testowaniu” będzie działać nie tylko w tym konkretnym scenariuszu, ale w każdym podobnym scenariuszu, z którym się spotkasz w przyszłości.
źródło
Odpowiedź nie jest taka oczywista
Pozwól, że wyjaśnię kilka rzeczy, zanim złożę jakieś odpowiedzi. Po pierwsze:
Co to jest kontroler?
Kontroler jest częścią systemu kontrolującego żądanie - po wysłaniu. Możemy więc zdefiniować go jako zestaw działań związanych z ... czym?
Jaki jest zakres kontrolera?
I to mniej więcej część, kiedy będziemy mieli jakąkolwiek odpowiedź. Co myślisz? Czy to kontroler rzeczy (na przykład konto) czy kontroler działań? Oczywiście jest to kontroler jakiegoś modelu lub czegoś bardziej abstrakcyjnego, co zapewnia działania na nim.
Odpowiedź to...
Nie, uwierzytelnianie to proces. Nie idź tą drogą.
To samo tutaj. Logowanie - akcja. Lepiej nie twórz kontrolera akcji (nie masz z nim skorelowanego modelu).
Całkiem dobrze, ale nie jestem przekonany, że warto zbudować ten kontroler niskiego poziomu (sam kontroler jest abstrakcją). W każdym razie tworzenie metod za pomocą * Get lub * Post jest niejasne.
Jakieś sugestie?
Tak, rozważ to:
AccountController:
I powiązany z nim model, ofc Klasa konta. To da ci możliwość przeniesienia pary model-kontroler gdzie indziej (jeśli będzie to konieczne) i zrobienia przejrzystego kodu (oczywiste jest, co
login()
oznacza metoda). Stincking do modelowania jest naprawdę sławny, szczególnie w aplikacjach CRUD i być może jest to sposób dla ciebie.źródło
Kontrolery są zwykle tworzone dla określonego zasobu (klasa encji, tabela w bazie danych), ale można je również utworzyć w celu zgrupowania działań odpowiedzialnych za określoną część aplikacji. W twoich przykładach byłby to kontroler, który obsługuje zabezpieczenia aplikacji:
Uwaga : nie umieszczaj akcji związanych z bezpieczeństwem i akcji profilu użytkownika w tym samym kontrolerze; może to mieć sens, ponieważ są one powiązane z użytkownikiem, ale jeden powinien obsługiwać uwierzytelnianie, a drugi powinien obsługiwać aktualizacje poczty e-mail, nazw itp.
Z kontrolerami utworzonymi dla zasobów (powiedzmy
Task
), będziesz miał zwykłe akcje CRUD :Oczywiście możesz dodać powiązane zasoby do tego samego kontrolera. Powiedz na przykład, że masz encję
Business
, a każda z nich ma kilkaBusinessService
encji. Kontroler może wyglądać następująco:Takie podejście ma sens, gdy powiązane elementy potomne nie mogą istnieć bez elementu macierzystego.
Oto moje rekomendacje:
Subscription
encję, która jest dostępna na podstawie ograniczonej liczby wpisów, możesz dodać nową akcję do kontrolera o nazwieuse()
, której jedynym celem jest odjęcie jednej pozycji odSubscription
)Niektóre zasoby do dalszego czytania tutaj .
źródło
Widzę dwie antagonistyczne „siły” projektowe (które nie są wyłączne dla kontrolerów):
Z punktu widzenia spójności wszystkie trzy działania (logowanie, wylogowanie, rejestracja) są powiązane, ale logowanie i wylogowanie to coś więcej niż rejestracja. Są one powiązane semantycznie (jeden jest odwróceniem drugiego) i całkiem możliwe, że będą również korzystać z tych samych obiektów usług (ich implementacje są również spójne).
Moim pierwszym pomysłem byłoby zgrupowanie logowania i wylogowania w jednym kontrolerze. Ale jeśli implementacje kontrolera logowania i wylogowania nie są tak proste (np. Logowanie ma captcha, więcej metod uwierzytelniania itp.), Nie miałbym problemu z podzieleniem ich na LoginController i LogoutController, aby zachować prostotę. To, gdzie leży ten próg złożoności (kiedy powinieneś zacząć dzielić kontroler), jest nieco osobiste.
Pamiętaj również, że niezależnie od tego, jak projektujesz kod na początku, możesz (i powinieneś) refaktoryzować go wraz ze zmianami. W tym przypadku dość typowe jest rozpoczęcie od prostego projektu (mieć jeden AuthenticationController), az czasem otrzymasz więcej wymagań, które skomplikują kod. Gdy przekroczy próg złożoności, powinieneś refaktoryzować go do dwóch kontrolerów.
BTW, twój kod sugeruje, że wylogowujesz użytkownika za pomocą żądania GET. To zły pomysł, ponieważ HTTP GET powinien być nullipotentny (nie powinien modyfikować stanu aplikacji).
źródło
Oto kilka praktycznych zasad:
Organizuj według tematu lub tematu, przy czym nazwa kontrolera to nazwa tematu.
Pamiętaj, że nazwa kontrolera pojawi się w adresie URL, będzie widoczna dla użytkowników, więc najlepiej powinna mieć dla nich sens.
W sytuacji, o której wspominasz (uwierzytelnianie) zespół MVC napisał już dla ciebie kontroler. Otwórz Visual Studio 2013, a następnie kliknij
AccountController.cs zawiera wszystkie metody zarządzania kontami użytkowników:
Dlatego uporządkowano według tematu „Konta użytkowników i uwierzytelnianie”, z widoczną nazwą tematu „Konto”.
źródło
Terminologia
Uważam, że błędem jest nazywanie klasy zawierającej niektóre metody związane z HTTP „kontrolerem”.
Kontroler to metoda obsługująca żądanie, ale nie klasa zawierająca takie metody . Więc
index()
,submit()
,logout()
są kontrolerzy.Klasa zawierająca tego rodzaju metody nosi nazwę „kontroler” tylko dlatego, że stanowi grupę kontrolerów i pełni rolę przestrzeni nazw „niższego poziomu”. W języku FP (jak Haskell) byłby to tylko moduł. Dobrą praktyką jest utrzymywanie tych klas „kontrolerów” w sposób jak najbardziej bezstanowy w językach OOP, z wyjątkiem odniesień do usług i innych rzeczy dotyczących całego programu.
Odpowiedź
Po uporządkowaniu terminologii pojawia się pytanie „w jaki sposób powinniśmy rozdzielić kontrolery na przestrzenie nazw / moduły?” Myślę, że odpowiedź brzmi: kontrolery w obrębie jednej przestrzeni nazw / modułu powinny obsługiwać ten sam rodzaj danych . Na przykład
UserController
dotyczy przede wszystkim wystąpieńUser
klasy, ale czasami dotyka innych powiązanych rzeczy, jeśli jest to wymagane.Ponieważ
login
,logout
i inne tego typu działania są głównie do czynienia z sesją, to prawdopodobnie najlepiej umieścić je wewnątrzSessionController
, aindex
kontroler, który po prostu drukuje formularz powinien być umieszczonyLoginPageController
, gdyż oczywiście zajmuje się strona logowania. Rozsądne jest umieszczenie renderowania HTML i zarządzania sesjami w jednej klasie, co naruszyłoby SRP i prawdopodobnie szereg innych dobrych praktyk.Ogólna zasada
Jeśli nie możesz zdecydować, gdzie umieścić fragment kodu, zacznij od danych (i typów), z którymi masz do czynienia.
źródło