PHP.GT

Application architecture

The most minimal PHP.GT WebEngine application consists of the following source files:

.                     
├── page          
|   └── index.html
└── composer.json    
For context, here's an example large application's source files
.
├── data
├── api
│   └── v1
│       ├── example-endpoint.json
│       ├── example-endpoint.php
│       ├── user-auth.json
│       └── user-auth.php
├── asset
│   ├── logo.svg
│   └── pattern.png
├── class
│   ├── Input
│   │   ├── Captcha.php
│   │   ├── Search.php
│   │   └── Validation.php
│   ├── Model
│   │   ├── Category.php
│   │   ├── Item.php
│   │   ├── OutOfStockException.php
│   │   └── SpecialOffer.php
│   ├── Payment
│   │   ├── AbstractPayment.php
│   │   ├── CreditDebitPayment.php
│   │   └── PayPalPayment.php
│   ├── UI
│       ├── Error.php
│       ├── Formatter.php
│       └── Message.php
├── page
│   ├── _component
│   │   ├── cart-popup.html
│   │   ├── category-item.html
│   │   ├── checkout-listing.html
│   │   ├── out-of-stock.html
│   │   ├── payment-form.html
│   │   └── shop-item.html
│   ├── checkout
│   │   ├── index.html
│   │   ├── index.php
│   │   ├── payment.html
│   │   └── payment.php
│   ├── shop
│   │   ├── category
│   │   │   ├── _common.php
│   │   │   └── @category.html
│   │   ├── categories.html
│   │   ├── categories.php
│   │   ├── index.html
│   │   ├── index.php
│   │   ├── item.html
│   │   └── item.php
│   ├── _common.php
│   ├── _footer.html
│   ├── _header.html
│   ├── about.html
│   ├── contact.html
│   ├── contact.php
│   ├── index.html
│   ├── index.php
│   └── terms.html
├── query
│   ├── _migration
│   │   ├── 001-create-table-shop-item.sql
│   │   ├── 002-create-table-shop-category.sql
│   │   ├── 003-alter-table-shop-item--add-field-currency.sql
│   │   ├── 004-create-table-purchase.sql
│   │   └── 005-create-table-payment.sql
│   └── shop
│       ├── category
│       │   └── getAll.sql
│       └── item
│           ├── completePurchase.sql
│           ├── getById.sql
│           ├── getInCategory.sql
│           └── startPurchase.sql
├── script
│   ├── Async
│   │   ├── Updater.es6
│   │   └── UrlHistory.es6
│   ├── Input
│   │   ├── Dirty.es6
│   │   ├── Message.es6
│   │   └── Validate.es6
│   └── script.es6
├── style
│   ├── animation
│   │   ├── flash.scss
│   │   └── popup.scss
│   ├── base
│   │   ├── form.scss
│   │   ├── normalise.scss
│   │   ├── typography.scss
│   │   └── variables.scss
│   ├── component
│   │   ├── cart-popup.scss
│   │   ├── category-item.scss
│   │   ├── checkout-listing.scss
│   │   ├── out-of-stock.scss
│   │   ├── payment-form.scss
│   │   └── shop-item.scss
│   ├── page
│   │   └── checkout.scss
│   └── script.scss
├── build.ini
├── composer.json
├── config.ini
└── redirects.csv

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:

  1. Model: a model can be described as how the application stores data, and in what form. For example, a class that represents a record in a database can be classified as a “model” object. This would be the “Entity” in the Repository-Entity pattern.
  2. 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. In WebEngine, the view is represented by the page view.
  3. 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. In WebEngine, the controller is represented by the page logic, and passes control to application classes as soon as possible.

File layout and execution flow affect maintainability from the very start of a project. In practice, architecture is not an academic concern here. It is simply the question of whether the next developer can find the right file, understand the request flow, and change the application without breaking unrelated things.

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.

Software Architecture Pyramid. From bottom to top: Foundation, Base, Framework, Logic, Entity, Unit, View

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

In a WebEngine application, the pyramid can be read top-down like this:

  • Testing WebEngine applications because the best time to prove behaviour is while the system is still small enough to reason about clearly
  • Page Views because the shape of the response should usually exist before the implementation grows around it
  • Page Logic because each request still needs an entry point
  • Application classes because business behaviour should move out of the page file as soon as it becomes reusable or non-trivial
  • domain entities and value objects where the application’s own concepts live
  • Databases and other infrastructure adapters where persistence and external systems are handled
  • framework glue such as routing, configuration, and the service container

Thin page logic

Page logic should orchestrate rather than own business rules. A good page file usually reads like a short story of the request: get the input, call the right service, bind the result, maybe redirect.

That keeps pages easy to understand because the request-specific flow is visible without dragging all the application behaviour into one file.

Keeping boundaries clean

Try to keep each layer from leaking into the others. Views should not need to know how the database works. Page logic should not need to know the SQL. Application classes should not need to know which page called them.

This does not need to be perfect from day one, but it is a useful direction to keep moving in. Application code that can survive framework changes is usually also application code that is easier to test and easier to explain.

Hypermedia

Hypermedia, in this context, simply means HTML over HTTP. The browser requests a URL, the server returns a document, and links and forms move the user through the system.

Writing for hypermedia first does not mean refusing JavaScript. It means making the URL, the document, and the request flow the primary design. That tends to lead to better architectural decisions because each page state has a clear entry point and a clear response.

Once that foundation is in place, the application can still gain a fluid user experience without giving up the simplicity of ordinary page requests.


Part of good application design is following a coding styleguide.