Właśnie rozumiem strukturę MVC i często zastanawiam się, ile kodu powinno zostać w tym modelu. Zwykle mam klasę dostępu do danych, która ma takie metody:
public function CheckUsername($connection, $username)
{
try
{
$data = array();
$data['Username'] = $username;
//// SQL
$sql = "SELECT Username FROM" . $this->usersTableName . " WHERE Username = :Username";
//// Execute statement
return $this->ExecuteObject($connection, $sql, $data);
}
catch(Exception $e)
{
throw $e;
}
}
Moje modele są zazwyczaj klasami jednostek odwzorowanymi na tabelę bazy danych.
Czy obiekt modelu powinien mieć wszystkie właściwości odwzorowane w bazie danych, a także powyższy kod, czy też można oddzielić ten kod, który faktycznie działa w bazie danych?
Czy skończę mieć cztery warstwy?
php
oop
model-view-controller
architecture
model
Dietpixel
źródło
źródło
Exception
nie ma dużej wartości dokumentacji. Osobiście, gdybym poszedł tą drogą, wybrałbym PHPDoc@exception
lub podobny mechanizm, więc pojawia się w wygenerowanej dokumentacji.Odpowiedzi:
Pierwszą rzeczą, którą muszę wyjaśnić, jest: model jest warstwą .
Po drugie: istnieje różnica między klasycznym MVC a tym, czego używamy do tworzenia stron internetowych. Oto trochę starszej odpowiedzi, którą napisałem, która krótko opisuje, jak się różnią.
Czym NIE jest model:
Model nie jest klasą ani żadnym pojedynczym obiektem. Bardzo często popełniany jest błąd (ja też to zrobiłem, chociaż oryginalna odpowiedź została napisana, gdy zacząłem uczyć się inaczej) , ponieważ większość ram utrwala to błędne przekonanie.
Nie jest to również technika mapowania obiektowo-relacyjnego (ORM) ani abstrakcja tabel baz danych. Każdy, kto mówi inaczej, najprawdopodobniej próbuje „sprzedać” inną zupełnie nową ORM lub całą platformę.
Co to jest model:
Przy odpowiedniej adaptacji MVC, M zawiera całą logikę biznesową domeny, a Warstwa Modelowa składa się głównie z trzech rodzajów struktur:
Obiekty Domeny
W tym miejscu możesz zdefiniować sposób sprawdzania poprawności danych przed wysłaniem faktury lub obliczenia całkowitego kosztu zamówienia. Jednocześnie obiekty domenowe są całkowicie nieświadome miejsca do przechowywania - ani skąd (baza danych SQL, interfejs API REST, plik tekstowy itp.), Ani nawet jeśli zostaną zapisane lub odzyskane.
Mapujący dane
Te obiekty są odpowiedzialne tylko za przechowywanie. Jeśli przechowujesz informacje w bazie danych, to właśnie tam mieszka SQL. A może używasz pliku XML do przechowywania danych, a twoi maperzy parsują zi do plików XML.
Usługi
Możesz myśleć o nich jako o „obiektach domeny wyższego poziomu”, ale zamiast logiki biznesowej, Usługi są odpowiedzialne za interakcję między obiektami domeny a Mapperami . Struktury te tworzą w końcu „publiczny” interfejs do interakcji z logiką biznesową domeny. Możesz ich uniknąć, ale pod groźbą wycieku logiki domeny do kontrolerów .
W pytaniu dotyczącym implementacji ACL znajduje się odpowiednia odpowiedź na ten temat - może być przydatna.
Komunikacja między warstwą modelu a innymi częściami triady MVC powinna odbywać się tylko za pośrednictwem Usług . Wyraźna separacja ma kilka dodatkowych zalet:
Jak wchodzić w interakcje z modelem?
Uzyskiwanie dostępu do wystąpień usług
Zarówno w przypadku widoków, jak i kontrolerów (które można nazwać „warstwą interfejsu użytkownika”) w celu uzyskania dostępu do tych usług, istnieją dwa ogólne podejścia:
Jak można podejrzewać, pojemnik DI jest o wiele bardziej eleganckim rozwiązaniem (choć nie jest najłatwiejszy dla początkującego). Dwie biblioteki, które polecam rozważyć pod kątem tej funkcjonalności, to samodzielny komponent DependencyInjection firmy Syfmony lub Auryn .
Zarówno rozwiązania wykorzystujące fabrykę, jak i kontener DI pozwoliłyby również na udostępnianie wystąpień różnych serwerów, które mają być współużytkowane przez wybrany kontroler i podgląd dla danego cyklu żądanie-odpowiedź.
Zmiana stanu modelu
Teraz, gdy masz dostęp do warstwy modelu w kontrolerach, musisz zacząć z nich korzystać:
Twoje kontrolery mają bardzo jasne zadanie: weź dane wejściowe użytkownika i, na podstawie tych danych wejściowych, zmień aktualny stan logiki biznesowej. W tym przykładzie zmieniane są stany: „użytkownik anonimowy” i „użytkownik zalogowany”.
Kontroler nie jest odpowiedzialny za sprawdzanie poprawności danych wejściowych użytkownika, ponieważ jest to część reguł biznesowych, a kontroler zdecydowanie nie wywołuje zapytań SQL, takich jak to, co zobaczysz tutaj lub tutaj (proszę ich nie nienawidzić, są wprowadzane w błąd, nie są złe).
Pokazuje użytkownikowi zmianę stanu.
Ok, użytkownik się zalogował (lub nie powiódł się). Co teraz? Ten użytkownik nadal nie jest tego świadomy. Musisz więc właściwie zareagować i to jest odpowiedzialność za widok.
W tym przypadku widok wygenerował jedną z dwóch możliwych odpowiedzi, w oparciu o bieżący stan warstwy modelu. W przypadku innego przypadku użycia widok wybierałby różne szablony do renderowania, na podstawie czegoś takiego jak „aktualnie wybrany artykuł”.
Warstwa prezentacji może być dość skomplikowana, jak opisano tutaj: Zrozumienie widoków MVC w PHP .
Ale właśnie tworzę interfejs API REST!
Oczywiście zdarzają się sytuacje, w których jest to przesada.
MVC jest tylko konkretnym rozwiązaniem dla zasady separacji problemów . MVC oddziela interfejs użytkownika od logiki biznesowej, aw interfejsie użytkownika oddzielił obsługę danych wejściowych i prezentacji użytkownika. To jest kluczowe. Chociaż często ludzie określają to jako „triadę”, tak naprawdę nie składa się ona z trzech niezależnych części. Struktura jest bardziej taka:
Oznacza to, że gdy logika warstwy prezentacji jest prawie nieistniejąca, pragmatyczne podejście polega na zachowaniu jej jako pojedynczej warstwy. Może także znacznie uprościć niektóre aspekty warstwy modelu.
Korzystając z tego podejścia, przykład logowania (dla interfejsu API) można zapisać jako:
Chociaż nie jest to trwałe, jeśli masz skomplikowaną logikę renderowania treści odpowiedzi, to uproszczenie jest bardzo przydatne w przypadku bardziej trywialnych scenariuszy. Ale uwaga , takie podejście stanie się koszmarem, gdy spróbujesz użyć go w dużych bazach kodowych ze złożoną logiką prezentacji.
Jak zbudować model?
Ponieważ nie ma jednej klasy „Model” (jak wyjaśniono powyżej), tak naprawdę nie „buduje się modelu”. Zamiast tego zaczynasz od tworzenia Usług , które są w stanie wykonywać określone metody. A następnie zaimplementuj Obiekty Domeny i Maperów .
Przykład metody usługi:
W obu powyższych podejściach zastosowano tę metodę logowania do usługi identyfikacji. Jak by to faktycznie wyglądało. Używam nieco zmodyfikowanej wersji tej samej funkcjonalności z biblioteki , którą napisałem ... ponieważ jestem leniwy:
Jak widać, na tym poziomie abstrakcji nic nie wskazuje na to, skąd dane zostały pobrane. Może to być baza danych, ale może to być również próbny obiekt do celów testowych. Nawet osoby mapujące dane, które są do tego faktycznie używane, są ukryte w
private
metodach tej usługi.Sposoby tworzenia twórców map
Aby wdrożyć abstrakcję trwałości, najbardziej elastycznym podejściem jest stworzenie niestandardowych maperów danych .
Od: Książka PoEAA
W praktyce są one implementowane do interakcji z określonymi klasami lub nadklasami. Powiedzmy, że masz
Customer
, aAdmin
w kodzie (zarówno dziedziczenie zUser
klasy nadrzędnej). Oba prawdopodobnie miałyby osobne pasujące mapowanie, ponieważ zawierają one różne pola. Ale skończysz także na wspólnych i często używanych operacjach. Na przykład: aktualizacja czasu „ostatni raz online” . I zamiast uczynić obecnych twórców map bardziej skomplikowanymi, bardziej pragmatycznym podejściem jest stworzenie ogólnego „User Mapper”, który aktualizuje tylko ten znacznik czasu.Kilka dodatkowych komentarzy:
Tabele i model bazy danych
Podczas gdy czasami istnieje bezpośredni związek 1: 1: 1 między tabelą bazy danych, obiektem domeny i maperem , w większych projektach może być mniej powszechny niż się spodziewasz:
Informacje używane przez pojedynczy obiekt domeny mogą być mapowane z różnych tabel, podczas gdy sam obiekt nie ma trwałości w bazie danych.
Przykład: jeśli generujesz raport miesięczny. To zbierałoby informacje z różnych tabel, ale
MonthlyReport
w bazie danych nie ma magicznej tabeli.Pojedynczy program mapujący może wpływać na wiele tabel.
Przykład: gdy przechowujesz dane z
User
obiektu, ten obiekt domeny może zawierać kolekcję innych obiektów domeny -Group
instancji. Jeśli je zmienisz i zapiszeszUser
, program mapujący dane będzie musiał zaktualizować i / lub wstawić wpisy w wielu tabelach.Dane z jednego obiektu domeny są przechowywane w więcej niż jednej tabeli.
Przykład: w dużych systemach (pomyśl: średniej wielkości sieć społecznościowa) przechowywanie danych uwierzytelniających użytkownika i często używanych danych osobno od większych fragmentów treści może być pragmatyczne, co jest rzadko wymagane. W takim przypadku nadal możesz mieć jedną
User
klasę, ale informacje w niej zawarte zależą od tego, czy zostały pobrane pełne szczegóły.Dla każdego obiektu domeny może być więcej niż jeden program odwzorowujący
Przykład: masz witrynę z wiadomościami ze wspólnym kodem opartym zarówno na oprogramowaniu publicznym, jak i oprogramowaniu do zarządzania. Ale chociaż oba interfejsy używają tej samej
Article
klasy, zarządzanie potrzebuje znacznie więcej informacji. W takim przypadku mielibyśmy dwa oddzielne elementy mapujące: „wewnętrzny” i „zewnętrzny”. Każde z nich wykonuje inne zapytania, a nawet korzysta z różnych baz danych (jak w trybie master lub slave).Widok nie jest szablonem
Wyświetl instancje w MVC (jeśli nie używasz wariantu wzorca MVP) są odpowiedzialne za logikę prezentacji. Oznacza to, że każdy widok zwykle żongluje co najmniej kilkoma szablonami. Pozyskuje dane z warstwy modelu a następnie na podstawie otrzymanych informacji wybiera szablon i ustawia wartości.
Jedną z korzyści, jakie z tego zyskujesz, jest możliwość ponownego użycia. Jeśli utworzysz
ListView
klasę, to z dobrze napisanym kodem możesz mieć tę samą klasę, która przekazuje listę użytkowników i komentarze poniżej artykułu. Ponieważ oba mają tę samą logikę prezentacji. Po prostu zmieniasz szablony.Możesz użyć natywnych szablonów PHP lub innego silnika szablonów. Mogą też istnieć biblioteki innych firm, które mogą całkowicie zastąpić wystąpienia programu View .
Co ze starą wersją odpowiedzi?
Jedyną istotną zmianą jest to, co nazywa się Modelem w starej wersji jest w rzeczywistości Usługą . Reszta „analogii bibliotecznej” nieźle sobie radzi.
Jedyną wadą, jaką widzę, jest to, że byłaby to naprawdę dziwna biblioteka, ponieważ zwróciłaby ci informacje z książki, ale nie pozwoliłaby ci dotknąć samej książki, ponieważ w przeciwnym razie abstrakcja zacząłaby „wyciekać”. Być może będę musiał wymyślić bardziej odpowiednią analogię.
Jaki jest związek między widokami a instancjami kontrolera ?
Struktura MVC składa się z dwóch warstw: interfejsu użytkownika i modelu. Głównymi strukturami w warstwie interfejsu użytkownika są widoki i kontroler.
Kiedy masz do czynienia ze stronami internetowymi, które używają wzorca projektowego MVC, najlepszym sposobem jest uzyskanie stosunku 1: 1 między widokami a kontrolerami. Każdy widok reprezentuje całą stronę w Twojej witrynie i ma dedykowany kontroler do obsługi wszystkich przychodzących żądań dla tego konkretnego widoku.
Na przykład, aby przedstawić otwarty artykuł, będziesz mieć
\Application\Controller\Document
i\Application\View\Document
. Zawierałoby to wszystkie główne funkcje warstwy interfejsu użytkownika, jeśli chodzi o obsługę artykułów (oczywiście możesz mieć niektóre komponenty XHR , które nie są bezpośrednio związane z artykułami) .źródło
Wszystko, co jest logiką biznesową, należy do modelu, niezależnie od tego, czy jest to zapytanie do bazy danych, obliczenia, wywołanie REST itp.
Możesz mieć dostęp do danych w samym modelu, wzorzec MVC nie ogranicza tego. Możesz pokryć go usługami, programami mapującymi i tym podobnymi, ale faktyczna definicja modelu to warstwa, która obsługuje logikę biznesową, nic więcej, nic więcej. Może to być klasa, funkcja lub pełny moduł z obiektami gazillionowymi, jeśli tego chcesz.
Zawsze łatwiej jest mieć osobny obiekt, który faktycznie wykonuje zapytania do bazy danych, zamiast wykonywać je bezpośrednio w modelu: będzie to szczególnie przydatne podczas testowania jednostkowego (ze względu na łatwość wstrzykiwania fałszywej zależności bazy danych w modelu):
Ponadto w PHP rzadko trzeba wychwytywać / odrzucać wyjątki, ponieważ ślad jest zachowany, szczególnie w przypadku takim jak twój przykład. Po prostu pozwól, aby wyjątek został zgłoszony i zamiast tego złap go w kontrolerze.
źródło
User
Klasa zasadniczo rozszerza model, ale itsn't obiekt. Użytkownik powinien być obiektem i mieć takie właściwości jak: id, name ... WdrażaszUser
klasę pomocnika.User
oznacza obiekt i powinien on mieć właściwości użytkownika, a nie metody typuCheckUsername
: co należy zrobić, jeśli chcesz utworzyć nowyUser
obiekt?new User($db)
W sieci - „MVC” możesz robić, co chcesz.
Oryginalna koncepcja (1) opisywała model jako logikę biznesową. Powinien reprezentować stan aplikacji i wymuszać pewną spójność danych. Takie podejście jest często opisywane jako „model tłuszczu”.
Większość frameworków PHP stosuje bardziej płytkie podejście, gdzie model jest tylko interfejsem bazy danych. Ale przynajmniej te modele powinny nadal weryfikować przychodzące dane i relacje.
Tak czy inaczej, nie będziesz bardzo daleko, jeśli oddzielisz SQL lub połączenia z bazą danych na inną warstwę. W ten sposób musisz skupić się na prawdziwych danych / zachowaniu, a nie na faktycznym interfejsie API pamięci. (Jednak przesadne jest nieuzasadnione. Np. Nigdy nie będziesz w stanie zastąpić zaplecza bazy danych magazynem, jeśli nie zostało to wcześniej zaplanowane).
źródło
Więcej oftenly większość aplikacji będzie miał danych, wyświetlanie i przetwarzanie części i po prostu umieścić wszystkie te, w listach
M
,V
iC
.Model (
M
) -> Ma atrybuty utrzymujące stan aplikacji i nie wie nic oV
iC
.View (
V
) -> Ma format wyświetlania aplikacji i wie tylko o tym, jak na nim przetrawić model, i nie przejmuje się tymC
.Controller (
C
) ----> Ma część przetwarzania aplikacji i działa jak połączenie między M i V i zależy od obuM
, wV
przeciwieństwieM
doV
.Zasadniczo istnieje oddzielny problem. W przyszłości wszelkie zmiany lub rozszerzenia mogą być bardzo łatwo dodawane.
źródło
W moim przypadku mam klasę bazy danych, która obsługuje wszystkie bezpośrednie interakcje z bazą danych, takie jak zapytania, pobieranie itp. Więc gdybym musiał zmienić bazę danych z MySQL na PostgreSQL , nie byłoby żadnego problemu. Dlatego dodanie tej dodatkowej warstwy może być przydatne.
Każda tabela może mieć własną klasę i określone metody, ale aby uzyskać dane, klasa bazy danych może sobie z tym poradzić:
Plik
Database.php
Klasa obiektu tabeliL
Mam nadzieję, że ten przykład pomoże ci stworzyć dobrą strukturę.
źródło
Database
w tym przykładzie nie ma klasy. To tylko opakowanie dla funkcji. Jak możesz mieć „klasę obiektów tabeli” bez obiektu?