A while back I was tasked to create a web application based on the popular MVC architecture pattern. Part of the task was not to use a framework, but to write the application all by myself. So I looked at my notes from school that taught me this basic model of an MVC architecture:
The main idea behind the pattern is its so called separation of concerns, meaning that each component of the architecture should be decoupled from the others. The model holds the data and the operations that are to be performed on it. The view is responsible for making that data graphically presentable. Meanwhile the controller is meant to handle user input and call on the model to perform the desired operations on the data. The architecture was first introduced many years before the invention of the HTTP protocol. In case of a web application with the program sitting on a server adjustments have to be made. The application is required to read the request and use that to decide which action to perform. In theory you could have all of that in the program's entry point, but it's good practice to distribute those actions across classes and methods. MVC has its origin in object oriented programming so MCV applications are meant to be architected in modules. Thankfully modern frameworks already solved this problem. Polymorphy can be used to map a controller and action directly to a request. I've worked with a couple of frameworks in the past and this design always appeared clever to me, so there was little need for me to think of a solution. Common practice is to simply let a routing class inject the controller corresponding to the current request into the main program. Since I also had to make UML diagrams at the time I wondered what this relationship would look like in UML:
The above in english: The Main class holds an object of the Router class. The Router creates an object of a controller class (that it itself determines) which is then held and used by the Main class. The Controller object is derived from the BaseController class which defined common functionality for all controllers. Fancy enough, but this is still missing the model and view components. I've worked with frameworks that reduce the model to classes that map to tables in a database and expect the controller to perform opeations on them. This works well enough but instigates the tendency to bleed business logic into the controllers. The solution here is to use additional Service classes that perform all of these operations, and are considered to be part of the model along with the ORM classes and storate abstractions (database). For simple applications those could just be singletons. Demanding applications however should minimize coupling by giving each controller access to the services it requires only. Services should also keep aside data for use by the view layer. Web application MVC frameworks often simplify the view layer, instigating developers to put presentation logic into controllers and template. But ideally one would want the controller to use an object of a View class that prepares data for presentation and makes use of templates.
tl;dr there is more to the MVC pattern than many online tutorials and web frameworks try to tell us. I've been used to improperly using the architecture concept so making sense of the pattern, and how it's actually meant to be used, wasn't exactly straightforward for me. Working this stuff out taught me a lot about the practices hailed by object-oriented programming and to make sense of them.