Practical PHP Patterns: Transaction Script
Join the DZone community and get the full member experience.Join For Free
This is the first article of the Practical PHP Patterns series continuation. The first part of this series dealt with the set of patterns presented in the original Design Patterns book by the Gang of Four. This collection of articles will focus instead on the Patterns of Enterprise Application Architecture, which are specific solutions for data organization, infrastructure management and other interesting tasks. The case study environment, as always, is a PHP web application where these patterns may be implemented or leveraged as part of a library like an ORM. Since most of them cannot work alone, I will favor real code samples instead of fabricated ones.
Transaction Script is the fancy name for the simplest approach to server-side development: a set of scripts executed on the server, each answering a particular kind of HTTP request. In the web applications field, HTTP requests are always the basic events produced from any client, and each of them is one of the basic transactions which the name of this pattern refers to. Transaction Scripts have nothing to do with database consistency.
The idea behind a Transactions Script is to encapsulate all the business logic involved in the interaction, acting as a gateway between the client and resources like filesystems, databases and external services. Duplicated code is usually refactored in a base abstract class or in small helper ones at this level of design.
Given that PHP is our language of choice, writing a Transaction Script is very simple. In fact, most of the PHP applications realized before url rewriting took place were an enormous set of .php files organized in a hierarchy of folders. Each of these files is a full Transaction Script, and is an entry point for HTTP requests which are routed to it by the webserver, an external infrastructure like Apache or Microsoft IIS.
This simple approach is what made PHP so popular, and so easy to deploy in a shared hosting environment. From the point of view of the HTTP server, the process is essentialy the same of finding a static file in a directory and transmitting it back to the client that made the request. The only difference is that, before sending the response, the file is passed to the PHP interpreter, which has the possibility to execute code and produce a dynamic output.
In this paradigm, the Transaction Script's contract is implicitly defined:
- a set of input variables contained in the request or inferred from it: $_GET and $_POST, $_COOKIE which derives from cookie-specific HTTP headers, and $_FILES whose content is generated for some POST requests.
- an output which mainly consists in what is printed to the standard output, plus a set of HTTP headers (which again comprehend cookies, and redirects). The response is in a format intellegible to the user-agent, such as html, json, xml and so on.
- state variations which may affect $_SESSION, a database on the back-end, or other external services of any kind.
Of course the same approach can be implemented in an object-oriented point of view and single entry point:
public function setState(PDO $db, array $session);
* @return string
public function request($get, $post);
This modelization makes implicit concepts such $_GET and $_POST explicit, and benefits testability by exposing an Api which could be the subject of programmatic, automated acceptance tests. If you are thinking "this is ugly," you're right. The level of abstraction is low and some features such as HTTP headers management can't be stubbed out easily or natively. It is though a step forward toward a comprehension of the dependencies of a TransactionScript.
Of course, by the time you model Transaction Scripts in PHP as classes, you would have to write an upper layer to route the HTTP requests to them, which basically instances the right class and call its run() method, passing the standard parameters. It may also wrap the standard header() and setcookie() function in an object that can be stubbed out in tests.
There are also other major issues to the naive use of Transaction Scripts in medium to large applications. The reutilization of business logic to prevent its duplication is maybe the biggest one. Business logic is the added value of an application: for example forces consistency in database rows, and propagate events between different tables. This kind of code gets duplicated in different Transaction Scripts as they are monolitic, independent objects whose methods aren't thought to be used at a finer level than the request/response interaction. This means that if we want to employ a Transaction Script, being it in the form of a .php file or a class, we have to prepare an HTTP request and gather a response:
<?php // we are forced to mock a request in production code: $_GET['id'] = $someRow['id]; $_GET['orderField'] = 'name'; include 'list.php'; // or even capturing the response: ob_start(); $_GET['id'] = $someRow['id]; $_GET['orderField'] = 'name'; include 'list.php'; $content = ob_get_content(); ob_end_clean();
Since a Transaction Script is not an HTTP client (unless it wants to, and it shouldn't be forced to become one), this approach quickly gets messy. Transaction Scripts show their limits here and different architectures have been proposed in the last years to solve these problems.
The most diffused version of Transaction Scripts is the Controller layer implementation in the MVC components of PHP frameworks (Action Controllers in Zend Framework, Actions in Symfony). These classes respond to different types of HTTP request, as the pattern requires, and some standard parameters are used to decide which one should handle the request.
There are two key facts that render these implementations interesting. The first is they standardize and factor out (at least for applications in the same framework) the management layer of Transaction Scripts, giving you an explicit class contract to adhere to. Common features implemented by the frameworks are for example the mapping of URLs to controllers, the generation of events for subscribers during the sequence of calls to it, and the wrapping of basic PHP infrastructure in an object-oriented Api.
The second advantage is that in the vision of modern frameworks, Transaction Script retain the same semantic from a black box view, but are not implemented as standalone scripts. They are a declarative layer over a rich object model, or over a database, or even over some kind service, which is the subject of acceptance tests. The business logic kept in them is request-specific, and deals with problems like the request comprehension via the analyzation of its parameters. Meanwhile, a lower layer factors out common business logic that can be reused throughout different Transaction Scripts. The design of this lower layer is not banal, and in PHP it is currently evolving from patterns like Table Data Gateway and Active Record to a richer and more flexible Domain Model.
Opinions expressed by DZone contributors are their own.