Skip to content

Request response lifecycle

Greg Bowler edited this page Sep 7, 2018 · 22 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 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.

Next, the Composer autoloader is loaded. 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.

Finally, now the Composer autoloader is in place, we can utilise the benefits of object oriented code by instantiating our first object - the Lifecycle object. We call the start() function of the Lifecycle 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 Lifecycle

The Lifecycle 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:

  • ServerInfo - an object oriented representation of the $_SERVER superglobal.

  • Config - loads configuration variables from .ini files and environment variables. Jump to the configuration section.

  • Input - the object for accessing user input (GET / POST parameters and FILES). Jump to the user input section.

  • CookieHandler - an object oriented representation of $_COOKIE superglobal. Jump to the cookies section.

  • SessionHandler - an object oriented representation of $_SESSION superglobal. Jump to the sessions section.

  • Database - the object for accessing the database. Jump to the database section.

  • Request - a PSR-7 compliant representation of the incoming HTTP request.

  • Router - an object that matches the incoming request to the correct area of your code.

  • Dispatcher - an object that creates and calls your application code in the correct order.

  • Response - a PSR-7 compliant representation of the outgoing HTTP response.

With the above objects created, the Lifecycle 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: Session handler

PHP's sessions that are typically loaded into the $_SESSION superglobal are done so through a class that implements SessionHandlerInterface. PHP.Gt/Session provides an alternative session handler that exposes session storage through an object oriented API, rather than accessing variables globally.

Learn more about session usage in the Sessions page.

Step 5: Protection of global variables

As the Lifecycle class is at the very beginning of the request/response lifecycle, it has a very important job of protecting the use of global variables. This prevents the code you write or third-party code you depend on from reading and writing to variables globally.

Superglobal variables should be avoided because since every function has access to them unconditionally, it's very difficult to know which functions (yours or third party) actually read or write to the variables, making the state of your application unpredictable. Security is also compromised by the use of superglobals due to all functions having full unmitigated access to all user input by default in PHP. Protecting global variables and replacing them with object oriented alternatives is a solution offered by PHP.Gt.

Step 6: Attach autoloaders

Composer is used to autoload code according to PSR-4, which makes the location of source code inherently obvious due to the namespaced code. However, the Page Logic classes of your application in WebEngine cannot adhere to PSR-4 -- instead, mapping to the URL they represent. This distinction is outlined in the PHP.Gt/Styleguide.

The pathnames throughout a project must be obvious by either mapping directly to a class's namespace, or relate to a requested URL.

For WebEngine to load your Page Logic files correctly for each requested URL, the Logic Autoloader is used which loads the correct classes where necessary.

Step 7: Request

WebEngine's Lifecycle class implements MiddlewareInterface, a well-known definition for HTTP Server Request Handlers (PSR-15). This interface comes with the the process method whose job is simply to take a PSR-7 Request object and return a PSR-7 Response object.

The Request object is part of a collection of HTTP-related classes which make up the PHP.Gt/Http repository and represents all parts of the incoming HTTP request such as the method (GET, POST, PUT, OPTIONS, etc.), uri and headers.

All information about the page that the user is requesting is represented by this object, and is used internally by different objects in the Lifecycle class to build up the final Response object (also part of the HTTP repository).

Step 8: Router

The concept of routing is the process of taking an incoming request and using its parameters to determine which areas of application logic should receive the request. All requests are processed through the same entrypoint of code so it's the job of the Router class to determine the type of Logic to instantiate along with any extra files that need to be executed. The collection of files/scripts that make up the entire route for a particular request are represented in an Assembly object.

Jump into the code to see how Router uses Assembly objects.

Step 9: Dispatcher

At this point of the lifecycle, all of the objects required for fulfilling the response are ready to be utilised. A Dispatcher object is initialised, which implements the RequestHandlerInterface part of PSR-15. This interface has a single method: handle, which takes a ServerRequestInterface and returns a ResponseInterface.

It is the job of the dispatcher's handle function to find the matching View and Logic files from the Router's Assembly objects and execute the go and do functions in the correct order.

Step 10: Process

The process function is exposed by PSR-15's MiddlewareInterface, which is designed to take a PSR-7 HTTP Request object and convert it into a PSR-7 HTTP Response object.

The Response object that is created is very similar to the HTTP Request object, being a representation of a message that has headers and a body. The Request is build as a representation of the browser's request, while the Response's body is actually used by the process function to stream the page back to the browser.

The final step in the handle function is to stream the contents of the view to the response's buffer, which begins the process of sending the HTTP response back to the user's browser.

Step 11: Finish

The last step is the simplest. After all of the above has been completed, the finish function's only job is to echo the body of the response to the browser.

Clone this wiki locally