Skip to content

Request response lifecycle

Greg Bowler edited this page May 17, 2026 · 26 revisions

When working with any tool, it is important to understand how the tool works. Here we will look under the bonnet at what makes WebEngine work so you can use the knowledge to make the best decisions when writing applications.

Within PHP.GT, everything is broken down into its own repository of single responsibility. The PHP.GT WebEngine brings all of these components together to produce a full application framework. As the name suggests, this framework utilises web technologies and the purpose of this page of the documentation is to walk through what goes into the whole lifecycle of the application.

Step 0: A request is made

The lifecycle starts its journey when a user enters a URI or clicks a link to be taken to a page within your application.

Before any code is executed, an HTTP request from the user's browser will be received by the web server. This can be any server, such as NGINX, Apache or the inbuilt server.

The job of the server is to listen for incoming requests and send back the correct response in the form of a static file (such as an image), a WebEngine page (handled by your code), or a 404 Not Found message.

When a request to a page of your application is received by the server, the request/response lifecycle begins. The entry point of all WebEngine requests is the go.php file in the root of the WebEngine directory. This is sometimes referred to as the router script. If WebEngine is installed using Composer, the path to the router script will be vendor/phpgt/webengine/go.php.

See the web servers section for more information about configuring servers and using the inbuilt server.

Step 1: Go!

Follow along in code by viewing the source.

The go.php file firstly loads the Composer autoloader. This allows object oriented code to be loaded automatically and efficiently based on the namespaces of the classes used. Read the introduction to Composer article at getcomposer.org for more information.

Next, the script checks to see if the incoming requested file exists in the public web root of your application. If a static file exists, the script immediately returns false. This is used by the inbuilt server as to not accidentally process static files. "Real" web servers such as NGINX and Apache should not get this far, as they should detect and serve static files themselves.

If there's a setup.php file in the project root, it will be loaded here. This allows us to inject any plain PHP to the start of the request before the Application has started at all, useful for tweaking the environment or setting up a custom error logger, for example.

Finally, now the Composer autoloader is in place, we can utilise the benefits of object oriented code by instantiating our first object - the Application object. We call the start() function of the Application object to jump into object oriented code. From here on, all executed code is done in a structured, predictable and understandable manner, thanks to Composer's autoloading namespaces.

Step 2: The start of the Application Lifecycle

The Application class initialises and calls the core elements of PHP.GT that make up WebEngine. The start function abstracts this as much as possible, giving you a high-level overview of what's going on.

Within the start function, all core objects are created:

  • Config - loaded first so the rest of the request can use project settings.
  • Redirect - checks file-driven redirects before the main application lifecycle continues.
  • Timer - measures the full request duration and logs slow requests.
  • OutputBuffer - captures unexpected direct output so the response stays under framework control.
  • RequestFactory - builds the incoming Request object from PHP globals.
  • Dispatcher - prepares routing, services, view handling, logic execution, and the final Response.
  • Protection - locks down direct access to superglobals and replaces them with explicit object-oriented access.

With the above objects created, the Application object passes them to areas of WebEngine and your code where required. Let's continue the journey...

Step 3: Configuration

The responsibility of loading project configuration is maintained within the PHP.GT/Config repository. In WebEngine, we call the ConfigFactory's createForProject function which allows many .ini files to be loaded, overriding defaults where necessary. This is useful to allow for different configuration per environment, such as on a staging server versus the live server.

Learn more about configuration in the Configuration page.

Step 4: Redirects, timing, and output buffering

Before the main request is handled, the Application checks whether the current path matches a redirect rule. If it does, the redirect is performed immediately and the rest of the lifecycle does not continue for that request.

If there is no redirect, WebEngine starts a timer and opens its debug output buffer. The timer is later used for request logging and slow-request notices. The output buffer captures any direct echo or debug output, so that the framework can still control the final HTTP response cleanly.

Step 5: Protect globals and build the request

At the start of the application lifecycle, WebEngine protects the PHP superglobals. This prevents page code and third-party code from casually reaching into $_GET, $_POST, $_FILES, $_COOKIE, $_SERVER, and related globals.

The reason for this is code safety: if every function can read and write global state directly, it becomes hard to track where input came from, which code changed it, and whether unrelated code can interfere with the request.

Once those globals are protected, RequestFactory builds a Request object from the original request data. That object becomes the main representation of the browser request for the rest of the lifecycle.

Step 6: Create the dispatcher

With configuration loaded and the request object available, Application creates a Dispatcher. This object owns most of the framework work that follows:

  • setting up the service container
  • preparing the response object
  • normalising the request path
  • loading router and view information
  • starting the session
  • executing page logic
  • streaming the final view into the response body

The dispatcher is also stored on the Application so that if an error occurs later, WebEngine can reuse the same request context when trying to render an error response.

Step 7: Initialise routing and view state

Inside the dispatcher, several setup stages happen in order:

First, the application autoloader is attached for our own namespaced classes, and the logic stream handler is prepared for page logic loading. Next, request initialisation normalises the path and can prepare an immediate redirect, such as for trailing-slash behaviour.

If no redirect is prepared, router initialisation loads the application's router, decides which view model type is needed, and builds the Assembly objects that describe the view and logic files for the current route.

At this point WebEngine knows which page is being handled, which view file belongs to it, which logic file belongs to it, and whether the response is HTML, JSON, or effectively empty.

Step 8: Start sessions and bind the view model

Once routing is known, the dispatcher starts the session using the configured session handler and places the session object into the service container.

Learn more about session usage in the Sessions page.

Then the view model is initialised. For HTML responses this includes preparing the document binder and related DOM binding helpers, as well as processing partials and components. This is where the framework gets the page ready for your logic to bind data into it cleanly.

That work makes more sense once you have read page views, binding data to the DOM, and DOM manipulation.

Step 9: Execute page logic

When the dispatcher generates the response, it first checks whether the route resolved to any actual page files. If neither a view nor a logic file exists, WebEngine raises a 404 Not Found.

Otherwise, the dispatcher processes the response in a defined order. It resolves dynamic path values, processes partial content, verifies CSRF state where required, initialises the view model, and then executes logic hooks for components followed by the page itself.

The logic execution order is:

  1. go_before
  2. matching do_* handler if a do input value is present
  3. go
  4. go_after

That order preserves the original WebEngine concept that page logic coordinates the incoming request, while still giving us explicit hook points before and after the main page action has executed.

See page logic, page logic conventions, and file-based routing for the request-facing conventions behind these hooks.

Step 10: Build the response body and headers

After logic has run, WebEngine applies response headers collected during the request, performs document cleanup, applies CSRF protection to the HTML where needed, and then streams the view model into the response body.

If our code did not set a status code explicitly, WebEngine finishes normal responses with 200 OK. It also records the executed logic hooks in an X-Logic-Execution response header. That header contains the page logic functions that ran, in execution order, using a compact path:function format. It is useful when checking which shared and page-specific handlers actually participated in a request.

For more on that header and the way WebEngine surfaces development-time traces, see Development diagnostics.

The resulting Response object is now a complete HTTP message containing headers, status, and body.

Step 11: Finish and send the response

The last step happens back in the Application object. Its finish method sends the response headers, performs request logging where configured, writes the response body to the browser, and then stops the timer.

That is also where slow-request timing is logged and where debug output collected by the output buffer can be appended in development-oriented scenarios.

Where application code runs

Our application code mainly enters the lifecycle once routing has discovered the relevant page files. The matching page logic file is where WebEngine looks for handlers such as go_before, do_*, go, and go_after.

The best companion pages here are page logic, page logic conventions, and service container.

By the time those handlers run, the important request-related services are already available. That includes input handling, sessions, configuration, and the view model itself. Those services are supplied through the service container, so page logic can stay light and ask only for what it needs.

In practice that means page logic should mainly coordinate the request:

  • read the relevant input
  • call application classes
  • bind data into the document
  • redirect or set response state where needed

Anything heavier than that usually belongs in application classes rather than in the page entry point itself.

How errors fit into the lifecycle

If something goes wrong while the request is being handled, WebEngine catches the throwable, logs it, and attempts to render the appropriate error response. In development that usually means more detail is available. In production the response should be simpler and safer.

The framework can also look for application error pages, so error output can match the rest of the site rather than falling back to plain text or a default PHP error screen. The details of that are covered later in Errors and logging.


Now we've covered the page lifecycle, let's move on to file-based routing.

Clone this wiki locally