Introduction
Hello to all habrovchanam, fans of Yii and Node.js.
I continue a series of articles about the
Jii framework and its parts. In the previous parts, we looked at parts of the framework that can be used without initializing the application, namely
Query Builder and
Active Record . From the vote (as well as letters and comments), it became clear that it was worthwhile to continue. And this time we will talk about the architecture and structural components of the Jii framework.
Ideology
Transferring the framework functionality from PHP to JavaScript is not a trivial task and cannot be performed by a robot. Languages have very different features and differences, the main one being the asynchrony of JavaScript.
Below is how these problems were solved and what practices I followed. These same practices should be followed by other developers when developing Jii and its extensions.
Promise
Each asynchronous operation returns a Promise object implementing the ES6 standard. For this, the
when library is used.
Methods starting with the prefix
load (loadData ()) return Promise, methods with the prefix
get (getUser (), get ('id')) - return data synchronously.
Saving API Yii 2
The original idea of Jii is to bring a beautiful and rich PHP framework to JavaScript. So that php developers who want to try / go to node.js can quickly and easily master the framework on
other programming language, even without reading reference class!
')
Buildability
Jii is designed taking into account the fact that it should work wherever possible - browser, server, phonegap, node-webkit, etc.
Despite the fact that
Yii generates quite a few global constants (
YII_ * ), the class
Yii and the naming
space yii ,
Jii do not
clog upglobal space. Browsers have access to a single Jii object, which can be hidden by calling
Jii.noConflict () . In the server part, nothing is written to global, and Jii is returned as a result.
call
require ('jii') .
Npm packages
Jii is distributed as a set of jii- * packages and does not have its own package managers (hello, Meteor). This means that with Jii you can connect any other npm packages.
Jii is broken into several packages, so it can be used in parts. For example, if you want to start using only ActiveRecord, then you install jii-ar-sql and do not have controllers, views, http server, and other code that you do not need.
If you have read the
previous articles , you have already seen this one.
The main packages for Jii at the moment are:
Getters and Setters
One of the main features of
Yii is the ease of accessing the properties and methods of objects through getters and setters, for example, by accessing
$ model-> user, you can get the model property, or call the
getUser () method, or even get the user relation, where DB - the magic is complete, but everyone likes it.
In JavaScript, not all browsers support getters and setters, and many projects still need to support IE8, so in Jii they are implemented as
get ('user') and
set ('user', 'Ivan') methods. This approach can be found in many libraries, for example in
Backbone .
In the future, when the need for old browsers disappears, then real getters and setters will be added in parallel with get / set methods that will work through them.
Classes and Neimspaces
As they say, every self-respecting programmer should write his own implementation of classes in JavaScript. So here, for the implementation of classes, my
Neatness library is used, which has already performed well in other internal projects.
Why not ES6 classes (and not CoffeeScript / TypeScript)?
In the
comments and on the
githaba there is already a small holivar about this.
I will sound here the reasons for the absence of ES6 in Jii:
- For node.js, you need preprocessing (io.js supports)
- In es6, there are no namespaces, but they are needed to repeat the features of Yii. They can be implemented through modules, but it will be a crutch. Change one crutch to another - it makes no sense.
- In ES6, you will want to use getters and setters, but they are not supported by older browsers, as mentioned above.
- Many developers do not know the format es6 / coffeescript / typescript and this will be an additional threshold of entry
- The difficulty of working with code of different formats (ES5 and ES6). It is not always possible in an existing project to change all the code to es6, and you still need to inherit the es6 classes, and you still have to use Jii.defineClass () to create classes - the crutch does not go away.
Below I added a poll on this topic, vote, comment!
Middlewares
In Yii, the framework components are available globally via
Yii :: $ app-> ... However, in
Jii, not all components can be arranged globally, since some of them (Request, Response, WebUser, Session, ...) are bound to the context (request).
Such components will be created in context (
Jii.base.Context ) and passed as a parameter to the action - an analogy with the transfer of request and response to
express .
Jii.defineClass('app.controllers.SiteController', { __extends: Jii.base.Controller, actionIndex: function(context) { context.response.data = this.render('index'); context.response.send(); } });
Entities
Jii applications are organized according to the model-view-behavior design pattern (MVC) and have the following entities:
- applications : these are globally accessible objects that perform the correct operation of the various components of the application and coordinate them to process the request;
- application components : these are objects registered in the application and providing various capabilities;
- request components : these are objects registered in the context of the request and providing various possibilities for processing the current request;
- modules : these are self-contained packages that include all the tools for MVC. An application can be organized using several modules;
Examples of their use can be seen in the
demo .
Applications
Applications are objects that control the entire structure and life cycle of an application system Jii. Usually on one worker (process) of Node.js comes one instance of the Jii application, which is accessible via Jii.app.
When the input script creates an application, it loads the configuration and applies it to the application, for example:
var Jii = require('jii');
An important difference from Yii is that the configuration is divided into two parts - the application configuration (application section) and the context (request) configuration (context section). Application configuration creates and configures components, modules and configures the application itself (Jii.app) - all this happens when you start the worker. In turn, the context configuration creates and configures components with each action call — http request, console command call, etc. The created components are passed as the first argument to the action method.
Due to the fact that the configuration of the application is often very complex, it is put into files and broken into several configuration files.
Application Components
Applications store many application components that provide various means for the operation of the application. For example, the components urlManager is responsible for routing web requests to the desired controller; the db component provides tools for working with the database; etc.
Each component of the application has its own unique ID, which allows you to identify it among other various components in the same application. You can access the component as follows:
Jii.app.ComponentID
Embedded Application Components
Jii has several built-in application components, with fixed IDs and default configurations.
Below is a list of embedded components of the application. You can configure them as well as other components of the application. When you configure the embedded component of the application and do not specify the class of this component, the default value will be used.
- db , Jii.sql.BaseConnection : is a connection to a database through which you can perform queries. Note that when you configure this component, you must specify the component class as well as the other necessary parameters.
- urlManager , Jii.urlManager.UrlManager : used for parsing and creating URLs;
- assetManager , Jii.assets.AssetManager : used to manage and publish application resources;
- view , Jii.view.View : used to display views.
Context components
The set of context components depends on where it is applied. The most common option is the HTTP request of the user, for which we will consider the built-in set of components.
As with application components, each application component has its own unique ID, which allows it to be identified among other various components in the same context. You can access the component as follows:
actionIndex: function(context) { context.ComponentID }
Built-in query components
Below is a list of embedded query components. You can configure them as well as other components in the context section of the configuration file.
- response , Jii.base.Response : is the data from the server that will be sent to the user;
- request , Jii.base.Request : is a request received from end users;
- user , Jii.user.WebUser : is the information of the authenticated user;
Controllers
Controllers are part of the
MVC architecture. These are objects of the classes inherited from
Jii.base.Controller and are responsible for processing the request and generating the response. In essence, when processing the HTTP request by the server (
Jii.httpServer.HttpServer ), the controllers will analyze the input data, transmit it
into models, insert model results into [views] (structure-views), and ultimately generate outgoing responses.
Actions
Controllers consist of
actions that are basic units that the end user can access and request the execution of a particular
functional. The controller may have one or more actions.
The following example shows a
post controller with two actions:
view and
create :
Jii.defineClass('app.controllers.PostController', { __extends: Jii.base.Controller, actionView: function(context) { var id = context.request.get('id'); return app.models.Post.findOne(id).then(function(model) { if (model === null) { context.response.setStatusCode(404); context.response.send(); return; } context.response.data = this.render('view', { model: model }); context.response.send(); }); }, actionCreate: function(context) { var model = new app.models.Post(); Jii.when.resolve().then(function() {
In the
view action (defined by the
actionView () method), the code first loads the model according to the requested model ID; If the model is successfully loaded, the code will display it using a view called
view .
In the
create action (defined by the
actionCreate () method), the code is similar. He first tries to load the model using the data from the query and save the model. If everything went well, the code redirects the browser to the action
view with the ID of the newly created model. Otherwise, it displays the
create view, through which the user can fill in the required data.
Routes
End users access actions via so-called * routes *. The route is a string consisting of the following parts:
- Module ID: it exists only if the controller belongs not to the application, but [to the module] (structure-modules);
- Controller ID: a string that uniquely identifies the controller among all other controllers of the same application (or the same module, if the controller belongs to the module);
- Action ID: A string that uniquely identifies an action among all other actions of the same controller.
Routes can be in the following format:
ControllerID/ActionID
or the following format, if the controller belongs to the module:
ModuleID/ControllerID/ActionID
Creating action
Creating actions is not as difficult as declaring the so-called * action methods * in the controller class. An action method is a method whose name begins with the word
action . The return value of the action method is the response data that will be sent to the end user. The code below defines two actions,
index and
hello-world :
Jii.defineClass('app.controllers.SiteController', { __extends: Jii.base.Controller, actionIndex: function(context) { context.response.data = this.render('index'); context.response.send(); }, actionHelloWorld: function(context) { context.response.data = 'Hello World'; context.response.send(); } });
Action classes
Actions can be defined as classes inherited from the
Jii.base.Action or its descendants.
To use such an action, you must specify it by overriding the
Jii.base.Controller.actions () method in your controller class, as follows:
actions: function() { return {
As you can see, the
actions () method should return an object whose keys are action IDs, and the values are the corresponding action class names or [configuration] (concept-configurations). Unlike inline actions, individual action IDs can contain arbitrary characters, as long as they are defined in the
actions () method.
To create a separate action, you must inherit from the
Jii.base.Action class or its descendants, and implement a public
run () method. The role of the
run () method is similar to other action methods. For example,
Jii.defineClass('app.components.HelloWorldAction', { __extends: Jii.base.Action, run: function(context) { context.response.data = 'Hello World'; context.response.send(); } });
Modules
Modules are self-sufficient program blocks consisting of models, views, controllers, and other auxiliary components. When installing modules into an application, the end user gets access to their
controllers. For this reason, modules are often considered as miniature applications. Unlike applications, modules cannot be created separately; they must be inside applications.
Creating modules
A module is placed in a directory called the
base module
path (
Jii.base.Module.basePath ). As well as in the application directory, in this directory there are subdirectories
controllers ,
models ,
views and others, in which controllers, models, views and other elements are located. The following example shows the approximate contents of the module:
modules/ forum/ Module.js controllers/ DefaultController.js models/ views/ layouts/ default/ DefaultController index.ejs
Module Classes
Each module is declared using a unique class that inherits from
Jii.base.Module . This class must be placed in the root of the
base path of the module. During the launch of the application (worker) will be
One instance of the corresponding module class has been created. Like application instances, module instances are needed so that the module code can share data and components.
Here is an example of what a module class might look like:
Jii.defineClass('app.modules.forum.Module', { __extends: Jii.base.Module, init: function(context) { this.params.foo = 'bar'; return this.__super(); } });
Module Controllers
When creating module controllers, it is customary to place controller classes in the controllers subspace of the module class namespace. This also implies that the controller class files must be located in the
controllers directory of the module's
base path . For example, to describe the
post controller in the
forum module from the previous example, the controller class is declared as follows:
var Jii = require('jii'); Jii.defineClass('app.modules.forum.controllers.PostController', { __extends: Jii.base.Controller,
You can change the namespace of controller classes by setting the
Jii.base.Module.controllerNamespace property. If any controllers fall out of this namespace, they can be accessed by setting the
Jii.base.Module.controllerMap property, similar to how it is done in the application.
Using Modules
To use a module in an application, it is enough to include it in the
Jii.base.Application.modules property in the application configuration. The following code in the application configuration uses the
forum module:
var config = { application: {
The Jii.base.Application.modules
property is assigned an object containing the module configuration. Each object key is a * module identifier *, which uniquely identifies a module among other application modules, and the corresponding object is a configuration for creating a module.
Access to modules
Access to the module instance can be obtained in the following way:
var module = Jii.app.getModule('forum');
Having an instance of a module, you can access the parameters and components registered in the module. For example,
var maxPostCount = module.params.maxPostCount;
In custody
Below, I offer a survey about the format of the Jii code. If you choose a non-JavaScript format, then indicate in the comments how you would solve the problems described in the section “Why not ES6 classes (and not CoffeeScript / TypeScript)?” In this article.
Let me remind you, Jii is an open source project, so I will be very happy if someone joins its development. Write to affka@affka.ru.
Framework Site -
jiiframework.ruGitHub -
https://github.com/jiisoftTalk features on
githabLiked? Put a
star on the githaba!