Application architecture - PhpGt/WebEngine GitHub Wiki
The most minimal PHP.Gt WebEngine application consists of the following source files:
.
├── page
| └── index.html
└── composer.json
For context, a tree diagram of an example large application with many source files can be seen on the example large application tree page.
Model View Controller (MVC)
When describing software architecture of any framework, the topic of MVC will always come up. MVC is a broad architectural pattern that describes three areas of responsibility that any software application should be split into:
- Model: A model can be described as how the application stores its data, and in what form. For example, an object that represents a record in a database can be classified as a "model" object.
- View: The view represents the output of an application. On the web, a view is is webpage, or the components that build up that page.
- Controller: A controller is the logic commands that react to user input, bind models to the views, and generally execute the whole business logic of the application.
The MVC architectural design pattern is used to identify the three major areas of an application, but is very broad and doesn't go into detail of how to lay out a project, or how to break down the large areas of code that make up the M, V and C.
Software Architecture Pyramid
Inspired heavily from Robert Martin's "Clean Architecture", The Software Architecture Pyramid is a client-server software architecture pattern in which layers of the application have strong boundaries with one-directional dependency trees.
The main aim of the Software Architecture Pyramid is to address the challenges faced by the MVC or multi-tier architecture patterns, such as tight coupling of dependencies and separations of concern between the different areas of an application.
This architecture pattern is designed so that business logic is not bound to the framework it runs upon. Layers of the pyramid should not be aware of the layers below. The outcome of this pattern is that all logic is abstracted away from a web request's execution path, instead inverting the path of control to the tip of the pyramid down.
From the top to the bottom:
- View layer: The views of the application should not know about any of the code beneath. They should be written so that they could be easily plugged into any software framework - the less proprietary technologies the better.
- Unit layer: The topmost layer of logical code is the business logic and use cases of the application. It should be pluggable into another framework without any modification. Larger projects can store their units in separate repositories.
- Entity layer: holds objects that are core to the domain that you're programming for, such as code for Customer, Order, Vehicle, etc.
- Logic layer - creates and dispatches the correct entities and units of the application in the correct order according to the incoming request.
- Framework layer - holds the code that builds up the framework, binding your application together.
- Base layer - defines the drivers of the software stack, such as extensions of PHP (PDO, XML, etc.).
- Foundation layer - includes the low level tools that are required to run the whole stack, such as PHP, Linux, Nginx, etc.
Using the metaphor of a pyramid in software design promotes clean dependency trees. Each layer has to be lighter than the one beneath it, as a top-heavy pyramid is going to be trouble. Each unit at the tip of the pyramid has one job and hardly any dependencies to achieve it.
This style of dependency injection greatly improves the testability, maintainability and readability, producing robust code that isn't fragile to change.
Layer boundaries
The business logic of an application can be seen as the policies and actions the business would perform even if there was not an automated software system in place. A perfect software design would keep these classes in their own code repository and the project would depend on them using Composer. This makes versioning of logic much simpler, allows for better isolated unit testing, and errors would typically be caught earlier. However, this may be overkill for small projects. Keep in mind that the business logic should be coded in a style that allows it to be extracted into its own repository should the need arise, but be mindful of unnecessary early overcomplication.
It's too easy to cheat the layers of an application's architecture if development is started from the base up. To enhance each layer's quality, build from the tip down as far as is feasible. Designing the static HTML page views first allows functionality to progressively enhance a static website towards a full dynamic application, and also allows an applications graphic and user interface design to be built in a reactive manner, rather than as a passing thought at the end of a project.
WebEngine example
Starting at the very tip of the pyramid, the views for a WebEngine application can be seen as the HTML pages or components of pages. These can be written in standard HTML so they can be read by any web framework. There are times when markup is required to indicate dynamic content, but this should be done using W3C standards compliant HTML, which can be understood by any system. Typically a designer or client-side developer will construct the page view files.
In an ideal project, the units of the business application can be built as a totally separate system; an API of business logic that can be run within any framework. On smaller projects, the units will be written in the class/
directory, organised by namespace, or on larger projects could even be stored in separate repositories and worked on by other teams. The code that makes up the unit layer consists of a software version of the business's existing policies and processes.
Entities of the application sit between the "Model" and "Controller" of an MVC system, and can be stored in the class/Entity/
directory, for example. With the knowledge of the units that make up the system, the server-side developer(s) can code entities of the system. These are object representations of the data that is persisted - User
, Vehicle
, Customer
, Order
, Payment
, etc. Entities will have methods that automate and simplify the interactions between different parts of the system.
To tie everything together, the Logic Layer sits between the "Controller" and "View" of an MVC system. The Logic Layer is the entrypoint of the code for each page request, promoting Distinct Logic Authority. Within the page/
directory, the logic script that matches the current URL is executed. For example, a request to /shop/categories
can be handled by the go()
function within page/shop/categories.php
.
The go()
function within a Page Logic class should aim to do as little as possible itself. Instead it should be as abstract as possible, passing just the required data to the correct functions and classes that are used to construct the whole dynamic response. The calls that the go()
function would typically make would be to classes that handle user input, load data from data sources and inject data into the page view by Binding data to the DOM.
As with all layers of the pyramid, the Page Logic of your application should not know or care whether the database is being queried using SQLite, Postgres or MySQL. The units of business logic should not know or care whether they are being used within a web application or controlled in a terminal.