Jak ustalić, co powinien otrzymać własny kontroler?

10

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.

Kid Diamond
źródło
Może rzucisz okiem na RESTful design. Nie rozwiązuje każdego tego rodzaju problemu, ale daje bardzo dobry kierunek, jak o tym myśleć.
thorsten müller

Odpowiedzi:

3

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).

AuthenticationControllerNie 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 LoginControllerjest 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).

AccountControllerDaje 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ść AccountControllerjest 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.

Mike Nakis
źródło
1

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...

AuthenticationController z akcjami:

  • index (), aby wyświetlić formularz logowania.
  • submission () do obsługi przesyłania formularza.
  • wylogowanie (), oczywiste.

Nie, uwierzytelnianie to proces. Nie idź tą drogą.

LoginController z akcjami:

  • index (), aby wyświetlić formularz logowania.
  • submission () do obsługi przesyłania formularza.

To samo tutaj. Logowanie - akcja. Lepiej nie twórz kontrolera akcji (nie masz z nim skorelowanego modelu).

AccountController z akcjami:

  • loginGet (), aby wyświetlić formularz logowania.
  • loginPost () do obsługi przesyłania formularza logowania.
  • logoutGet () do obsługi wylogowania.
  • registerGet (), aby wyświetlić formularz rejestracyjny.
  • registerPost () do obsługi przesyłania formularzy.

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:

  • login (AccountModel)
  • wyloguj się (AccountModel)
  • zarejestruj się (AccountModel)
  • indeks()

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.

Dawid Pura
źródło
1

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:

class SecurityController
{
    // can handle both the login page display and
    // the login page submission
    login(); 

    logout();

    register();

    // optional: confirm account after registration
    confirm();

    // displays the forgot password page
    forgotPassword();

    // displays the reset password page
    // and handle the form submission
    resetPassword();
}

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 :

class TasksController
{
    // usually displays a paginated list of tasks
    index();

    // displays a certain task, based on an identifier
    show(id);

    // displays page with form and
    // handles form submission for creating
    // new tasks
    create();

    // same as create(), but for changing records
    update(id);     

    // displays confirmation message
    // and handles submissions in case of confirmation
    delete()
}

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 kilka BusinessServiceencji. Kontroler może wyglądać następująco:

class BusinessController
{
    index();

    show(id);

    create();

    update(id);

    delete();

    // display the business services for a certain business
    listBusinessServices(businessId);

    // displays a certain business service
    showBusinessService(id);

    // create a new business service for a certain business
    createBusinessService(businessId);

    // updates a certain business service
    updateBusinessService(id);

    // deletes a certain business service
    deleteBusinessService(id);
}

Takie podejście ma sens, gdy powiązane elementy potomne nie mogą istnieć bez elementu macierzystego.

Oto moje rekomendacje:

  • tworzyć kontrolery w oparciu o grupę powiązanych operacji (zajmujących się pewnymi obowiązkami, takimi jak bezpieczeństwo lub operacje CRUD na zasobach itp.);
  • w przypadku kontrolerów opartych na zasobach nie dodawaj niepotrzebnych akcji (jeśli nie masz aktualizować zasobu, nie dodawaj akcji aktualizacji);
  • możesz dodać „niestandardowe” akcje, aby uprościć rzeczy (np. masz Subscriptionencję, która jest dostępna na podstawie ograniczonej liczby wpisów, możesz dodać nową akcję do kontrolera o nazwie use(), której jedynym celem jest odjęcie jednej pozycji od Subscription)
  • zachowaj prostotę - nie zaśmiecaj kontrolera ogromną liczbą akcji i skomplikowanej logiki, staraj się uprościć rzeczy poprzez zmniejszenie liczby akcji lub utworzenie dwóch kontrolerów;
  • jeśli używasz frameworka skoncentrowanego na MVC, postępuj zgodnie ze wskazówkami dotyczącymi najlepszych praktyk (jeśli go mają).

Niektóre zasoby do dalszego czytania tutaj .

qretzu
źródło
0

Widzę dwie antagonistyczne „siły” projektowe (które nie są wyłączne dla kontrolerów):

  • spójność - administratorzy powinni grupować powiązane działania
  • prostota - kontrolery powinny być tak małe, jak to możliwe, aby zarządzać ich złożonością

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).

qbd
źródło
0

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

File / New / Project... 
Search installed templates for "ASP.NET MVC4 Web Application"
Choose "Internet Application" / OK.

AccountController.cs zawiera wszystkie metody zarządzania kontami użytkowników:

Login()
Logoff()
Register()
Disassociate()
Manage()
ExternalLogin()

Dlatego uporządkowano według tematu „Konta użytkowników i uwierzytelnianie”, z widoczną nazwą tematu „Konto”.


źródło
0

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 UserControllerdotyczy przede wszystkim wystąpień Userklasy, ale czasami dotyka innych powiązanych rzeczy, jeśli jest to wymagane.

Ponieważ login, logouti inne tego typu działania są głównie do czynienia z sesją, to prawdopodobnie najlepiej umieścić je wewnątrz SessionController, a indexkontroler, który po prostu drukuje formularz powinien być umieszczony LoginPageController, 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.

scriptin
źródło
2
Przepraszamy, to są akcje, a nie kontrolery :)
JK01,
@ JK01 Nazywa się je. Wiesz, to terminologia. Istnieją również frameworki, które nazywają te funkcje „kontrolerami” (lub „programami obsługi”), ponieważ istnieje wiele frameworków, które nie organizują ich w klasy, ponieważ przestrzenie nazw / moduły są już wystarczające. Możesz użyć dowolnych terminów, które lubisz, to tylko słowa, ale myślę, że mniej terminów jest lepsze.
scriptin