Of the patterns, I was satisfied with mvc, registry. For requests, I wrote a small abstraction layer, for routing - my own request parsing function.
The structure of the web application will be like this

application folder

The input file index.php includes bootstrap.php. That, in turn, connects the kernel, a config file, some libraries and starts the router.

Use Core\Route; require_once "lib/registry.php"; require_once "config.php"; require_once "lib/datebase.php"; require_once "core/model.php"; require_once "core/view.php"; require_once "core/controller.php"; require_once "core/route.php"; $router = new Route(); $router->start(); //start the router

The registry is simple:

Namespace Lib; class Lib_Registry ( static private $data = array(); static public function set($key, $value) ( ​​self::$data[$key] = $value; ) static public function get($key) ( return isset( self::$data[$key]) ? self::$data[$key] : null; ) static public function remove($key) ( if (isset(self::$data[$key])) ( unset (self::$data[$key]); ) ) )

Here are getters and setters for storing global values.

Use Lib\Lib_Registry; define("PATH_SITE", $_SERVER["DOCUMENT_ROOT"]); define("HOST", "localhost"); define("USER", "root"); define("PASSWORD", "mypass"); define("NAME_BD", "articles"); define("DS", DIRECTORY_SEPARATOR); $mysqli = new mysqli(HOST, USER, PASSWORD,NAME_BD)or die("Unable to establish a connection to the database."$mysqli->connect_errno()); Lib_Registry::set("mysqli",$mysqli); $mysqli->query("SET names "utf8""); //base set the data encoding in the database

A request like http://domen.ru/articles/index is converted into a Controller - Action. The name of the controller and action is set in the Zend framework style, camel case - Controller_Name, function action_name (). The controller, model, view file must match the controller name in lower case without the Controller_ or Model_ prefix

The model class is specified in the same way - Model_Name, we have already found out the view file - by the name of the action or explicitly in the generate(name) method

Since we are planning to create an admin panel in the future, we will create the client and admin folders. By the way, our router will take into account subfolders, i.e. it will be possible to create subfolders in controllers (e.g. /about/contacts/contacts.php) and access it along its path /about/contacts/
So we started the router

/** * */ public function start() ( // catch AJAX request if ($this->getIsAjaxRequest()) ( ) session_start(); $this->dispatch(); ) /** * */ public function dispatch())( // the dispatcher receives a file matching the name of the controller, action and arguments $this->getDirections($file, $controller, $action, $args); /* ************ * include Controller - Model */ if (is_readable($file) == false) ( die ("File $file 404 Not Found"); ) // connect the controller include ($file); $model = str_replace("controller", "model", $file); // Model additional if(is_readable($model))( // connect the model include( $model); ) /* ****** get the class ** */ $controller = ucfirst($controller); $class = ucfirst($this->namespace)."\Controller_" . $controller; // create an instance of $controller = new $class($this->controller_path_folder); if (is_callable(array($controller, $action)) == false) ( die ("Action $action 404 Not Found"); ) // call action $controller->$action($args); )

The dispatcher calls the getDirections() method, i.e. get request directives. By default, the default controller is articles, the action is index.

/** * @param $file * @param $controller * @param $action * @param $args */ private function getDirections(&$file, &$controller, &$action, &$args) ( $route = ( empty($_SERVER["REQUEST_URI"])) ? "" : $_SERVER["REQUEST_URI"]; unset($_SERVER["REQUEST_URI"]); $route = trim($route, "/\\"); $ controller_path = $this->path; if (empty($route)) ( /* ******************* Default directions ******** */ $controller = "articles"; $action = "action_index"; $controller_path = $this->controller_path_folder = "application/controllers/$this->namespace/"; $file = $controller_path.$controller.".php"; ) else ( $parts = explode("/", $route); /* ************** namespace ********** */ if($parts = = "admin") ( $this->namespace = "admin"; array_shift($parts); ) /* ***************** folders & subfolders ***** ** */ $fullpath = $this->controller_path_folder = $controller_path . $this->namespace; foreach ($parts as $part) ( $fullpath .= DS . $part; if (is_dir($fullpath)) ( array_shift ($parts); continue; ) if (is_file($fullpath . ".php")) ( array_shift($parts); $file = "$fullpath.php"; break; ) ) /* ************* ** Controller, Action, Params ******** */ if(!isset($part)) $part = "articles"; $controller = $part; if(!$file) $file = $fullpath."/$part.php"; $action = array_shift($parts); if(!$action) $action = "action_index"; else $action = "action_$action"; $args = $parts; ) )

In the next lesson we will look at creating a basic controller, models and views, and write queries.

Lesson 1 files here

https://github.com/vaajnur/create_php_application
The master branch will be the 1st lesson, then for each lesson there will be a branch of the same name - lesson1, lesson2, 3..

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 linked to each other), put off the study and use of modern tools “on the back burner.”

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, the purpose of which is 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:

  • When a user visits a web resource, the initialization script creates an instance of the application and launches it for execution.
    This displays the view, say home page site.
  • 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).
  • The application instantiates the controller and runs the action method,
    which, for example, contains model calls that read information from the database.
  • 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 specify 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.

    The controller is the glue that connects models, views and other components into 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.

    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"); // "Contacts" page 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 form feedback 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. The index.php file is the entry point into the application. The bootstrap.php file initiates the loading of the application, including everything required 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 go to the bootstrap.php file:
    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. Last lines connect 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 focus on 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 all page processing 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 in 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 things, 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 ( // here it would also be wiser to throw an exception 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:

  • will include the model_portfolio.php file from the models folder, containing the Model_Portfolio class;
  • will include the controller_portfolio.php file from the controllers folder, containing the Controller_Portfolio class;
  • 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.

    Contents of the model.php file
    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.

    Contents of the view.php file
    class View ( //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:

  • $content_file - views displaying page content;
  • $template_file - template common to all pages;
  • $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.

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

    2.3. Implementation of the descendant classes Model and Controller, creation of View "s Now the fun begins! Our business card website will consist of the following pages:
  • home
  • Services
  • Portfolio
  • Contacts
  • 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 template_view.php is highlighted separately - this is a template containing markup common to all pages. In the simplest case it could look like this:
    home
    To give the site a presentable look, we design CSS template and integrate it into our website by changing the structure of the HTML markup and connecting CSS and JavaScript files:

    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 its 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:
    Welcome!

    OLOLOSHA TEAM is 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...


    This contains simple markup without any PHP calls.
    To display the main page, you can use one of the following addresses:

    We will consider an example using a view displaying data obtained from the model below.

    2.3.2. Create a “Portfolio” page In our case, the “Portfolio” page is the only page that uses the model.
    The model usually includes data sampling methods, for example:
  • methods of native pgsql or mysql libraries;
  • 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.
    Place the model file model_portfolio.php 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 of the dark Dunkel beer 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 on Android based OS and accessories for them."), // todo); ) )

    The model controller class is contained in the controller_portfolio.php file, here is its 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 portfolio_view.php file.
    Portfolio

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


    Everything is simple here, the view displays the data obtained from the model.

    2.3.3. Creating the remaining pages The remaining pages are created in the same way. Their code is available in the GitHub repository, a link to which is provided at the end of the article, in the “Result” section.3. Result Here's what happened in the end:

    Screenshot of the resulting business card website



    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 CMSs that were created in order to be able to develop qualitatively more complex solutions in a shorter period of 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.

    Tags: Add tags

    The first article on developing your own framework will be devoted to an overview of the main intended capabilities and architectural features of the new framework. Here I will describe modules and components, the development of which will be described in detail in subsequent articles. So, let’s decide how we organize the code of the framework and applications that will use it, and then we’ll create a roadmap for the development of the main components.

    To begin with, I chose a simple and concise project directory structure:

      /
    • app – directory of the webroot server, put index.php here
      • public – here we will store static application files
    • src – directory with source code the application itself
      • Blog – each application is located in its own directory
    • lib – directory for external libraries. The framework code will also be located here.
    • var – directory for application files (cache, file storage, sessions, etc.)

    We've decided on the directories. Now we can conveniently separate individual application components into different directories, use PSR0 to autoload classes, etc.
    Next, you need to select the name of the framework in order to simply create a directory for it inside lib. Without thinking twice, I chose the name Bun - short, memorable and seemingly not in conflict with anything.

    Inside lib we create a bun directory (namespace of the vendor), in it another bun directory (namespace of the library), and inside it src - here we will place the code of our framework. This sophisticated directory structure will allow us to connect the framework via http://packagist.org in the future without unnecessary problems.

    Next, we select a namespace for the base classes of the framework - I chose Core. To organize the connection of components outside the Core directory in the future, you will need to organize a modular structure of the framework. Therefore, the first component of the system I have is a module (Bun\Core\Module).

    Bun\Core\Module

    The module should provide basic information to an independent framework component. To begin with, the configurations and dependencies used by the module, version, description of the module, etc. Each directory inside lib/bun/bun/src will be a separate module and must contain a class of the same name (for example, Bun\Core\Core.php) - which represents the implementation of the module.

    Bun\Core\Application

    The application is an implementation of the Front Controller pattern - a single entry point into the application for all requests. The Bun architecture will provide that all requests, including php cli or requests for static files, must be processed within the Application. An application instance will be created in index.php (start file); when initializing the application, its execution environment (production, development, testing, etc) will be indicated. The application will then do the work of loading the configuration, initializing the service container, routing requests through auxiliary components, calling controllers, issuing a response, and terminating the application.

    The first thing we do when an application starts, as a rule, is to load configuration files, so the next basic component will be Config

    Bun\Core\Config

    To configure the application and framework components, a separate set of classes is allocated - Bun\Core\Config\ApplicationConfig - the service itself for managing configs and the Bun\Core\Config\AbstractConfig class - an abstract class that is the base class for configuration elements. The PHP class is selected as the configuration format. This is convenient from a caching point of view, i.e. it is more profitable to store configs directly in the application code than to use separate files xml formats, json, ini, etc.

    Using classes as configs is also convenient for separating the configurations of individual components and parts of the application. It is also convenient for overriding the default framework settings within an application, or application settings within a specific execution environment. By design, each module or application contains within itself the Config namespace - in which configuration classes are stored. Each class has its own namespace, and stores configuration parameters as an array in a protected property.
    Access to configs is assumed via dot notation $config->get("name1.name2.param1") .

    After we have initialized the application configuration, we can begin processing the request. A separate set of Http components is allocated for working with requests and responses from web applications



    Bun\Core\Http

    The set of Http components will be responsible for abstracting from working with superglobal variables $_SERVER, $_GET, $_POST, etc. The Bun\Core\Http\Request service will be responsible for this. In addition, Http will include the Response class - which will be the standard for the result of the application, i.e. launching the controller must end with receiving the Bun\Core\Http\Response object. This class abstracts us from working with http headers, etc. In addition, it is convenient to use derived classes such as AjaxResponse, DownloadResponse, ConsoleResponse, etc.

    When we are ready to receive information about the request, we can move on to routing: the next component is Router

    Bun\Core\Router

    Router is a standard component of modern PHP web applications. There is nothing extra ordinary in the Bun Framework router, a simple config in the form of an array of URL request templates, which is mapped to controller classes and their actions. I plan to implement the ability to parse parameters from a url like /page/view/:page_id - which will be passed to the controller action as arguments. I also plan to separate requests by method (convenient when some methods can only be called via POST - no need to do unnecessary checks in the business logic code)

    From one standard component php applications Let's move on to something else - request routing is closely related to the implementation of the MVC (Model View Controller) pattern. This involves separating application logic, data and display.

    Bun Framework MVC

    It is difficult to distinguish yourself in the implementation of MVC - so everything here is also quite prosaic. Inside Core, I allocate the Controller namespace - here I create a base controller class that has access to all components of the system, stores the object of the application that launched it, and a config service. During initialization, the controller receives launch parameters: the name of the method (action) and its arguments.

    I allocate the display code to a separate View directory inside Core. The Bun Framework does not provide any specific components of the View type - it is assumed that you simply pull the desired template inside the controller, passing data there. By default, I intend to add support for the Twig template engine and support for native *.phtml templates into the framework

    The last component is the Model. I also allocate a separate namespace for it inside Core: Model. This is where another basic component of the framework will come into play - ObjectMapper. The models themselves are just classes, i.e. are not ActiveRecord, but they implement a certain Bun\Core\Model\ModelInterface. It is these classes that ObjectMapper can work with and save them to some kind of storage.

    Now we have to talk about both the object mapper and the storage, let's start with the first.

    Bun\Core\ObjectMapper

    The object mapper is perhaps the most complex part of the framework's Core module. It will be a service that can turn a model object into records in some database, and also do the opposite - take a record from the data store and map it into a model object. By default, the Core module will include a service that implements file storage objects.

    Bun\Core\Storage

    The Storage component set represents the abstraction and interfaces that any storage implementation in an application must follow. The first such storage will be Bun\Core\Storage\FileStorage . To operate, the file storage will use a set of auxiliary classes for working with files, as well as for constructing queries to search for records in the file storage.

    The object mapper described above will be able to work with any implementation of Storage to store its objects there. Here it is worth highlighting another important component of the Core module – Repository.

    Bun\Core\Repository

    Repositories are a layer of access to an object. In order not to overload the object mapper with search functionality and various selections of objects from the storage - this work submitted to the repository. The repository can work directly with the repository, select data from there, and then turn it into objects through ObjectMapper and transfer it to the application.

    Let me immediately mention something related to the components described above – cache

    Bun\Core\Cache

    Cache – will contain a set of abstractions for implementing interaction with various cache storages, such as Redis, Memacached, etc. In addition, the Core module will include the FileCacheDriver component, which implements data caching in files.

    Here we move on to an important and, in my opinion, architecture-defining component of the framework. Flexibility and replaceability of components is achieved when your code is not strictly tied to leading services, but can quickly switch between them. In addition, the next component does a great job of efficiently organizing the framework and application code.

    Bun\Core\Container

    Container – implements one of my favorite programming patterns – Dependency Injection. Dependency injection is good in everything - parts of the application are weakly dependent on specific components, individual classes are easy to test by replacing their dependencies. Convenient to implement alternative options implementation in several services, and then easily switch between them even without changes to the application code. Plus, your classes that use dependency injection clearly show their dependencies - you can even build a dependency graph of your application components.

    Container initializes and stores configured services during application runtime when these services are accessed by the application. Within one class, you essentially have control over all parts of the application.

    This completes the basic components of the framework in the Core module for now. During implementation, the Core module may include implementations of event management, FormBuilder. Among the auxiliary components, it is worth noting the typing of exceptions. The base exception class Bun\Core\Exception\Exception is provided so that all other typed exceptions within the application and framework inherit from it. This provides centralized exception interception at the application level and prevents uncaught exceptions from occurring and causing the application to crash.

    In the following parts of the story about the development of the Bun Framework, I will begin to talk about the Application component, and also describe the essence of autoloading files according to the PSR0 standard. After which, I’ll move on to a description of the application configuration service and a set of Http components

    Last update: 11/29/2017

    Angular has its own routing system that allows you to map routes to components. But ASP.NET Core also has its own routing system, which is used to process requests. What problems might we face? For example, let's take the project from the previous topic, where routing was already used. If we follow links inside the Angular application, then everything will be fine.

    However, if we are directly in address bar browser, we enter the address we need and send a request, then such a request will not be properly processed:

    Despite the fact that the request is being made to the same address, in the second case we will receive a 404 error.

    When we click on a link or programmatically inside an Angular application, the corresponding HTML5 API (Pushstate API) is used, which changes the URL in the browser without sending an HTTP request. But when we manually enter the resource address in the browser's address bar, the browser sends a new request to the ASP.NET Core application.

    In the example above, the request "product/1" was sent. But we don't have a controller action that maps to such a request. So naturally we will get an error. And we need to add additional server-side code so that such requests will also be processed by the Angular application.

    What can we do in this case? It all depends on how the web page is formed. Or it is directly a static web page that is sent directly to the user. Or this is a view (a file with a cshtml extension) on which the loading code for the Angulr application is defined.

    Queries for static files

    In the previous topic, the Angular application was loaded into a static web page, index.html, which was located in the project in the wwwroot folder:

    Angular in ASP.NET Core

    And in this case, we need that, if the request was not intended for a static file, and was not intended for any of the controller actions, then the index.html file would be sent in response to such a request. And in this case, on the server side we can use several approaches.

    Run method

    The easiest way is to add middleware to the end of the request processing pipeline, which would send the index.html file. To do this, change the Configure method in the Startup class:

    Public void Configure(IApplicationBuilder app, IHostingEnvironment env) ( if (env.IsDevelopment()) ( app.UseDeveloperExceptionPage(); app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions ( HotModuleReplacement = true )); ) app.UseDefaultFiles(); app.UseStaticFiles( ); app.UseMvc(); // process routes that are not previously mapped to resources app.Run(async (context) => ( context.Response.ContentType = "text/html"; await context.Response.SendFileAsync(Path .Combine(env.WebRootPath, "index.html")); )); )

    Thus, for all requests that are not mapped to resources in the application, the file wwwroot/index.html will be sent.

    The disadvantage of this approach is that it will be difficult for us to enable 404 error handling on the server side. Since if we do not have controllers and actions corresponding to the request, then the request will still be processed, albeit on the client side in Angular.

    Sending a file using the controller method

    A similar approach is provided by sending a file using the controller method. For example, let's say you have the following HomeController in your project in the Controllers folder:

    Using Microsoft.AspNetCore.Mvc; using System.IO; using Microsoft.AspNetCore.Hosting; namespace HelloAngularApp.Controllers ( public class HomeController: Controller ( IHostingEnvironment env; public HomeController(IHostingEnvironment env) ( this.env = env; ) public IActionResult Index() ( return new PhysicalFileResult(Path.Combine(env.WebRootPath, "index.html "), "text/html"); ) ) )

    In this case, the Index method sends the index.html file.

    In this case, change the Configure method of the Startup class as follows:

    Public void Configure(IApplicationBuilder app, IHostingEnvironment env) ( if (env.IsDevelopment()) ( app.UseDeveloperExceptionPage(); app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions ( HotModuleReplacement = true )); ) ` app.UseDefaultFiles(); app.UseStaticFiles (); app.UseMvc(routes =>

    In this case, two routes are defined for the MVC infrastructure. The first route maps to regular controllers and their actions. The second route is defined using the MapSpaFallbackRoute() method and maps to the Index action of the Home controller, which in this case sends an index.html file back to the user.

    Requests to controller actions

    We can optionally define the loading of an Angular application in a static html page. It could also be a performance. For example, let’s define the Views/Home directory in the project and place it in new file Index.cshtml:

    Angular in ASP.NET Core

    In this case, the view contains only html code, although if necessary, you can also define Razor instructions in it, create a master page for it, etc.

    And the HomeController's Index method will use this view:

    Using Microsoft.AspNetCore.Mvc; namespace HelloAngularApp.Controllers ( public class HomeController: Controller ( public IActionResult Index() ( return View(); ) ) )

    In the Configure method of the Startup class, we define the following code:

    Public void Configure(IApplicationBuilder app, IHostingEnvironment env) ( if (env.IsDevelopment()) ( app.UseDeveloperExceptionPage(); app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions ( HotModuleReplacement = true )); ) //app.UseDefaultFiles(); - this the method is no longer needed app.UseStaticFiles(); app.UseMvc(routes => ( routes.MapRoute(name: "default", template: "(controller=Home)/(action=Index)/(id?)"); routes.MapSpaFallbackRoute("angular-fallback", new ( controller = "Home", action = "Index" )); )); )

    Here again, using the routes.MapSpaFallbackRoute method, all other requests that are not mapped to other resources will be handled by the Index method of the HomeController.