De weergaven voor inloggen en uitloggen. Model-update-Bekijk patroon en afhankelijke typen

Voornamelijk voor het ontwikkelen van gebruikersinterfaces. Om het te gebruiken, moet u een modeltype maken dat de volledige status van het programma vertegenwoordigt, het berichttype dat de gebeurtenissen van de externe omgeving beschrijft, waarop het programma moet reageren door zijn status te wijzigen, de updater-functie, die van de oude staat en message creëert een nieuwe status van het programma en de view-functie, die de vereiste effecten op de externe omgeving berekent uit de status van het programma, die gebeurtenissen van het type Message oproepen. Het patroon is erg handig, maar het heeft een klein nadeel: het laat niet toe om te beschrijven welke gebeurtenissen zinvol zijn voor specifieke staten van het programma.

Een soortgelijk probleem doet zich voor (en wordt opgelost) bij het gebruik van de OO-patroonstatus.

De Elm-taal is eenvoudig, maar zeer strikt - het controleert of de updater-functie op de een of andere manier alle mogelijke combinaties van modelstatus en berichtgebeurtenis afhandelt. Daarom moet u onnodige, zij het triviale - schrijven - in de regel, het model ongewijzigd laten, code. Ik wil laten zien hoe dit kan worden vermeden in complexere talen - Idris, Scala, C ++ en Haskell.

Alle hier getoonde code is beschikbaar op GitHub om te experimenteren. Laten we eens kijken naar de meest interessante plaatsen.


De msg-functie is ongebruikelijk - het retourneert geen waarde, maar een type. Tijdens runtime is er niets bekend over waardetypes - de compiler wist alle onnodige informatie. Dat wil zeggen, een dergelijke functie kan alleen worden aangeroepen tijdens het compileren.

MUV is een constructeur. Het accepteert parameters: model - de initiële status van het programma, updater - de functie van het bijwerken van de status van een externe gebeurtenis en bekijken - de functie van het creëren van een externe weergave. Merk op dat het type updater en weergavefuncties afhangt van de waarde van het model (met behulp van de msg-functie van de typeparameters).

Laten we nu eens kijken hoe we deze applicatie kunnen uitvoeren.

MuvRun: (ApplicatiemodelType msgType IO) -> IO a muvRun (MUV-modelupdaterweergave) = doe msg<- view model muvRun (MUV (updater model msg) updater view)
Als externe weergave (view) hebben we gekozen voor een I/O-operatie (in Idris, net als in Haskell, zijn I/O-operaties eersteklas waarden, zodat ze zouden worden uitgevoerd, aanvullende acties moeten worden ondernomen, meestal retourneren zo'n operatie van de hoofdfunctie).

IO in één oogopslag

Bij het uitvoeren van een bewerking van het type (IO a) treedt enige invloed op de buitenwereld op, mogelijk leeg, en wordt een waarde van het type a teruggegeven aan het programma, maar de functies van de standaardbibliotheek zijn zo ontworpen dat deze alleen worden verwerkt door het genereren van een nieuwe waarde van het type IO b. Dit scheidt pure functies van functies met bijwerkingen. Dit is ongebruikelijk voor veel programmeurs, maar het helpt om betrouwbaardere code te schrijven.


Aangezien de muvRun-functie I / O voortbrengt, zou deze IO moeten retourneren, maar aangezien deze nooit zal worden voltooid, kan het type bewerking elk zijn - IO a.

Nu zullen we de soorten entiteiten beschrijven waarmee we gaan werken.

Gegevensmodel = uitgelogd | Login String data MsgOuted = Login String data MsgIned = Logout | Begroet totaal msgType: Model -> Type msgType Uitgelogd = MsgOuted msgType (Logined_) = MsgIned
Het modeltype wordt hier beschreven, wat de aanwezigheid van twee statussen van de interface weergeeft: de gebruiker is niet ingelogd en de gebruiker is ingelogd met een naam van het type String.

Vervolgens beschrijven we twee verschillende berichttypen die relevant zijn voor verschillende varianten van het model - als we zijn uitgelogd, kunnen we alleen inloggen onder een bepaalde naam, en als we al zijn ingelogd, kunnen we uitloggen of hallo zeggen. Idris is een sterk getypeerde taal die je niet toelaat om verschillende typen door elkaar te halen.

Eindelijk een functie die de overeenkomst van de modelwaarde met het berichttype instelt.

De functie wordt tot totaal verklaard - dat wil zeggen, hij mag niet crashen of vastlopen, de compiler zal proberen dit bij te houden. msgType wordt aangeroepen in de compilatiefase, wat betekent dat de totaliteit ervan betekent dat de compilatie niet vastloopt vanwege onze fout, hoewel het niet kan garanderen dat de uitvoering van deze functie de systeembronnen zal uitputten.
Het is ook gegarandeerd dat het "rm -rf /" niet zal uitvoeren omdat er geen IO in de handtekening staat.

Laten we de updater beschrijven:

Totale updater: (m: Model) -> (msgType m) -> Model updater Uitgelogd (Inlognaam) = Ingelogde naam updater (Ingelogde naam) Uitloggen = Uitgelogde updater (Ingelogde naam) Greet = Ingelogde naam
Ik denk dat de logica van deze functie duidelijk is. Ik wil de totaliteit herhalen - het betekent dat de Idris-compiler zal controleren of we alle alternatieven hebben overwogen die door het typesysteem zijn toegestaan. Elm voert ook een dergelijke controle uit, maar hij kan niet weten dat we niet kunnen uitloggen als we nog niet zijn ingelogd, en zal expliciete verwerking van de voorwaarde vereisen

Updater Uitgelogd Uitloggen = ???
Idris daarentegen zal bij een onnodige controle een typemismatch vinden.

Laten we nu naar de weergave gaan - zoals gebruikelijk in de gebruikersinterface, zal dit het moeilijkste deel van de code zijn.

Total loginPage: IO MsgOuted loginPage = do putStr "Login:" map Login getLine total genMsg: String -> MsgIned genMsg "" = Logout genMsg _ = Begroet totale workPage: String -> IO MsgIned workPage name = do putStr ("Hallo," ++ name ++ "\ n") putStr "Voer een lege string in voor uitloggen of niet-leeg voor begroeting \ n" map genMsg getLine totale weergave: (m: Model) -> IO (msgType m) weergave Afgemeld = loginPaginaweergave (Inlogde naam ) = naam werkpagina
view moet een I / O-bewerking maken die berichten retourneert, waarvan het type weer afhangt van de waarde van het model. We hebben twee opties: loginPage, die het bericht "Login:" uitvoert, een tekenreeks van het toetsenbord leest en deze in een Login-bericht en workPage verpakt met een gebruikersnaamparameter, die een begroeting afdrukt en verschillende berichten retourneert (maar van de hetzelfde type - MsgIned), afhankelijk van of de gebruiker een lege of niet-lege tekenreeks invoert. view retourneert een van deze bewerkingen, afhankelijk van de waarde van het model, en de compiler controleert hun type, ook al is het anders.

Nu kunnen we onze applicatie bouwen en uitvoeren.

App: Applicatiemodel Main.msgType IO app = MUV Uitgelogde updater view main: IO () main = muvRun app
Een subtiel punt moet hier worden opgemerkt - de muvRun-functie keert terug IO a waarbij a niet is opgegeven en main van het type is IO (), waar () is de naam van het type, meestal genoemd Eenheid, die een enkele waarde heeft, ook geschreven als leeg stom () ... Maar de compiler handelt dit gemakkelijk af. vervangen in plaats van een ().

Scala- en padafhankelijke typen

Scala ondersteunt afhankelijke typen niet volledig, maar er zijn padafhankelijke typen. In de afhankelijke typetheorie kunnen ze worden beschreven als een variant van het sigmatype. Met padafhankelijke typen kunt u het toevoegen van vectoren uit verschillende vectorruimten verbieden, of beschrijven met wie u kunt kussen. Maar we zullen ze gebruiken voor eenvoudigere taken.

Verzegelde abstracte klasse MsgLogouted case-klasse Login (naam: String) breidt MsgLogouted verzegelde abstract-klasse uit MsgLogined case-klasse Logout () breidt MsgLogined case-klasse uit Greet () breidt MsgLogined abstracte klasse uit View (def run (): Msg) verzegelde abstracte klasse Model (type Bericht def view (): View) case class Uitgelogd () breidt Model uit (type Message = MsgLogouted override def view (): View ....) case class Logined (name: String) breidt Model uit (type Message = MsgLogouted override def view ( ): Weergave ....)
De algebraïsche typen van Scala worden gemodelleerd door middel van overerving. Een soort ontmoet verzegelde abstracte klasse, en voor elke constructor die ervan is geërfd geval klasse... We zullen proberen ze precies als algebraïsche typen te gebruiken, waarbij we alle variabelen beschrijven als behorend tot de ouder verzegelde abstracte klasse.

De klassen MsgLogined en MsgLogouted in ons programma hebben geen gemeenschappelijke voorouder. De view-functie moest over verschillende modelklassen worden verdeeld om toegang te krijgen tot een bepaald type berichten. Dit heeft zijn voordelen, die zullen worden gewaardeerd door OO-supporters - de code blijkt gegroepeerd te zijn in overeenstemming met de bedrijfslogica, alles wat met één use case te maken heeft, is dichtbij. Maar ik had het uitzicht graag opgesplitst in een aparte functie, waarvan de ontwikkeling aan een ander zou kunnen worden gedelegeerd.

Laten we nu updater implementeren

Object Updater (def update (model: Model) (msg: model.Message): Model = (model match (case Logouted () => msg match (case Login (name) => Logined (name)) case Logined (name) => bericht match (case Uitloggen () => Uitgelogd () case Greet () => model))))
Hier beschrijven we, met behulp van padafhankelijke typen, het type van het tweede argument vanaf de waarde van het eerste. Om Scala dergelijke afhankelijkheden te laten accepteren, moeten functies worden beschreven in curried-vorm, dat wil zeggen als een functie van het eerste argument, die een functie van het tweede argument retourneert. Helaas voert Scala op dit moment niet veel van de typecontroles uit waarvoor de compiler voldoende informatie heeft.

Laten we nu de volledige implementatie van het model en de weergave geven

Case klasse Logouted () breidt Model uit (type Message = MsgLogouted override def view (): View = new View (override def run () = (println ("Voer naam in") val name = scala.io.StdIn.readLine () Login (naam)))) case class Aangemeld (naam: String) breidt Model uit (type Bericht = MsgLogined override def view (): View = new View (override def run () = (println (s "Hello, $ name") println ("Lege tekenreeks voor uitloggen, geen voor begroeting.") Scala.io.StdIn.readLine () match (case "" => Logout () case _ => Greet ())))) abstracte klasse View (def run ( ): Msg) object Viewer (def view (model: Model): View = (model.view ()))
Het retourtype van de view-functie hangt af van de instantie van het argument. Maar voor de implementatie draait het om het model.

De applicatie die op deze manier is gemaakt, wordt als volgt gestart

Object Main (import scala.annotation.tailrec @tailrec def process (m: Model) (val msg = Viewer.view (m) .run () process (Updater.update (m) (msg))) def main (args: Array) = (proces (Uitgelogd ())))
De runtime-systeemcode weet dus niets over de interne structuur van de modellen en de soorten berichten, maar de compiler kan controleren of het bericht overeenkomt met het huidige model.

Hier hadden we niet alle mogelijkheden nodig die padafhankelijke typen bieden. Interessante eigenschappen zullen verschijnen als we parallel werken met verschillende instanties van Model-Updater-View-systemen, bijvoorbeeld bij het simuleren van een multi-agent wereld (een weergave zou dan de invloed van de agent op de wereld vertegenwoordigen en feedback ontvangen). In dit geval controleerde de compiler of het bericht werd verwerkt door de agent waarvoor het bedoeld was, ondanks het feit dat alle agenten van hetzelfde type zijn.

C ++

C++ is nog steeds gevoelig voor de volgorde van definities, zelfs als ze allemaal in hetzelfde bestand worden gedaan. Dit zorgt voor enige overlast. Ik zal de code verstrekken in een volgorde die handig is om ideeën te demonstreren. De gecompileerde geordende versie kan worden bekeken op GitHub.

Algebraïsche typen kunnen op dezelfde manier worden geïmplementeerd als in Scala - een abstracte klasse komt overeen met een type, en concrete overerven komen overeen met constructors (laten we ze "constructorklassen" noemen, om niet te worden verward met gewone C++-constructors) van een algebraïsch type.

C ++ heeft ondersteuning voor padafhankelijke typen, maar de compiler kan dat type niet abstract gebruiken zonder het echte type te kennen waarmee het is geassocieerd. Daarom is het onmogelijk om Model-Updater-View met hun hulp te implementeren.

Maar C++ heeft een krachtig sjabloonsysteem. De afhankelijkheid van het type van de waarde van het model kan worden verborgen in de sjabloonparameter van een gespecialiseerde versie van het uitvoerende systeem.

Struct Processor (virtuele const Processor * volgende () const = 0;); sjabloon struct ProcessorImpl: openbare processor (const CurModel * model; ProcessorImpl (const CurModel * m): model (m) (); const Processor * volgende () const (const View * weergave = model-> weergave (); const typenaam CurModel :: Bericht * msg = bekijken-> uitvoeren (); weergave verwijderen; const Model * newModel = msg-> proces (model); bericht verwijderen; retourneer newModel-> processor (); ));
We beschrijven een abstract uitvoerend systeem, waarbij de enige methode is om te doen wat nodig is en een nieuw uitvoerend systeem terug te geven dat geschikt is voor de volgende iteratie. De specifieke versie heeft een sjabloonparameter en zal gespecialiseerd zijn voor elke "constructorklasse" van het model. Hierbij is het van belang dat alle eigenschappen van het type CurModel tijdens sjabloonspecialisatie worden gecontroleerd met een specifieke typeparameter, en op het moment van compilatie van de sjabloon zelf niet hoeven te worden beschreven (hoewel het mogelijk is met behulp van concepten of andere methoden voor het implementeren van typeklassen). Scala heeft ook een redelijk krachtig systeem van geparametriseerde typen, maar het controleert de eigenschappen van type-parameters tijdens het compileren van het geparametriseerde type. Daar is de implementatie van een dergelijk patroon moeilijk, maar mogelijk dankzij de ondersteuning van typeklassen.

Laten we het model beschrijven.

Struct Model (virtueel ~ Model () (); virtuele const Processor * processor () const = 0;); struct Logined: public Model (struct Message (const virtual Model * process (const Logined * m) const = 0; virtual ~ Message () ();); struct Logout: public Message (const Model * process (const Logined * m) const;); struct Greet: public Message (const Model * process (const Logined * m) const;); const std :: string name; Logined (std :: string lname): name (lname) (); struct LoginedView: openbare weergave (...); const Bekijk * bekijk () const (retourneer nieuwe LoginedView (naam);); const Processor * processor () const (retourneer nieuwe ProcessorImpl (dit); ); ); struct Logouted: public Model (struct Message (const virtual Model * process (const Logouted * m) const = 0; virtual ~ Message () ();); struct Login: public Message (const std :: string naam; Login (std :: string lname): naam (lname) (); const Model * process (const Logouted * m) const;); struct LogoutedView: public View (...); const Bekijk * bekijk () const (retourneer nieuwe LogoutedView ();); const Processor * processor () const (retourneer nieuwe ProcessorImpl (dit); ); );
"Klassen-constructeurs" van het model "dragen alles bij zich" - dat wil zeggen, ze bevatten gespecialiseerde klassen van berichten en weergaven, en weten ook hoe ze een uitvoerend systeem voor zichzelf kunnen creëren. Native View-typen hebben een gemeenschappelijke voorouder voor alle modellen, wat handig kan zijn bij het ontwikkelen van complexere uitvoeringssystemen. Berichttypen zijn in principe volledig geïsoleerd en hebben geen gemeenschappelijke voorouder.

De implementatie van de updater staat los van het model omdat het vereist dat het modeltype al volledig is beschreven.

Const Model * Uitgelogd :: Login :: process (const Uitgelogd * m) const (verwijder m; retourneer nieuwe Logined (naam);); const Model * Ingelogd :: Uitloggen :: process (const Ingelogd * m) const (verwijder m; retourneer nieuwe Uitgelogd ();); const Model * Ingelogd :: Greet :: process (const Ingelogd * m) const (return m;);
Laten we nu alles met betrekking tot de weergave samenvoegen, inclusief de interne entiteiten van de modellen.

Sjabloon struct View (virtuele const Message * run () const = 0; virtual ~ View () (); ); struct Logined: public Model (struct LoginedView: public View (const std :: string naam; LoginedView (std :: string lname): naam (lname) (); virtuele const Message * run () const (char buf; printf ("Hallo% s", naam.c_str ()) ; fgets (buf, 15, stdin); return (* buf == 0 || * buf == "\ n" || * buf == "\ r")? static_cast (nieuwe Uitloggen ()): static_cast (nieuwe Groet); ); ); const Bekijk * bekijk () const (retourneer nieuwe LoginedView (naam);); ); struct Logouted: public Model (struct LogoutedView: public View (virtuele const Message * run () const (char buf; printf ("Login:"); fgets (buf, 15, stdin); retourneer nieuwe Login (buf););); const Bekijk * bekijk () const (retourneer nieuwe LogoutedView ();); );
Laten we tot slot main schrijven

Int main (int argc, char ** argv) (const Processor * p = nieuwe ProcessorImpl (nieuw uitgelogd ()); while (true) (const Processor * pnew = p-> volgende (); delete p; p = pnew;) return 0; )

Weer Scala, met typeklassen

In structuur is deze implementatie bijna identiek aan de C++ versie.

Een soortgelijk deel van de code

abstracte klasse View (def run (): Message) abstracte class Processor (def next (): Processor;) verzegelde abstracte klasse Model (def processor (): Processor) verzegelde abstracte klasse LoginedMessage-caseklasse Logout () breidt LoginedMessage-caseklasse uit Greet ( ) breidt LoginedMessage case-klasse uit Logined (naam: String) breidt Model uit (override def processor (): Processor = new ProcessorImpl (this)) verzegelde abstracte klasse LogoutedMessage case class Login (naam: String) breidt LogoutedMessage case-klasse uit Logouted () breidt Model uit (override def processor (): Processor = new ProcessorImpl (this)) object Main (import scala.annotation.tailrec @tailrec def process (p: Processor) (process (p.next ())) def main (args: Array) = (proces (nieuwe ProcessorImpl (Uitgelogd ()))))


Maar bij de implementatie van de runtime ontstaan ​​​​subtiliteiten.

Class ProcessorImpl (model: M) (impliciete updater: (M, Message) => Model, view: M => View) breidt Processor uit (def next (): Processor = (val v = view (model) val msg = v. run () val newModel = updater (model, msg) newModel.processor ()))
Hier zien we mysterieuze nieuwe opties (impliciete updater: (M, Message) => Model, view: M => View)... Het impliciete sleutelwoord betekent dat de compiler bij het aanroepen van deze functie (meer bepaald de klassenconstructor) in de context zal zoeken naar objecten van geschikte typen die als impliciet zijn gemarkeerd en deze als geschikte parameters doorgeven. Dit is een vrij complex concept en een van de toepassingen ervan is de implementatie van typeclasses. Hier beloven ze de compiler dat we alle noodzakelijke functies zullen bieden voor specifieke implementaties van het model en bericht. Laten we die belofte nu houden.

Object-updaters (impliciete def logoutedUpdater (model: Logouted, msg: LogoutedMessage): Model = ((model, msg) match (case (Logouted (), Login (name)) => Logined (name))) impliciete def viewLogouted (model : Uitgelogd) = nieuwe weergave (overschrijven def run (): LogoutedMessage = (println ("Voer naam in") val naam = scala.io.StdIn.readLine () Login (naam))) impliciete def loginedUpdater (model: Logined, msg : LoginedMessage): Model = ((model, msg) match (case (Logined (name), Logout ()) => Logouted () case (Logined (name), Greet ()) => model)) impliciete def viewLogined ( model: Logined) = nieuwe weergave (val name = model.name override def run (): LoginedMessage = (println (s "Hallo, $ naam") println ("Lege tekenreeks voor uitloggen, geen teken voor begroeting.") scala.io .StdIn.readLine () match (case "" => Logout () case _ => Greet ())))) import updaters._

Haskell

Er zijn geen afhankelijke typen in de reguliere Haskell. Het mist ook overerving, die we voornamelijk gebruikten bij het implementeren van het patroon in Scala en C ++. Maar overerving op één niveau (met elementen van afhankelijke typen) kan worden gemodelleerd met behulp van min of meer standaard taalextensies -TypeFamilies en ExistentialQuantification. Voor de algemene interface van kind-OOP-klassen wordt een typeklasse gemaakt, waarin er een afhankelijk "familie" -type is, de onderliggende klassen zelf worden vertegenwoordigd door een afzonderlijk type en vervolgens verpakt in een "existentieel" type met een enkele constructor .

Gegevensmodel = voor alle m. (Updatable m, Viewable m) => Model m class Updateable m where data Bericht m :: * update :: m -> (Message m) -> Model class (Updatable m) => Viewable m where view :: m -> (Bekijken (Bericht m)) gegevens Uitgelogd = Uitgelogde gegevens Ingelogd = Ingelogd String
Ik heb geprobeerd de updater en weergave zo ver mogelijk te verspreiden, dus ik heb twee verschillende typeklassen gemaakt, maar tot nu toe bleek het slecht.

De implementatie van de updater is eenvoudig

Instance Bijwerkbaar Uitgelogd waar gegevens Bericht Uitgelogd = Inloggen String update Uitgelogd (Inlognaam) = Model (Inlogde naam) instantie Bijwerkbaar Ingelogd waar gegevens Bericht Ingelogd = Uitloggen | Update begroeting m Uitloggen = Model Uitgelogde update m Begroeting = Model m
IO moest worden vastgesteld als View. Pogingen om het abstracter te maken hebben alles enorm gecompliceerd en de samenhang van de code vergroot - het modeltype moet weten welke weergave we gaan gebruiken.

Import System.IO type View a = IO een instance Zichtbaar Uitgelogd waar view Uitgelogd = do putStr "Login:" hFlush stdout fmap Login getLine instance Zichtbaar Aangemeld waar view (Logined name) = do putStr $ "Hallo" ++ name ++ " ! \ n "hFlush stdout l<- getLine pure $ if l == "" then Logout else Greeting
Welnu, de uitvoerbare omgeving verschilt niet veel van die in Idris.

RunMUV :: Model -> IO een runMUV (Model m) = do msg<- view m runMUV $ update m msg main:: IO () main = runMUV (Model Logouted)

Velen beginnen een project te schrijven om met een enkele taak te werken, zonder te impliceren dat het kan uitgroeien tot een beheersysteem voor meerdere gebruikers, nou ja, laten we zeggen, inhoud, of God verhoede, productie. En alles lijkt cool en cool te zijn, alles werkt totdat je begint te begrijpen dat de code die wordt geschreven volledig uit krukken en hardcode bestaat. Code vermengd met lay-out, verzoeken en krukken, soms zelfs onleesbaar. Er doet zich een urgent probleem voor: bij het toevoegen van nieuwe functies moet je heel lang aan deze code sleutelen, onthoudend "wat stond daar dat was?" en vervloek jezelf in het verleden.

Je hebt misschien zelfs gehoord van ontwerppatronen en zelfs door deze uitstekende boeken gebladerd:

  • E. Gamma, R. Helm, R. Johnson, J. Vlissides "Objectgeoriënteerde ontwerptechnieken. Ontwerp patronen ";
  • M. Fowler "Architectuur van bedrijfssoftwaretoepassingen".
En velen, die niet bang waren voor enorme handleidingen en documentatie, probeerden een van de moderne kaders te bestuderen en werden geconfronteerd met de moeilijkheid om te begrijpen (vanwege de aanwezigheid van vele architecturale concepten die slim met elkaar verbonden waren) om de studie en toepassing van moderne hulpmiddelen op een laag pitje.

Het gepresenteerde artikel zal vooral nuttig zijn voor beginners. Hoe dan ook, ik hoop dat je over een paar uur een idee zult krijgen van de implementatie van het MVC-patroon dat ten grondslag ligt aan alle moderne webframeworks, en ook wat "voedsel" krijgt om verder na te denken over "hoe te doen het." Aan het einde van het artikel vindt u een selectie van handige links die u ook zullen helpen begrijpen waar webframeworks (naast MVC) van gemaakt zijn en hoe ze werken.

Het is onwaarschijnlijk dat hard-core PHP-programmeurs iets nieuws voor zichzelf zullen vinden in dit artikel, maar hun opmerkingen en commentaar op de hoofdtekst zouden zeer nuttig zijn! Omdat zonder theorie is praktijk onmogelijk, en zonder praktijk is theorie nutteloos, dan komt er eerst een beetje theorie, en dan gaan we verder met de praktijk. Als je al bekend bent met het concept van MVC, kun je het theoriegedeelte overslaan en meteen naar de praktijk gaan.

1. Theorie

Het MVC-patroon beschrijft een eenvoudige manier om de structuur van een applicatie te bouwen, met als doel de bedrijfslogica te scheiden van de gebruikersinterface. Hierdoor is de applicatie makkelijker te schalen, te testen, te onderhouden en natuurlijk te implementeren.

Laten we eens kijken naar het conceptuele schema van het MVC-patroon (naar mijn mening is dit het meest succesvolle schema dat ik heb gezien):

In MVC-architectuur biedt het model gegevens en bedrijfslogicaregels, is de view verantwoordelijk voor de gebruikersinterface en is de controller verantwoordelijk voor de interactie tussen het model en de view.

Een typische workflow voor een MVC-toepassing kan als volgt worden beschreven:

  1. Wanneer een gebruiker een webresource invoert, maakt het initialisatiescript een instantie van de toepassing en start deze voor uitvoering.
    Dit toont een weergave van bijvoorbeeld de startpagina van de site.
  2. De applicatie ontvangt een verzoek van de gebruiker en bepaalt de gevraagde controller en actie. In het geval van een stramienpagina is de standaardactie ( inhoudsopgave).
  3. De applicatie start de controller en voert de actiemethode uit,
    die bijvoorbeeld modelaanroepen bevat die informatie uit de database lezen.
  4. Daarna genereert de actie een weergave met de gegevens die uit het model zijn verkregen en geeft het resultaat aan de gebruiker weer.
Model- bevat de bedrijfslogica van de applicatie en bevat methoden voor het ophalen (dit kunnen ORM-methoden zijn), verwerking (bijvoorbeeld validatieregels) en het verstrekken van specifieke gegevens, waardoor het vaak erg dik wordt, wat heel normaal is.
Het model mag niet rechtstreeks communiceren met de gebruiker. Alle variabelen met betrekking tot het verzoek van de gebruiker moeten in de controller worden verwerkt.
Het model mag geen HTML of andere weergavecode genereren die kan veranderen op basis van de behoeften van de gebruiker. Dergelijke code moet in views worden verwerkt.
Hetzelfde model, bijvoorbeeld: het gebruikersauthenticatiemodel kan zowel in het gebruikers- als in het administratieve deel van de applicatie worden gebruikt. In dit geval kunt u de algemene code naar een aparte klasse verplaatsen en ervan erven, waarbij u subtoepassingsspecifieke methoden definieert in de erven.

Weergave- wordt gebruikt om de externe weergave van gegevens die van de controller en het model worden ontvangen, in te stellen.
Weergaven bevatten HTML-opmaak en kleine invoegingen van PHP-code om gegevens te doorkruisen, op te maken en weer te geven.
Mag niet rechtstreeks toegang krijgen tot de database. Dit moet worden gedaan door modellen.
Zou niet moeten werken met gegevens die zijn verkregen uit een gebruikersverzoek. Deze taak moet worden uitgevoerd door de controller.
Heeft direct toegang tot de eigenschappen en methoden van de controller of modellen om kant-en-klare gegevens te krijgen.
Weergaven zijn meestal onderverdeeld in een gemeenschappelijke sjabloon die de opmaak bevat die voor alle pagina's geldt (bijvoorbeeld een kop- en voettekst) en delen van de sjabloon die worden gebruikt om gegevensuitvoer van het model weer te geven of formulieren voor gegevensinvoer weer te geven.

Controller- de lijm die modellen, aanzichten en andere componenten verbindt tot een productietoepassing. De verwerkingsverantwoordelijke is verantwoordelijk voor het afhandelen van gebruikersverzoeken. De controller mag geen SQL-query's bevatten. Ze worden het best bewaard in modellen. De controller mag geen HTML of andere opmaak bevatten. Het moet in de soort worden verwijderd.
In een goed ontworpen MVC-toepassing zijn controllers meestal erg dun en bevatten ze slechts enkele tientallen regels code. Dat kan niet gezegd worden over Stupid Fat Controllers (SFC) in CMS Joomla. De logica van de controller is vrij typisch en het meeste wordt uitgevoerd in basisklassen.
Modellen daarentegen zijn erg dik en bevatten de meeste gegevensverwerkingscode. de gegevensstructuur en bedrijfslogica die het bevat, is meestal vrij toepassingsspecifiek.

1.1. Frontcontroller en paginacontroller

In de meeste gevallen vindt de interactie van de gebruiker met de webapplicatie plaats door op links te klikken. Kijk nu in de adresbalk van uw browser - via deze link heeft u deze tekst ontvangen. Andere links, zoals die aan de rechterkant van deze pagina, zullen u andere inhoud geven. De link vertegenwoordigt dus een specifieke opdracht naar de webtoepassing.

Ik hoop dat je al hebt gemerkt dat verschillende sites perfecte verschillende formaten kunnen hebben voor het bouwen van de adresbalk. Elk formaat kan de architectuur van een webapplicatie vertegenwoordigen. Hoewel dit niet altijd het geval is, is het in de meeste gevallen een duidelijk feit.

Overweeg twee opties voor de adresbalk, die een soort tekst en gebruikersprofiel tonen.

Eerste optie:

  1. www.example.com/article.php?id=3
  2. www.voorbeeld.com/gebruiker.php?id=4
Hier is elk script verantwoordelijk voor het uitvoeren van een specifiek commando.

Tweede optie:

  1. www.voorbeeld.com/index.php?article=3
  2. www.voorbeeld.com/index.php?user=4
En hier gebeuren alle oproepen in één scenario. index.php.

Je kunt de multi-touchpoint-benadering zien op de phpBB-forums. Het forum wordt bekeken via een script viewforum.php, het onderwerp bekijken via viewtopic.php enzovoort. De tweede benadering, met toegang via een enkel fysiek scriptbestand, kan worden waargenomen in mijn favoriete CMS MODX, waar alle oproepen doorlopen index.php.

Deze twee benaderingen zijn totaal verschillend. De eerste is kenmerkend voor het Page Controller-patroon en de tweede benadering wordt geïmplementeerd door het Front Controller-patroon. De paginacontroller is goed voor sites met vrij eenvoudige logica. Op zijn beurt consolideert de verzoekcontroller alle acties voor het verwerken van verzoeken op één plek, wat hem extra mogelijkheden geeft, waardoor het mogelijk is om moeilijkere taken uit te voeren dan gewoonlijk door de paginacontroller wordt afgehandeld. Ik zal niet ingaan op de details van de implementatie van de paginacontroller, maar ik zal alleen zeggen dat in het praktische gedeelte de verzoekcontroller zal worden ontwikkeld (enige schijn).

1.2. URL-routering

Met URL-routering kunt u uw applicatie configureren om verzoeken te accepteren van URL's die niet overeenkomen met de daadwerkelijke bestanden in de applicatie, en om CNC's te gebruiken die semantisch betekenisvol zijn voor gebruikers en die de voorkeur hebben voor zoekmachineoptimalisatie.

Voor een normale pagina waarop een contactformulier wordt weergegeven, kan de URL er bijvoorbeeld als volgt uitzien:
http://www.example.com/contacts.php?action=feedback

Geschatte verwerkingscode in dit geval:
switch ($ _GET ["action"]) (case "about": required_once ("about.php"); // "Over ons" pagina pauze; case "contacten": required_once ("contacts.php"); // "Contacten" pagina pauze; case "feedback": required_once ("feedback.php"); // "Feedback" pagina pauze; standaard: required_once ("page404.php"); // pagina "404" pauze; )
Ik denk dat bijna iedereen dit al eens heeft gedaan.

Met behulp van de URL-routeringsengine kunt u uw toepassing configureren om deze verzoeken te accepteren om dezelfde informatie weer te geven:
http://www.voorbeeld.com/contacts/feedback

Hier is contacten een controller en feedback is een controllermethode die contacten wordt genoemd en die een feedbackformulier weergeeft, enz. In het praktijkgedeelte komen we hierop terug.

Het is ook de moeite waard om te weten dat u met veel webframework-routers willekeurige URL-routes kunt maken (specificeer wat elk deel van de URL betekent) en hoe u ermee om moet gaan.
We hebben nu voldoende theoretische kennis om over te gaan naar de praktijk.

2. Oefenen

Laten we eerst de volgende bestands- en mapstructuur maken:


Vooruitkijkend, zal ik zeggen dat de kernmap de basismodel-, weergave- en controllerklassen zal opslaan.
Hun afstammelingen worden opgeslagen in de directory's controllers, modellen en views. Bestand index.php dit is een punt in het begin van de app. Bestand bootstrap.php start het downloaden van de applicatie, verbindt alle benodigde modules, enz.

We zullen consequent gaan; open het bestand index.php en vul het met de volgende code:
ini_set ("display_errors", 1); vereisen_once "applicatie / bootstrap.php";
Er mogen geen vragen zijn.

Laten we vervolgens direct naar het bestand gaan bootstrap.php:
vereisen_once "core / model.php"; vereisen_once "core / view.php"; vereisen_once "core / controller.php"; vereisen_once "core / route.php"; Route :: begin (); // start de router
De eerste drie regels bevatten niet-bestaande kernbestanden. De laatste regels verbinden het bestand met de routerklasse en starten het voor uitvoering door de statische startmethode aan te roepen.

2.1. URL-router implementatie

Laten we voorlopig afwijken van de implementatie van het MVC-patroon en de routering behandelen. De eerste stap die we moeten doen, is de volgende code in te schrijven: .htaccess:
RewriteEngine op RewriteCond% (REQUEST_FILENAME)! -F RewriteCond% (REQUEST_FILENAME)! -D RewriteRule. * Index.php [L]
Deze code zal de verwerking van alle pagina's omleiden naar: index.php, dat is wat we nodig hebben. Weet je nog dat we het in het eerste deel hadden over de Front Controller?!

We zetten de routing in een apart bestand route.php naar de kernmap. In dit bestand zullen we de Route-klasse beschrijven, die de controller-methoden zal uitvoeren, die op hun beurt de paginaweergave zullen genereren.

Inhoud van route.php-bestand

klas Route ( statisch functie start () ( // controller en standaardactie$ controller_name = "Hoofd"; $ action_name = "index"; $ routes = explode ("/", $ _SERVER ["REQUEST_URI"]); // haal de naam van de controller op if (! leeg ($ routes)) ($ controllernaam = $ routes;) // haal de naam van de actie op if (! leeg ($ routes)) ($ action_name = $ routes;) // voeg voorvoegsels toe$ model_name = "Model_" $ controller_name; $ controller_name = "Controller_" $ controller_name; $ action_name = "action_" $ action_name; // koppel het bestand aan de modelklasse (het modelbestand bestaat mogelijk niet)$ model_file = strtolower ($ model_name) ".php"; $ model_path = "toepassing / modellen /" $ model_file; if (file_exists ($ model_path)) (inclusief "application / models /". $ model_file;) // koppel het bestand aan de controllerklasse$ controller_file = strtolower ($ controller_name) ".php"; $ controller_path = "toepassing / controllers /" $ controller_file; if (file_exists ($ controller_path)) (inclusief "application / controllers /". $ controller_file;) else ( / * het zou correct zijn om hier een uitzondering te plaatsen, maar voor de eenvoud zullen we onmiddellijk doorverwijzen naar een 404-pagina * / Route :: ErrorPage404 (); ) // maak een controller$ controller = nieuw $ controllernaam; $ actie = $ actienaam; if (method_exists ($ controller, $ actie)) ( // roep de actie van de controller aan$ controller -> $ actie (); ) anders ( // het zou ook redelijker zijn om hier een uitzondering te plaatsen Route :: ErrorPage404 (); )) functie ErrorPage404 () ($ host = "http: //". $ _ SERVER ["HTTP_HOST"]. "/"; header ("HTTP / 1.1 404 niet gevonden"); header ("Status: 404 niet gevonden"); header ("Locatie:". $ host. "404"); ))


Merk op dat de klasse een zeer vereenvoudigde logica implementeert (ondanks de omvangrijke code) en zelfs beveiligingsproblemen kan hebben. Dit is met opzet gedaan, aangezien het schrijven van een volwaardige routeringsklasse verdient op zijn minst een apart artikel. Laten we eens kijken naar de belangrijkste punten ...

Het element van de globale array $ _SERVER ["REQUEST_URI"] bevat het volledige adres waarmee de gebruiker contact heeft opgenomen.
Bijvoorbeeld: voorbeeld.ru/contacts/feedback

De functie gebruiken: ontploffen het adres is opgedeeld in onderdelen. Als resultaat krijgen we de naam van de controller, voor het gegeven voorbeeld is dit de controller contacten en de naam van de actie, in ons geval - feedback.

Vervolgens wordt het modelbestand gekoppeld (het model kan afwezig zijn) en het controllerbestand, indien aanwezig, en ten slotte wordt de controller geïnstantieerd en wordt de actie aangeroepen, opnieuw als deze werd beschreven in de controllerklasse.

Dus als u bijvoorbeeld naar het adres gaat:
voorbeeld.com/portfolio
of
voorbeeld.com/portfolio/index
de router doet het volgende:

  1. zal het bestand model_portfolio.php uit de map modellen bevatten die de klasse Model_Portfolio bevat;
  2. zal het bestand controller_portfolio.php uit de map controllers bevatten die de klasse Controller_Portfolio bevat;
  3. zal een instantie van de klasse Controller_Portfolio maken en de standaardactie - action_index die erin wordt beschreven, aanroepen.
Als de gebruiker contact probeert te maken met het adres van een niet-bestaande controller, bijvoorbeeld:
voorbeeld.com/ufo
dan wordt het doorgestuurd naar de "404"-pagina:
voorbeeld.com/404
Hetzelfde gebeurt als de gebruiker een actie onderneemt die niet in de controller is beschreven.

2.2. Terug naar de MVC-implementatie

Laten we naar de kernmap gaan en nog drie bestanden toevoegen aan het route.php-bestand: model.php, view.php en controller.php


Laat me je eraan herinneren dat ze basisklassen zullen bevatten, die we nu gaan schrijven.

Bestandsinhoud model.php
klassemodel ( openbaar functie get_data () ( } }
De modelklasse bevat een enkele lege methode voor het ophalen van gegevens die zal overlappen in afstammelingenklassen. Wanneer we afstammelingenklassen maken, wordt alles duidelijker.

Bestandsinhoud bekijk.php
klas bekijken ( // public $ template_view; // hier kunt u de standaard algemene weergave specificeren. functie genereren ( $ content_view, $ template_view, $ data = null) { / * if (is_array ($ data)) (// transformeer de array-elementen in variabelen extract ($ data);) * / omvatten "applicatie / views /". $ template_view; ))
Het is niet moeilijk te raden dat de methode genereren ontworpen om een ​​uitzicht te vormen. De volgende parameters worden eraan doorgegeven:

  1. $ content_file - weergaven die pagina-inhoud weergeven;
  2. $ template_file - sjabloon gemeenschappelijk voor alle pagina's;
  3. $ data is een array die pagina-inhoudsitems bevat. Meestal ingevuld in het model.
De functie include verbindt dynamisch de algemene sjabloon (weergave), waarin de weergave wordt ingesloten
om de inhoud van een specifieke pagina weer te geven.

In ons geval bevat de algemene sjabloon koptekst, menu, zijbalk en voettekst, en de inhoud van de pagina's wordt in een apart formulier opgenomen. Nogmaals, dit is gedaan voor de eenvoud.

Bestandsinhoud controller.php
klasse Controller ( openbaar $-model; openbare $ weergave; functie __construct () ($ dit -> weergave = nieuwe weergave (); )))
Methode action_index- dit is de standaardactie, we zullen deze overschrijven bij het implementeren van de afstammelingenklassen.

2.3. Afstammelingenklassen Model en Controller implementeren, View "s . maken

Nu begint het plezier! Onze visitekaartjessite zal bestaan ​​uit de volgende pagina's:
  1. huis
  2. Diensten
  3. Portefeuille
  4. Contacten
  5. En ook - pagina "404"
Elke pagina heeft zijn eigen controller uit de map controllers en een weergave uit de map views. Sommige pagina's kunnen het model of de modellen uit de modellenmap gebruiken.


In de vorige afbeelding is het bestand afzonderlijk gemarkeerd. template_view.php Is een sjabloon met opmaak die voor alle pagina's geldt. In het eenvoudigste geval kan het er als volgt uitzien:
<html lang = "ru"> <hoofd> <meta charset = "utf-8"> <titel> huistitel> hoofd> <lichaam> $ inhoud_weergave; ?> lichaam> html>
Om de site een presentabel uiterlijk te geven, maken we een CSS-sjabloon en integreren deze in onze site door de structuur van de HTML-opmaak te wijzigen en CSS- en JavaScript-bestanden aan elkaar te koppelen:
<link rel = "stylesheet" type = "text / css" href = "/ css / style.css" /> <script src = "/ js / jquery-1.6.2.js" type = "tekst / javascript">script>

Aan het einde van het artikel, in de sectie "Resultaat", staat een link naar de GitHub-repository met een project waarin stappen zijn genomen om een ​​eenvoudige sjabloon te integreren.

2.3.1. Een startpagina maken

Laten we beginnen met de controller controller_main.php, hier is de code:
class Controller_Main breidt Controller uit ( functie action_index () ($ dit -> bekijken-> genereren ("main_view.php", "template_view.php"); ))
In de methode genereren een instantie van de klasse View, de namen van de bestanden van de algemene sjabloon en de weergave met de pagina-inhoud worden doorgegeven.
Naast de indexactie kan de controller natuurlijk nog andere acties bevatten.

We hebben het dossier eerder met een algemeen beeld bekeken. Overweeg een inhoudsbestand main_view.php:
<h1> Welkom!h1> <p> <img src = "/ afbeeldingen / office-small.jpg" align = "left"> <a href = "/"> OLOLOSHA-TEAMeen>- een team van eersteklas websitebouwers met jarenlange ervaring in het verzamelen van Mexicaanse maskers, bronzen en stenen beelden uit India en Ceylon, bas-reliëfs en sculpturen gemaakt door de meesters van Equatoriaal Afrika vijf tot zes eeuwen geleden ...p>
Het bevat eenvoudige opmaak zonder PHP-aanroepen.
Om de hoofdpagina weer te geven, kunt u een van de volgende adressen gebruiken:

  • methoden van bibliotheken die data-abstractie implementeren. Bijvoorbeeld de methoden van de PEAR MDB2-bibliotheek;
  • ORM-methoden;
  • methoden voor het werken met NoSQL;
  • en etc.
  • Voor de eenvoud zullen we hier geen SQL-query's of ORM-statements gebruiken. In plaats daarvan zullen we echte gegevens emuleren en onmiddellijk een reeks resultaten retourneren.
    Modelbestand model_portfolio.php zet het in de map modellen. Hier zijn de inhoud:
    class Model_Portfolio breidt Model ( openbaar functie get_data () ( return array (array ("Year" => "2012", "Site" => "http://DunkelBeer.ru", "Description" => "Promotiesite voor donker Dunkelbier van de Duitse fabrikant Löwenbraü, geproduceerd in Rusland door brouwerij SAN InBev."), array ("Jaar" => "2012", "Site" => "http://ZopoMobile.ru", "Beschrijving" => "Russischtalige catalogus van Chinese telefoons van het bedrijf Zopo op basis van Android OS en bijbehorende accessoires."), // Te doen); ))

    De modelcontrollerklasse bevindt zich in het bestand controller_portfolio.php, hier is de code:
    class Controller_Portfolio breidt Controller ( functie __construct () ($ dit -> model = nieuw Model_Portfolio (); $ dit -> weergave = nieuwe weergave (); ) functie action_index () ($ data = $ dit -> model-> get_data (); $ dit -> bekijken-> genereren ("portfolio_view.php", "template_view.php", $ data); ))
    In een variabele gegevens de array die door de methode wordt geretourneerd, wordt geschreven gegevens verkrijgen waar we eerder naar keken.
    Verder wordt deze variabele doorgegeven als een parameter van de methode genereren, die ook worden doorgegeven: de naam van het bestand met de gemeenschappelijke sjabloon en de naam van het bestand met de weergave met de inhoud van de pagina.

    De weergave met de pagina-inhoud bevindt zich in het bestand portfolio_view.php.

    Portefeuille

    Alle projecten in de volgende tabel zijn fictief, dus probeer niet eens de gegeven links te volgen. " ; }


    GitHub-link: https://github.com/vitalyswipe/tinymvc/zipball/v0.1

    Maar in deze versie heb ik de volgende klassen geschetst (en hun bijbehorende weergaven):

    • Controller_Login waarin een weergave met een formulier voor het invoeren van een login en wachtwoord wordt gegenereerd, na het invullen wordt een authenticatieprocedure uitgevoerd en, indien succesvol, wordt de gebruiker doorgestuurd naar het admin panel.
    • Contorller_Admin met een indexactie, waarin wordt gecontroleerd of de gebruiker eerder als beheerder op de site was geautoriseerd (als hij dat was, wordt de weergave van het beheerderspaneel weergegeven) en een uitlogactie om uit te loggen.
    Authenticatie en autorisatie is een ander onderwerp, dus het wordt hier niet behandeld, maar alleen de hierboven aangegeven link wordt gegeven, zodat er iets is om vanaf te beginnen.

    4. Conclusie

    Het MVC-patroon wordt gebruikt als architecturale basis in veel frameworks en CMS'en, die zijn gemaakt om in kortere tijd complexere oplossingen van hogere kwaliteit te kunnen ontwikkelen. Dit werd mogelijk door de toename van het abstractieniveau, aangezien er een grens is aan de complexiteit van structuren waarop het menselijk brein kan opereren.

    Maar het gebruik van webframeworks zoals Yii of Kohana, bestaande uit enkele honderden bestanden, is bij het ontwikkelen van eenvoudige webapplicaties (bijvoorbeeld visitekaartjessites) niet altijd aan te raden. Nu zijn we in staat om een ​​prachtig MVC-model te maken, zodat we geen PHP-, Html-, CSS- en JavaScript-code in één bestand hoeven te mengen.

    Dit artikel is meer een startpunt om CMF te leren dan een voorbeeld van iets dat echt correct is en dat u als basis voor uw webtoepassing kunt nemen. Misschien heeft ze je zelfs geïnspireerd en denk je er al aan om je eigen microframework of CMS te schrijven op basis van MVC. Maar denk nog eens goed na voordat u de volgende fiets opnieuw uitvindt met "blackjack en hoeren", misschien kunnen uw inspanningen verstandiger worden gericht op de ontwikkeling en het helpen van de gemeenschap van een bestaand project?!

    P.S.: Het artikel is herschreven, rekening houdend met enkele opmerkingen die in de opmerkingen zijn achtergelaten. De kritiek was zeer nuttig. Aan de reacties te zien: reacties, persoonlijke verzoeken en het aantal gebruikers dat de post bij hun favorieten heeft gezet, bleek het idee om deze post te schrijven nog niet zo slecht. Helaas is het door tijdgebrek niet mogelijk om met alle wensen rekening te houden en steeds gedetailleerder te schrijven ... maar misschien wordt het gedaan door die mysterieuze figuren die minus de originele versie zijn. Veel succes met je projecten!

    5. Een selectie van nuttige links over het onderwerp

    Het artikel raakt vaak het onderwerp van webframeworks - dit is een zeer breed onderwerp, omdat zelfs microframeworks uit vele componenten bestaan ​​die slim met elkaar zijn verbonden en er zou meer dan één artikel nodig zijn om over deze componenten te praten. Toch besloot ik hier een kleine selectie van links te geven (die ik gevolgd heb bij het schrijven van dit artikel), die op de een of andere manier betrekking hebben op het onderwerp kaders.

    Bewerk het bestand urls.py bijlagen rekening:

    van django.conf.urls importeer url van. import views urlpatterns = [# vorige login view # url (r "^ login / $", views.user_login, naam = "login"),# login / logout urls url (r "^ login / $", "django.contrib.auth.views.login", naam = "login"), url (r "^ uitloggen / $", "django.contrib.auth.views.logout", name = "uitloggen"), url (r "^ uitloggen-dan-login / $", "django.contrib.auth.views.logout_then_login", naam = "logout_then_login"),]

    We hebben commentaar gegeven op het url-patroon voor de weergave gebruiker login eerder gemaakt om de weergave te gebruiken Log in Django.

    Maak een nieuwe map in de map met app-sjablonen rekening en noem het registratie. Maak een nieuw bestand in een nieuwe map, noem maar op inloggen.html

    (% breidt "base.html"% uit) (% bloktitel%) Inloggen (% endblock%) (% blokinhoud%)

    Log in

    (% als formulier.fouten%)

    Uw gebruikersnaam en wachtwoord komen niet overeen. Probeer het opnieuw.

    (% anders%)

    Gebruik het volgende formulier om in te loggen:

    (% stop als%) (% eindblok%)

    Deze inlogsjabloon lijkt erg op degene die we eerder hebben gemaakt. Django gebruikt Authenticatieformulier gelegen in django.contrib.auth.forms... Dit formulier probeert de gebruiker te authenticeren en geeft een validatiefout als de gebruikersnaam niet correct was. In dit geval kunnen we naar fouten zoeken met het commando (% if form.errors%). Houd er rekening mee dat we een verborgen element hebben toegevoegd om een ​​waarde naar een variabele met de naam te sturen De volgende.

    Parameter De volgende moet een url zijn. Als deze parameter is opgegeven, wordt deze, nadat de gebruiker zich heeft aangemeld, omgeleid naar de opgegeven URL.

    Maak nu een sjabloon ingelogd_uit.html in de sjabloonmap registratie en plak de volgende code erin:

    (% breidt "base.html"% uit) (% bloktitel%) Uitgelogd (% endblock%) (% blokinhoud%)

    Uitgelogd

    U bent succesvol uitgelogd. U kunt weer inloggen.

    (% eindblok%)

    Dit is het sjabloon dat wordt weergegeven nadat de gebruiker is ingelogd.

    Na het toevoegen van de URL-patronen en sjablonen voor de invoer- en uitvoerweergaven, is de site klaar om in te loggen met de authenticatieweergaven van Django.

    Houd er rekening mee dat het uitzicht logout_then_login inbegrepen in onze urlconf, heeft geen sjabloon nodig omdat het een omleiding doet naar inlogweergave.

    Laten we nu een nieuwe weergave maken om het dashboard voor de gebruiker weer te geven, zodat we weten wanneer de gebruiker is ingelogd op zijn account. Open het bestand views.py bijlagen rekening en voeg er de volgende code aan toe:

    van django.contrib.auth.decorators importeer login_required @login_required def dashboard (verzoek): render render (verzoek, "account / dashboard.html", ("sectie": "dashboard"))

    We voegen een decorateur toe aan ons uitzicht Inloggen vereist authenticatie raamwerk. Decorateur Inloggen vereist controleert of de huidige gebruiker is geverifieerd. Als de gebruiker is geauthenticeerd, wordt de indiening uitgevoerd; Als de gebruiker niet is geverifieerd, wordt hij doorgestuurd naar de inlogpagina.

    We hebben ook de variabele gedefinieerd sectie... We gaan deze variabele gebruiken om bij te houden welk gedeelte van de site de gebruiker bekijkt.

    Nu moet u een sjabloon maken voor uw dashboardweergave. Maak een nieuw bestand in sjablonen / account sjablonen / account / en noem het dashboard.html :

    (% breidt "base.html"% uit) (% bloktitel%) Dashboard (% endblock%) (% blokinhoud%)

    Dashboard

    Welkom op je dashboard.

    (% eindblok%)

    Voeg vervolgens het volgende url-patroon toe om het bestand te wijzigen: urls.py bijlagen rekening:

    Urlpatronen = [# ... url (r "^ $", views.dashboard, naam = "dashboard"),]

    Bewerk nu het bestand instellingen.py:

    van django.core.urlresolvers import reverse_lazy LOGIN_REDIRECT_URL = reverse_lazy ("dashboard") LOGIN_URL = reverse_lazy ("login") LOGOUT_URL = reverse_lazy ("uitloggen")
    • LOGIN_REDIRECT_URL: Vertelt naar welke URL de gebruiker moet worden omgeleid na het inloggen.
    • LOGIN_URL: URL om de gebruiker om te leiden om in te loggen (bijvoorbeeld met behulp van een decorateur) Inloggen vereist)
    • LOGOUT_URL: URL om gebruiker om te leiden naar uitloggen

    We gaan nu inlog- en uitloglinks toevoegen aan onze basissjabloon.

    Om dit te doen, moet u bepalen of de huidige gebruiker is ingelogd of niet om de link weer te geven die overeenkomt met de huidige status van de gebruiker. De huidige gebruiker is ingesteld in HttpRequest een intermediaire klasse object authenticatie. Het is toegankelijk met verzoek.gebruiker... Het gebruikersobject wordt in de aanvraag gevonden, zelfs als de gebruiker niet is geverifieerd. Niet-geverifieerde gebruiker, opgegeven in de aanvraag als een instantie Anonieme gebruiker... De beste manier om de authenticatiestatus van de huidige gebruiker te controleren, is door uit te dagen request.user.is_authenticated ()

    Bewerken in sjabloon base.html

    met ID-header:

    Zoals u kunt zien, wordt het sitemenu alleen weergegeven voor geverifieerde gebruikers. We controleren ook de huidige sectie om het geselecteerde klasseattribuut toe te voegen aan het corresponderende element

  • om de huidige sectie in het menu te markeren met behulp van CSS. Het toont ook de gebruikersnaam en inloglink als de gebruiker is geverifieerd, of een inloglink.

    Open http://127.0.0.1:8000/account/login/ in uw browser. U zou een invoerpagina moeten zien. Voer een geldige gebruikersnaam en wachtwoord in. U ziet het volgende:

    U kunt zien dat het gedeelte Mijn dashboard is gemarkeerd met CSS omdat het een klasse heeft geselecteerd... Aangezien de gebruiker is geverifieerd, wordt de gebruikersnaam weergegeven aan de rechterkant van de kop. Klik De link Uitloggen... U krijgt de volgende pagina te zien:

    Op deze pagina kunt u zien dat de gebruiker is uitgelogd en daarom wordt het websitemenu niet meer weergegeven. De link aan de rechterkant van de koptekst toont nu Log in.

    Als u een uitlogpagina van de Django-beheerderssite ziet en niet uw eigen uitlogpagina, controleer dan uw INSTALLED_APPS-instellingen en zorg ervoor django.contrib.admin is na rekening... Beide sjablonen bevinden zich in hetzelfde relatieve pad en de Django-sjabloonlader zal de eerste gebruiken die wordt gevonden.

  • 
    JaarProjectBeschrijving
    ". $ rij [" Jaar "]."". $ rij [" Site "]."". $ rij [" Beschrijving "]."