Crazy logout html view. Model-Update-View Pattern and Dependent Types


Edit the file urls.py applications account:

from django.conf.urls import url from . import views urlpatterns = [ # previous login view # url(r"^login/$", views.user_login, name="login"),# login / logout urls url(r"^login/$" , "django.contrib.auth.views.login", name="login" ), url(r"^logout/$" , "django.contrib.auth.views.logout", name="logout" ), url(r"^logout-then-login/$" , "django.contrib.auth.views.logout_then_login", name="logout_then_login" ), ]

We have commented out the URL template for the view user_login, created earlier to use the view login Django.

Create a new directory in the application templates directory account and name it registration. Create a new file in a new directory, name it login.html

(% extends "base.html" %) (% block title %)Log-in(% endblock %) (% block content %)

Log-in

(% if form.errors %)

Your username and password didn't match. Please try again.

(%else%)

Please, use the following form to log-in:

(% endif %) (%endblock%)

This login template is very similar to the one created earlier. Django uses AuthenticationForm, located in django.contrib.auth.forms. This form attempts to authenticate the user and generates a validation error if the username was incorrect. In this case, we can look for errors using the command (% if form.errors %) . Please note that we have added a hidden element to send the value of a variable named next.

Parameter next must be a URL. If this parameter is specified, then after the user logs in, they are redirected to the specified URL.

Now create a template logged_out.html inside the template directory registration and paste the following code into it:

(% extends "base.html" %) (% block title %)Logged out(% endblock %) (% block content %)

Logged out

You have been successfully logged out. You can log-in again.

(%endblock%)

This is the template that will be displayed after the user logs in.

After adding the URL templates and templates for the input and output views, the site is ready for logins using Django's authentication views.

Please note that the presentation logout_then_login included in our urlconf, doesn't need a template since it redirects to log in view.

Now let's create a new view to display a dashboard for the user so that we know when the user logs into his account. Open the file views.py applications account and add the following code to it:

from django.contrib.auth.decorators import login_required @login_required def dashboard(request) : return render(request, "account/dashboard.html" , ("section" : "dashboard" ))

We add a decorator to our view login_required authentication framework. Decorator login_required checks whether the current user is authenticated. If the user is authenticated, the submission will be executed; If the user is not authenticated, they will be redirected to the login page.

We also defined a variable section. We're going to use this variable to track which section of the site the user is watching.

Now you need to create a template for the dashboard view. Create a new file inside templates/account templates/account/ and name it dashboard.html :

(% extends "base.html" %) (% block title %)Dashboard(% endblock %) (% block content %)

Dashboard

Welcome to your dashboard.

(%endblock%)

Then add the following URL pattern for this change file urls.py applications account:

Urlpatterns = [ # ... url(r"^$" , views.dashboard, name="dashboard" ), ]

Now edit the file settings.py:

from django.core.urlresolvers import reverse_lazy LOGIN_REDIRECT_URL = reverse_lazy("dashboard" ) LOGIN_URL = reverse_lazy("login" ) LOGOUT_URL = reverse_lazy("logout" )
  • LOGIN_REDIRECT_URL: Tells which URL to redirect the user to after logging in.
  • LOGIN_URL: URL to redirect the user to login (for example, using a decorator login_required)
  • LOGOUT_URL: URL to redirect the user to exit

Now we're going to add login and logout links to our basic template.

To do this, it is necessary to determine whether the current user is logged in or not in order to display a link corresponding to the current user state. The current user is specified in HttpRequest object of the authentication intermediate class. It can be accessed using request.user. The request will find a user object, even if the user is not authenticated. Unauthenticated user, specified in the request as an instance AnonymousUser. The best way to check the current user's authentication status is to call request.user.is_authenticated()

Edit in the template base.html

with ID header:

As you can see, the site menu is displayed only for authenticated users. We also check the current section to add the selected class attribute to the corresponding element

  • to highlight the current section in the menu using CSS. It also displays the username and a link to log out if the user is authenticated, or a link to log in.

    Open http://127.0.0.1:8000/account/login/ in your browser. You should see a login page. Enter a valid login and password. You will see the following:

    You can see that the My dashboard section is highlighted with CSS as it has a class selected. Since the user has been authenticated, the username is displayed on the right side of the header. Click the link Logout. You will see the following page:

    On this page you can see that the user is logged out and therefore the website menu is no longer displayed. The link on the right side of the header now shows Log-in.

    If you see the log out page from the Django admin site rather than your own logout page, check your INSTALLED_APPS settings and make sure that django.contrib.admin is after account. Both templates are in the same relative path, and the Django template loader will use the first one it finds.

    This example demonstrates how to automatically logout with default Spring security configuration.

    To logout, we just need to access URL "/logout" with POST request.

    In the POST /logout form, we also need to include the CSRF token, which is a protection against CSRF attack .

    Let's see the example how to do that.

    Java Config class

    @Configuration @EnableWebSecurity @EnableWebMvc @ComponentScan public class AppConfig extends WebSecurityConfigurerAdapter ( protected void configure(HttpSecurity http) throws Exception ( http.authorizeRequests() .anyRequest().authenticated() .and() .formLogin(); ) @Override public void configure(AuthenticationManagerBuilder builder) throws Exception ( builder.inMemoryAuthentication() .withUser("joe") .password("123") .roles("ADMIN"); ) @Bean public ViewResolver viewResolver() ( InternalResourceViewResolver viewResolver = new InternalResourceViewResolver (); viewResolver.setPrefix("/WEB-INF/views/"); viewResolver.setSuffix(".jsp"); return viewResolver; ) )

    Note that, in above configuration, we are also overriding configure(HttpSecurity http) to omit the default Basic Authentication (see the original method in WebSecurityConfigurerAdapter source code) and use form based Authentication. We are doing so because browsers cache the Basic Authentication information aggressively (after the first successful login) and there is no way to logout the user in the current session. In most of the examples, we will not be using Basic Authentication mechanism.

    A controller

    @Controller public class ExampleController ( @RequestMapping("/") public String handleRequest2(ModelMap map) ( map.addAttribute("time", LocalDateTime.now().toString()); return "my-page"; ) )

    The JSP page

    src/main/webapp/WEB-INF/views/my-page.jsp

    Spring Security Example

    Time: $(time)

    To try examples, run embedded tomcat (configured in pom.xml of example project below):

    Mvn tomcat7:run-war

    Output

    Initial access to URI "/" will redirect to "/login":

    After submitting user name and password as we setup in our AppConfig class:

    Clicking on "Logout" button:


    Example Project

    Dependencies and Technologies Used:

    • spring-security-web 4.2.3.RELEASE: spring-security-web.
    • spring-security-config 4.2.3.RELEASE: spring-security-config.
    • spring-webmvc 4.3.9.RELEASE: Spring Web MVC.
    • javax.servlet-api 3.1.0 Java Servlet API
    • JDK 1.8
    • Maven 3.3.9

    Mainly for developing user interfaces. To use it, you need to create a Model type that represents the complete state of the program, a Message type that describes the events of the external environment to which the program must respond by changing its state, an updater function that creates a new state of the program from the old state and message, and a view function that Based on the state of the program, it calculates the required impacts on the external environment, which generate events of the Message type. The pattern is very convenient, but it has a small drawback - it does not allow you to describe which events make sense for specific program states.

    A similar problem arises (and is solved) when using the State OO pattern.

    The Elm language is simple, but very strict - it checks that the updater function somehow handles all possible combinations of model-state and message-events. Therefore, you have to write extra, albeit trivial, code that usually leaves the model unchanged. I want to demonstrate how this can be avoided in more complex languages ​​- Idris, Scala, C++ and Haskell.

    All code shown here is available on GitHub for experimentation. Let's look at the most interesting places.


    The msg function is unusual - it returns a type, not a value. During execution, nothing is known about value types - the compiler erases all unnecessary information. That is, such a function can only be called at the compilation stage.

    MUV is a constructor. It accepts parameters: model - the initial state of the program, updater - a function for updating the state upon an external event, and view - a function for creating an external view. Note that the type of the updater and view functions depends on the value of the model (using the msg function from the type parameters).

    Now let's see how to launch this application

    MuvRun: (Application modelType msgType IO) -> IO a muvRun (MUV model updater view) = do msg<- view model muvRun (MUV (updater model msg) updater view)
    We chose an input/output operation as an external representation (view) (in Idris, as in Haskell, input/output operations are first class values; in order for them to be executed, additional actions must be taken, usually returning such an operation from the main function).

    Briefly about IO

    When performing an operation of type (IO a), some impact on the outside world occurs, possibly empty, and a value of type a is returned to the program, but the functions of the standard library are designed in such a way that it can only be processed by generating a new value of type IO b. This way pure functions are separated from functions with side effects. This is unusual for many programmers, but it helps to write more reliable code.


    Since the muvRun function generates I/O, it should return IO, but since it will never complete, the operation type can be anything - IO a.

    Now let's describe the types of entities we are going to work with

    Data Model = Logouted | Logined String data MsgOuted = Login String data MsgIned = Logout | Greet total msgType: Model -> Type msgType Logouted = MsgOuted msgType (Logined _) = MsgIned
    Here we describe a model type that reflects the presence of two interface states - the user is not logged in, and the user with a name of type String is logged in.

    Next we describe two different types of messages relevant for different variants of the model - if we are logged out, then we can only log in under a certain name, and if we are already logged in, we can either log out or say hello. Idris is a strongly typed language that will not allow for the possibility of mixing up different types.

    And finally, a function that sets the correspondence of the model value to the message type.

    The function is declared total - that is, it should not crash or freeze, the compiler will try to monitor this. msgType is called at compilation time, and its totality means that the compilation will not freeze due to our error, although it cannot guarantee that executing this function will exhaust system resources.
    It is also guaranteed that it will not execute "rm -rf /" because there is no IO in its signature.

    Let's describe updater:

    Total updater: (m:Model) -> (msgType m) -> Model updater Logouted (Login name) = Logined name updater (Logined name) Logout = Logouted updater (Logined name) Greet = Logined name
    I think the logic of this function is clear. I would like to note once again totality - it means that the Idris compiler will check that we have considered all the alternatives allowed by the type system. Elm also performs this check, but it cannot know that we cannot log out if we are not logged in yet, and will require explicit processing of the condition

    Updater Logouted Logout = ???
    Idris will find type mismatches in an unnecessary check.

    Now let's move on to the view - as usual in UI, this will be the most difficult part of the code.

    Total loginPage: IO MsgOuted loginPage = do putStr "Login: " map Login getLine total genMsg: String -> MsgIned genMsg "" = Logout genMsg _ = Greet total workPage: String -> IO MsgIned workPage name = do putStr ("Hello, " ++ name ++ "\n") putStr "Input empty string for logout or nonempty for greeting\n" map genMsg getLine total view: (m: Model) -> IO (msgType m) view Logouted = loginPage view (Logined name ) = workPage name
    view must create an I/O operation that returns messages, the type of which again depends on the value of the model. We have two options: loginPage, which prints a "Login:" message, reads a string from the keyboard and wraps it in a Login message, and workPage with a username parameter, which prints a greeting and returns different messages (but the same type - MsgIned) depending on whether the user enters an empty or non-empty string. view returns one of these operations depending on the value of the model, and the compiler checks their type, even though it is different.

    Now we can create and run our application

    App: Application Model Main.msgType IO app = MUV Logouted updater view main: IO () main = muvRun app
    A subtle point should be noted here - the muvRun function returns IO a, where a was not specified and the value main is of type IO(), Where () is the name of a type usually called Unit, which has a single value, also written as an empty tuple () . But the compiler can handle this easily. substituting a() instead.

    Scala and path-dependent types

    Scala does not have full support for dependent types, but there are types that depend on the instance of the object through which it is referenced (path dependent types). In the theory of dependent types, they can be described as a variant of the sigma type. Path-dependent types make it possible to prohibit the addition of vectors from different vector spaces, or to describe who can kiss whom. But we will use them for simpler tasks.

    Sealed abstract class MsgLogouted case class Login(name: String) extends MsgLogouted sealed abstract class MsgLogined case class Logout() extends MsgLogined case class Greet() extends MsgLogined abstract class View ( def run() : Msg ) sealed abstract class Model ( type Message def view() : View ) case class Logouted() extends Model ( type Message = MsgLogined override def view() : View .... ) case class Logined(name: String) extends Model ( type Message = MsgLogined override def view( ): View .... )
    Algebraic types in Scala are modeled through inheritance. The type corresponds to some sealed abstract class, and each constructor inherited from it case class. We will try to use them exactly as algebraic types, describing all variables as belonging to the parent sealed abstract class.

    The MsgLogined and MsgLogouted classes within our program do not have a common ancestor. The view function had to be spread across different classes of the model in order to have access to a specific type of message. This has its advantages, which OO supporters will appreciate - the code is grouped in accordance with business logic, everything related to one use case is nearby. But I would rather separate view into a separate function, the development of which could be transferred to another person.

    Now let's implement updater

    Object Updater ( def update(model: Model)(msg: model.Message) : Model = ( model match ( case Logouted() => msg match ( case Login(name) => Logined(name) ) case Logined(name) => msg match ( case Logout() => Logouted() case Greet() => model ) ) ) )
    Here we use path-dependent types to describe the type of the second argument from the value of the first. In order for Scala to accept such dependencies, functions have to be described in curried form, that is, as a function from the first argument, which returns a function from the second argument. Unfortunately, Scala doesn't do many type checks at this point for which the compiler has enough information.

    Now let's give a complete implementation of the model and view

    Case class Logouted() extends Model ( type Message = MsgLogouted override def view() : View = new View ( override def run() = ( println("Enter name ") val name = scala.io.StdIn.readLine() Login (name) ) ) case class Logined(name: String) extends Model ( type Message = MsgLogined override def view() : View = new View ( override def run() = ( println(s"Hello, $name") println ("Empty string for logout, nonempy for greeting.") scala.io.StdIn.readLine() match ( case "" => Logout() case _ => Greet() ) ) ) abstract class View ( def run( ) : Msg ) object Viewer ( def view(model: Model): View = ( model.view() ) )
    The return type of a view function depends on the instance of its argument. But for implementation it turns to the model.

    The application created in this way is launched like this:

    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) = ( process(Logouted()) ) )
    The runtime system code thus does not know anything about the internal structure of models and message types, but the compiler can check that the message matches the current model.

    Here we did not need all the capabilities provided by path-dependent types. Interesting properties will appear if we work in parallel with several instances of Model-Updater-View systems, for example, when simulating a multi-agent world (the view would then represent the agent’s influence on the world and receiving feedback). In this case, the compiler checked that the message was processed by exactly the agent for which it was intended, despite the fact that all agents have the same type.

    C++

    C++ is still sensitive to the order of definitions, even if they are all made in the same file. This creates some inconvenience. I will present the code in a sequence convenient for demonstrating ideas. A compilable version can be found on GitHub.

    Algebraic types can be implemented in the same way as in Scala - an abstract class corresponds to a type, and concrete descendants correspond to constructors (let's call them “constructor classes”, so as not to be confused with ordinary C++ constructors) of the algebraic type.

    C++ has support for path-dependent types, but the compiler cannot use the type in the abstract without knowing the real type it is associated with. Therefore, it is impossible to implement Model-Updater-View with their help.

    But C++ has a powerful templating system. The dependence of the type on the model value can be hidden in a template parameter of a specialized version of the executive system.

    Struct Processor ( virtual const Processor *next() const = 0; ); template struct ProcessorImpl: public Processor ( const CurModel * model; ProcessorImpl (const CurModel* m) : model(m) ( ); const Processor *next() const ( const View * view = model->view(); const typename CurModel::Message * msg = view->run(); delete view; const Model * newModel = msg->process(model); delete msg; return newModel->processor(); ) );
    We describe an abstract execution system, with a single method to do whatever is required and return a new execution system suitable for the next iteration. The specific version has a template parameter and will be specialized for each “constructor class” of the model. It is important here that all properties of the CurModel type will be checked during the specialization of the template with a specific type parameter, and at the time of compilation of the template itself, they are not required to be described (although it is possible using concepts or other ways of implementing type classes). Scala also has a fairly powerful system of parameterized types, but it checks the properties of parameter types during compilation of the parameterized type. There, the implementation of such a pattern is difficult, but possible, thanks to the support of type classes.

    Let's describe the model.

    Struct Model ( virtual ~Model() (); virtual 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: public View (...); const View * view() const ( return new LoginedView(name); ); const Processor *processor() const ( return new ProcessorImpl (this); ); ); struct Logouted: public Model ( struct Message ( const virtual Model * process(const Logouted * m) const = 0; virtual ~Message() (); struct Login: public Message ( const std::string name; Login(std ::string lname) : name(lname) ( ); const Model * process(const Logouted * m) const; ); struct LogoutedView: public View (...); const View * view() const ( return new LogoutedView(); ); const Processor *processor() const ( return new ProcessorImpl (this); ); );
    “Constructor classes” of the model “carry everything with them” - that is, they contain message and view classes specialized for them, and are also able to create an executive system for themselves. Native View types have a common ancestor for all models, which can be useful when developing more complex execution systems. It is important that message types are completely isolated and do not have a common ancestor.

    The updater implementation is separate from the model because it requires that the model type be already fully described.

    Const Model * Logouted::Login::process(const Logouted * m) const ( delete m; return new Logined(name); ); const Model * Logined::Logout::process(const Logined * m) const ( delete m; return new Logouted(); ); const Model * Logined::Greet::process(const Logined * m) const ( return m; );
    Now let's put together everything that relates to the view, including the internal entities of the models

    Template struct View ( virtual const Message * run() const = 0; virtual ~View () (); ); struct Logined: public Model ( struct LoginedView: public View ( const std::string name; LoginedView(std::string lname) : name(lname) (); virtual const Message * run() const ( char buf; printf("Hello %s", name.c_str()) ; fgets(buf, 15, stdin); return (*buf == 0 || *buf == "\n" || *buf == "\r") ? static_cast (new Logout()) : static_cast (new Greet); ); ); const View * view() const ( return new LoginedView(name); ); ); struct Logouted: public Model ( struct LogoutedView: public View ( virtual const Message * run() const ( char buf; printf("Login: "); fgets(buf, 15, stdin); return new Login(buf); ); ); const View * view() const ( return new LogoutedView(); ); );
    And finally, let's write main

    Int main(int argc, char ** argv) ( const Processor * p = new ProcessorImpl (new Logouted()); while(true) ( ​​const Processor * pnew = p->next(); delete p; p = pnew; ) return 0; )

    And again Scala, this time with type classes

    In structure, this implementation almost completely replicates the C++ version.

    Similar part of the code

    abstract class View ( def run(): Message ) abstract class Processor ( def next(): Processor; ) sealed abstract class Model ( def processor(): Processor ) sealed abstract class LoginedMessage case class Logout() extends LoginedMessage case class Greet( ) extends LoginedMessage case class Logined(val name: String) extends Model ( override def processor(): Processor = new ProcessorImpl(this) ) sealed abstract class LogoutedMessage case class Login(name: String) extends LogoutedMessage case class Logouted() extends Model ( 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) = ( process(new ProcessorImpl(Logouted())) ) )


    But in the implementation of the runtime environment, subtleties arise.

    Class ProcessorImpl(model: M)(implicit updater: (M, Message) => Model, view: M => View) extends Processor ( def next(): Processor = ( val v = view(model) val msg = v. run() val newModel = updater(model,msg) newModel.processor() ) )
    Here we see new mysterious parameters (implicit updater: (M, Message) => Model, view: M => View). The implicit keyword means that when calling this function (more precisely, the class constructor), the compiler will look for objects of suitable types marked as implicit in the context and pass them as appropriate parameters. This is a fairly complex concept, one of whose applications is the implementation of type classes. Here they promise the compiler that for specific implementations of the model and message, all the necessary functions will be provided by us. Now let's fulfill this promise.

    Object updaters ( implicit def logoutedUpdater(model: Logouted, msg: LogoutedMessage): Model = ( (model, msg) match ( case (Logouted(), Login(name)) => Logined(name) ) ) implicit def viewLogouted(model : Logouted) = new View ( override def run() : LogoutedMessage = ( println("Enter name ") val name = scala.io.StdIn.readLine() Login(name) ) ) implicit def loginedUpdater(model: Logined, msg : LoginedMessage): Model = ( (model, msg) match ( case (Logined(name), Logout()) => Logouted() case (Logined(name), Greet()) => model ) ) implicit def viewLogined( model: Logined) = new View ( val name = model.name override def run() : LoginedMessage = ( println(s"Hello, $name") println("Empty string for logout, nonempy for greeting.") scala.io .StdIn.readLine() match ( case "" => Logout() case _ => Greet() ) ) ) import updaters._

    Haskell

    There are no dependent types in mainstream Haskell. It also lacks inheritance, which we significantly used when implementing the pattern in Scala and C++. But single-level inheritance (with elements of dependent types) can be modeled using more or less standard language extensions - TypeFamilies and ExistentialQuantification. For the common interface of child OOP classes, a type class is created, in which there is a dependent “family” type, the child classes themselves are represented by a separate type, and then wrapped in an “existential” type with a single constructor.

    Data Model = forall m. (Updatable m, Viewable m) => Model m class Updatable m where data Message m:: * update:: m -> (Message m) -> Model class (Updatable m) => Viewable m where view:: m -> (View (Message m)) data Logouted = Logouted data Logined = Logined String
    I tried to separate updater and view as far as possible, so I created two different type classes, but so far it hasn’t worked out well.

    The updater implementation is simple

    Instance Updatable Logouted where data Message Logouted = Login String update Logouted (Login name) = Model (Logined name) instance Updatable Logined where data Message Logined = Logout | Greeting update m Logout = Model Logouted update m Greeting = Model m
    I had to fix IO as View. Attempts to make it more abstract greatly complicated everything and increased the coupling of the code - the Model type must know which View we are going to use.

    Import System.IO type View a = IO a instance Viewable Logouted where view Logouted = do putStr "Login: " hFlush stdout fmap Login getLine instance Viewable Logined where view (Logined name) = do putStr $ "Hello " ++ name ++ " !\n" hFlush stdout l<- getLine pure $ if l == "" then Logout else Greeting
    Well, the executable environment differs little from the similar one in Idris

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

    Many people start writing a project to work with a single task, not implying that it can grow into a multi-user management system, for example, content or, God forbid, production. And everything seems great and cool, everything works, until you begin to understand that the code that is written consists entirely of crutches and hard code. The code is mixed with layout, queries and crutches, sometimes even unreadable. A pressing problem arises: when adding new features, you have to tinker with this code for a very long time, remembering “what was written there?” and curse yourself in the past.

    You may have even heard about design patterns and even leafed through these wonderful books:

    • E. Gamma, R. Helm, R. Johnson, J. Vlissides “Object-oriented design techniques. Design Patterns";
    • M. Fowler "Architecture of Enterprise Software Applications."
    And many, undaunted by the huge manuals and documentation, tried to study any of the modern frameworks and, faced with the complexity of understanding (due to the presence of many architectural concepts cleverly interconnected), put off the study and use of modern tools in a “shelter.”

    This article will be useful primarily for beginners. In any case, I hope that in a couple of hours you will be able to get an idea of ​​the implementation of the MVC pattern, which underlies all modern web frameworks, and also get “food” for further reflection on “how to do it.” At the end of the article there is a selection of useful links that will also help you understand what web frameworks consist of (besides MVC) and how they work.

    Seasoned PHP programmers are unlikely to find anything new for themselves in this article, but their comments and comments on the main text would be very helpful! Because Without theory, practice is impossible, and without practice, theory is useless, then first there will be a little theory, and then we will move on to practice. If you are already familiar with the MVC concept, you can skip the theory section and go straight to the practice.

    1. Theory

    The MVC pattern describes a simple way to structure an application that aims to separate business logic from the user interface. As a result, the application is easier to scale, test, maintain and, of course, implement.

    Let's look at the conceptual diagram of the MVC pattern (in my opinion, this is the most successful diagram I have seen):

    In MVC architecture, the model provides the data and business logic rules, the view is responsible for the user interface, and the controller provides interaction between the model and the view.

    A typical flow of an MVC application can be described as follows:

    1. When a user visits a web resource, the initialization script creates an instance of the application and launches it for execution.
      This displays a view of, say, the main page of the site.
    2. The application receives a request from the user and determines the requested controller and action. In the case of the main page, the default action is performed ( index).
    3. The application instantiates the controller and runs the action method,
      which, for example, contains model calls that read information from the database.
    4. After this, the action creates a view with the data obtained from the model and displays the result to the user.
    Model- contains the business logic of the application and includes methods for sampling (these can be ORM methods), processing (for example, validation rules) and providing specific data, which often makes it very thick, which is quite normal.
    The model should not directly interact with the user. All variables related to the user request must be processed in the controller.
    The model should not generate HTML or other display code that can change depending on the user's needs. Such code should be processed in views.
    The same model, for example: the user authentication model can be used in both the user and administrative parts of the application. In this case, you can move the general code into a separate class and inherit from it, defining sub-application-specific methods in its descendants.

    View- used to set the external display of data received from the controller and model.
    Views contain HTML markup and small inserts of PHP code to traverse, format, and display data.
    Should not directly access the database. This is what models should do.
    Should not work with data obtained from a user request. This task must be performed by the controller.
    Can directly access properties and methods of a controller or models to obtain output-ready data.
    Views are usually divided into a common template, containing markup common to all pages (for example, a header and footer) and parts of the template that are used to display data output from the model or display data entry forms.

    Controller- the glue that connects models, views, and other components into a working application. The controller is responsible for processing user requests. The controller should not contain SQL queries. It is better to keep them in models. The controller should not contain HTML or other markup. It’s worth bringing it into view.
    In a well-designed MVC application, controllers are usually very thin and contain only a few dozen lines of code. The same cannot be said about Stupid Fat Controllers (SFC) in CMS Joomla. The controller logic is quite typical and most of it is transferred to base classes.
    Models, on the contrary, are very thick and contain most of the code related to data processing, because the data structure and business logic contained within it are usually quite specific to a particular application.

    1.1. Front Controller and Page Controller

    In most cases, user interaction with a web application occurs through clicking on links. Look now at the address bar of your browser - you received this text from this link. Other links, such as those on the right side of this page, will provide you with different content. Thus, the link represents a specific command to the web application.

    I hope you have already noticed that different sites can have completely different formats for constructing the address bar. Each format can display the architecture of a web application. Although this is not always the case, in most cases it is a clear fact.

    Let's consider two options for the address bar, which display some text and a user profile.

    First option:

    1. www.example.com/article.php?id=3
    2. www.example.com/user.php?id=4
    Here, each script is responsible for executing a specific command.

    Second option:

    1. www.example.com/index.php?article=3
    2. www.example.com/index.php?user=4
    And here all calls occur in one scenario index.php.

    You can see the multi-touchpoint approach on the phpBB forums. Browsing the forum occurs through a script viewforum.php, view topic via viewtopic.php etc. The second approach, with access through a single physical script file, can be seen in my favorite CMS MODX, where all accesses go through index.php.

    These two approaches are completely different. The first is typical for the Page Controller pattern, and the second approach is implemented by the Front Controller pattern. The page controller is good for sites with fairly simple logic. In turn, the request controller consolidates all request processing activities in one place, which gives it additional capabilities that can allow you to implement more complex tasks than are usually solved by the page controller. I will not go into details of the implementation of the page controller, but will only say that in the practical part, it will be the request controller (something similar) that will be developed.

    1.2. URL Routing

    URL routing allows you to configure your application to accept requests from URLs that do not correspond to actual application files, and to use CNCs that are semantically meaningful to users and preferred for search engine optimization.

    For example, for a regular page displaying a contact form, the URL might look like this:
    http://www.example.com/contacts.php?action=feedback

    Approximate processing code in this case:
    switch ($_GET ["action" ]) ( case "about" : require_once ("about.php" ); // "About Us" page break ; case "contacts" : require_once ( "contacts.php" ); // page "Contacts" break ; case "feedback" : require_once("feedback.php" ); // "Feedback" page break ; default : require_once ( "page404.php" ); // page "404" break ; )
    I think almost everyone has done this before.

    Using a URL routing engine, you can configure your application to accept requests like this to display the same information:
    http://www.example.com/contacts/feedback

    Here contacts represents the controller, and feedback is the contacts controller method that displays the feedback form, etc. We will return to this issue in the practical part.

    It's also worth knowing that many web frameworks' routers allow you to create custom URL routes (specify what each part of the URL means) and rules for processing them.
    Now we have sufficient theoretical knowledge to move on to practice.

    2. Practice

    First, let's create the following file and folder structure:

    Looking ahead, I will say that the core classes Model, View and Controller will be stored in the core folder.
    Their children will be stored in the controllers, models and views directories. File index.php This is the entry point into the application. File bootstrap.php initiates the download of the application, connecting all the necessary modules, etc.

    We will go sequentially; Let's open the index.php file and fill it with the following code:
    ini_set("display_errors" , 1 ); require_once "application/bootstrap.php" ;
    There shouldn't be any questions here.

    Next, let's immediately move on to the halyard bootstrap.php:
    require_once "core/model.php" ; require_once "core/view.php" ; require_once "core/controller.php" ; require_once "core/route.php" ; Route::start(); //start the router
    The first three lines will include currently non-existent kernel files. The last lines include the file with the router class and launch it for execution by calling the static start method.

    2.1. Implementing a URL Router

    For now, let's deviate from the implementation of the MVC pattern and move on to routing. The first step we need to do is write the following code in .htaccess:
    RewriteEngine On RewriteCond %(REQUEST_FILENAME) !-f RewriteCond %(REQUEST_FILENAME) !-d RewriteRule .* index.php [L]
    This code will redirect processing of all pages to index.php, which is what we need. Remember in the first part we talked about Front Controller?!

    We will place the routing in a separate file route.php to the core directory. In this file we will describe the Route class, which will run controller methods, which in turn will generate the page view.

    Contents of the route.php file

    class Route( static function start() ( // controller and default action$controller_name = "Main" ; $action_name = "index" ; $routes = explode("/" , $_SERVER ["REQUEST_URI" ]); // get the controller name if (!empty ($routes )) ( $controller_name = $routes ; ) // get the action name if (!empty ($routes )) ( $action_name = $routes ; ) // add prefixes$model_name = "Model_" .$controller_name ; $controller_name = "Controller_" .$controller_name ; $action_name = "action_" .$action_name ; // hook up the file with the model class (there may not be a model file)$model_file = strtolower($model_name )..php" ; $model_path = "application/models/" .$model_file ; if (file_exists($model_path )) ( include "application/models/" .$model_file ; ) // hook up the file with the controller class$controller_file = strtolower($controller_name )..php" ; $controller_path = "application/controllers/" .$controller_file ; if (file_exists($controller_path )) ( include "application/controllers/" .$controller_file ; ) else ( /* it would be correct to throw an exception here, but to simplify it, we’ll immediately redirect to the 404 page */ Route::ErrorPage404(); ) // create a controller$controller = new $controller_name ; $action = $action_name ; if (method_exists($controller , $action )) ( // call the controller action$controller ->$action(); ) else ( // it would also be wiser to throw an exception here Route::ErrorPage404(); ) ) function ErrorPage404() ($host = "http://" .$_SERVER ["HTTP_HOST" ]."/" ; header("HTTP/1.1 404 Not Found" ); header("Status: 404 Not Found" ); header("Location:" .$host ."404" ); ) )


    I note that the class implements very simplified logic (despite the voluminous code) and may even have security problems. This was done intentionally, because... writing a full-fledged routing class deserves at least a separate article. Let's look at the main points...

    The global array element $_SERVER["REQUEST_URI"] contains the full address to which the user contacted.
    For example: example.ru/contacts/feedback

    Using the function explode The address is divided into components. As a result, we get the name of the controller, for the example given, this is controller contacts and the name of the action, in our case - feedback.

    Next, the model file (the model may be missing) and the controller file, if any, are connected and finally, an instance of the controller is created and the action is called, again, if it was described in the controller class.

    Thus, when going to, for example, the address:
    example.com/portfolio
    or
    example.com/portfolio/index
    The router will perform the following actions:

    1. will include the model_portfolio.php file from the models folder, containing the Model_Portfolio class;
    2. will include the controller_portfolio.php file from the controllers folder, containing the Controller_Portfolio class;
    3. will create an instance of the Controller_Portfolio class and call the default action, action_index, described in it.
    If the user tries to access the address of a non-existent controller, for example:
    example.com/ufo
    then he will be redirected to the “404” page:
    example.com/404
    The same thing will happen if the user accesses an action that is not described in the controller.

    2.2. Let's return to the MVC implementation

    Let's go to the core folder and add three more files to the route.php file: model.php, view.php and controller.php

    Let me remind you that they will contain base classes, which we will now begin writing.

    File contents model.php
    class Model( public function get_data() ( } }
    The model class contains a single empty data fetch method, which will be overridden in descendant classes. When we create descendant classes everything will become clearer.

    File contents view.php
    classView( //public $template_view; // here you can specify the default general view. function generate ( $content_view , $template_view , $data = null) { /* if(is_array($data)) ( // convert array elements into variables extract($data); ) */ include "application/views/" .$template_view ; ) )
    It is not difficult to guess that the method generate intended to form a view. The following parameters are passed to it:

    1. $content_file - views displaying page content;
    2. $template_file — template common to all pages;
    3. $data is an array containing page content elements. Usually filled in in the model.
    The include function dynamically connects a general template (view) within which the view will be embedded
    to display the content of a specific page.

    In our case, the general template will contain header, menu, sidebar and footer, and the page content will be contained in a separate form. Again, this is done for simplicity.

    File contents controller.php
    class Controller( public $model ; public $view ; function __construct() ($this ->view = new View(); ) ) )
    Method action_index- this is the action called by default; we will override it when implementing descendant classes.

    2.3. Implementation of descendant classes Model and Controller, creation of View's

    Now the fun begins! Our business card website will consist of the following pages:
    1. home
    2. Services
    3. Portfolio
    4. Contacts
    5. And also - the “404” page
    Each page has its own controller from the controllers folder and a view from the views folder. Some pages may use a model or models from the models folder.

    In the previous figure, the file is highlighted separately template_view.php is a template containing markup common to all pages. In the simplest case it could look like this:
    <html lang="ru" > <head > <metacharset="utf-8"> <title > hometitle > head > <body > $content_view ; ?> body > html >
    To give the site a presentable look, we create a CSS template and integrate it into our site by changing the structure of the HTML markup and connecting CSS and JavaScript files:
    <link rel ="stylesheet" type ="text/css" href ="/css/style.css" /> <script src="/js/jquery-1.6.2.js" type="text/javascript" >script >

    At the end of the article, in the “Result” section, there is a link to a GitHub repository with a project in which steps have been taken to integrate a simple template.

    2.3.1. Creating the main page

    Let's start with the controller controller_main.php, here is his code:
    class Controller_Main extends Controller ( function action_index() ($this ->view->generate("main_view.php" , "template_view.php" ); ) )
    In method generate an instance of the View class, the names of the files of the general template and the view with the page content are passed.
    In addition to the index action, the controller can of course contain other actions.

    We reviewed the general view file earlier. Consider the content file main_view.php:
    <h1 > Welcome!h1 > <p> <img src ="/images/office-small.jpg" align ="left" > <a href = "/" > OLOLOSHA TEAMa >- a team of first-class specialists in the field of website development with many years of experience in collecting Mexican masks, bronze and stone statues from India and Ceylon, bas-reliefs and sculptures created by masters of Equatorial Africa five or six centuries ago...p>
    This contains simple markup without any PHP calls.
    To display the main page, you can use one of the following addresses:

    • methods of libraries that implement data abstraction. For example, methods of the PEAR MDB2 library;
    • ORM methods;
    • methods for working with NoSQL;
    • and etc.
    • For simplicity, we will not use SQL queries or ORM statements here. Instead, we will emulate real data and immediately return an array of results.
      Model file model_portfolio.php put it in the models folder. Here are its contents:
      class Model_Portfolio extends Model ( public function get_data() ( return array (array ("Year" => "2012" , "Site" => "http://DunkelBeer.ru" , "Description" => "Promotional site for dark beer Dunkel from the German manufacturer Löwenbraü, produced in Russia by the brewing company SUN InBev."), array ("Year" => "2012" , "Site" => "http://ZopoMobile.ru" , "Description" => "Russian-language catalog of Chinese phones from Zopo based on Android OS and accessories for them."), // todo ); ) )

      The model controller class is contained in the file controller_portfolio.php, here is his code:
      class Controller_Portfolio extends Controller ( function __construct() ($this ->model = new Model_Portfolio(); $this ->view = new View(); ) function action_index() ($data = $this ->model->get_data(); $this ->view->generate("portfolio_view.php" , "template_view.php" , $data ); ) )
      To a variable data the array returned by the method is written get_data which we looked at earlier.
      This variable is then passed as a method parameter generate, which also contains: the name of the file with the general template and the name of the file containing the view with the page content.

      The view containing the page content is in the file portfolio_view.php.

      Portfolio

      All projects in the following table are fictitious, so do not even try to follow the links provided. " ; }


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

      But in this version I sketched out the following classes (and their corresponding types):

      • Controller_Login in which a view is generated with a form for entering login and password, after filling which the authentication procedure is performed and, if successful, the user is redirected to the admin panel.
      • Contorller_Admin with an index action that checks whether the user was previously authorized on the site as an administrator (if so, the admin panel view is displayed) and a logout action for logging out.
      Authentication and authorization is a different topic, so it is not discussed here, but only the link given above is provided so that you have something to start from.

      4. Conclusion

      The MVC pattern is used as an architectural basis in many frameworks and CMS, which were created in order to be able to develop qualitatively more complex solutions in a shorter time. This was made possible by increasing the level of abstraction, since there is a limit to the complexity of the structures that the human brain can operate with.

      But, using web frameworks such as Yii or Kohana, consisting of several hundred files, when developing simple web applications (for example, business card sites) is not always advisable. Now we can create a beautiful MVC model so as not to mix Php, Html, CSS and JavaScript code in one file.

      This article is more of a starting point for learning CMF than an example of something truly correct that you can use as the basis for your web application. Perhaps it even inspired you and you are already thinking about writing your own microframework or CMS based on MVC. But, before reinventing the next wheel with “blackjack and whores,” think again: maybe it would be more reasonable to direct your efforts to the development and helping the community of an already existing project?!

      P.S.: The article was rewritten taking into account some comments left in the comments. The criticism turned out to be very useful. Judging by the response: comments, PMs and the number of users who added the post to favorites, the idea of ​​writing this post turned out to be not so bad. Unfortunately, it is not possible to take into account all the wishes and write more and in more detail due to lack of time... but perhaps those mysterious individuals who downvoted the original version will do this. Good luck with your projects!

      5. A selection of useful links on the subject

      The article very often touches on the topic of web frameworks - this is a very broad topic, because even microframeworks consist of many components cleverly interconnected and it would take more than one article to talk about these components. However, I decided to present here a small selection of links (which I followed while writing this article) that in one way or another relate to the topic of frameworks.




      

      2024 gtavrl.ru.
      YearProjectDescription
      " .$row ["Year" ]."" .$row ["Site" ]."" .$row ["Description" ]."