RPG to the Web: The PHP Way

Article ID: 21225
Take direct programming control of your web application

Click here to download the code bundle.
To report code errors, email SystemiNetwork.com
This article demonstrates a few simple PHP programming techniques for providing a web interface to an existing order-entry application. You'll learn how to deploy traditional non-object-oriented PHP scripts that interact with the example Acme order-entry application RPG stored procedures (see "RPG and the Web: Technologies to Get There," page ProVIP 9). I show no code biases toward any particular PHP product or PHP framework.

The simple learning curve of PHP will quickly put you in direct programming control of your PHP web application. Imagine writing simple PHP code instead of exhaustive learning, customizing, and deploying bulky OO frameworks for simple web data applications. If you've been searching for an easy-to-learn web interface language on i5/OS, perhaps this information is for you.

Web versus 5250 Interactive

The principal idea of web programming with Apache/PHP is to allow many diverse, browser-based applications to use a small pool of prestarted server jobs (Apache worker jobs), giving each browser client the appearance of sole ownership of the machine while avoiding stress to machine resources.

Apache-based PHP application programming is therefore stateless; that is, after each subsecond browser client uses the Apache/PHP job, all programming attributes (RPG variables, PHP variables, open files) are released to allow the next browser client to use the same job for a completely different purpose.

The small pool of Apache worker jobs is generally assigned to each subsecond web client request (browser click) in a random "who's not busy?" fashion. Therefore, our application is not guaranteed to arrive back at the same Apache/PHP job on each screen transition, so we should not leave data and machine resources hanging around in the Apache/PHP job that may adversely affect the next unrelated application reusing the job.

On the other hand, in a traditional RPG interactive (5250) application, the entire user session is held hostage to a single job/single client, therefore keeping full state (RPG variables, open files) while the user is working in the application. Traditional GUI applications on Windows, Linux, AIX, and standalone Java are also full-state, single client/job/threaded "hostage" designs like 5250 sessions. In the network, most client/server applications also deploy a single client/job/threaded "hostage" design, simply inserting communications protocol (socket) transfers of data between the screen and the business logic server. Web programming is different.

So how do we use Apache/PHP as a web interface to an existing RPG order-entry application? PHP has a number of i5/OS popular extensions, including ibm_db2 and the i5/OS Toolkit, to call RPG back-end business logic. The Acme order-entry application provided stateless stored procedures to call from our PHP front end, so ibm_db2 is a good choice for this demonstration.

Tip: Designing a web interface as a GUI front end "same as" your interactive application is a common reason that a web project fails for beginning developers. The Acme order-entry application provides a good example of a stateless stored procedure interface to pattern your web application after.

Green-Screen-like Sessions

The RPG version of the Acme order-entry application uses RPG variables, open files, and subfiles to hold application state between each screen transition. As we learned previously, Apache/PHP won't guarantee our application arrives back at the same job between each screen transition, so we need to save/restore the PHP application state between screens.

PHP has a language feature called sessions that enables us to save and restore our PHP application data between browser/Apache transitions. To remember values between calls, we'll create a data structure that's stored in the session. Each time the PHP script ends, it saves the data in the session. And each time it's restarted because the user clicked something in the browser, we'll restore it from the session.

The PHP Acme application uses session_name() for the unique file name for each browser client session IFS file that is created in session_path("/tmp"). Each time our PHP Acme application leaves the Apache server, save_session() will be called to write global data into the IFS file. And when the browser asks for a page in the application, session_restore() will read the data from the file.

PHP session_write_close() collects any data in global array $_SESSION and writes it to our IFS file. Conversely, PHP session_start() will restore the $_SESSION array. PHP implode() creates a flat line of text from the values of global $_DS array separated by semicolons to store in the session file. We save the associative name of $_DS using array_keys() so we can call explode() and array_combine() to re-create the global $_DS array (Figure 1).

The global $_DS array used in our PHP Acme application is an associative array. It's much like a keyed lookup in a database file or an array search with the %LOOKUP built-in function in RPG, and I think it's one of the better features of PHP. If you master PHP associative arrays, you'll be able to do most anything with PHP. You can think of a dictionary as an example of an associative array (Figure 2).

The PHP Acme application global $_DS array contains all save/restore "state" data for the application. The array associative index names have been selected to closely match the RPG version of the Acme application (RPG DS data) so you can compare the data structures side by side (Figure 3).

Tip: You can improve performance by using a database file keyed by session number (or customer ID) and storing the session data in a big VARCHAR field. If you choose ibm_db2 to manage the database file, remember to use persistent connections (db2_pconnect) to avoid open/close/process overhead on i5/OS.

PHP header() for Program Flow

Like the Acme RPG program, the PHP version will use the same select/when logic for program flow control, except that it's called "switch/case" in PHP. The switch/case program flow logic is contained in PHP file index.php. I selected the file name index.php because it is the default page for the Zend Core Apache configuration; http://i5:89/acme will deliver our default index.php page.

Each time the browser contacts index.php, we will check the $_GET['action'] variable to see which step we should perform. PHP provides the $_GET global array to allow our script to see the data parameters passed along in the URL line http://i5:89/Company/index.php?action="editHeader" (Figure 4).

The loop for the PHP Acme application switch/case is much wider than a self-contained RPG program; as a matter of fact, the actual loop is all the way back to the browser for each step. PHP provides header("Location: $go") to easily send HTTP header information back to the browser. The Location: HTTP header tells the browser to go to the supplied URL.

For example, if I sent "Location: http://www.systeminetwork.com", it would send the browser to view the System iNetwork web page. In this case, I'm using Location: to direct the browser to make a new request to my PHP code, passing a different parameter. That mechanism lets me move on to the next step of my order-entry application. I decided to provide a function goto_browser() for my header() calls so I could remember the true purpose of header() and do any model/session cleanup before I left the server (Figure 5).

Tip: I could potentially use this "Location: URL" technique as a poor-man's load balancing. I could run different parts of my application on different servers and/or mix different web programming languages!

A Common Storage Model Interface

Whenever I write the PHP database communication for my code, I try to code the same functions with the same parameters. That way, if I want to switch to a different database, or simply hard-code values for testing, all I have to do is replace it with another PHP module that supplies the same procedures (e.g., model_db2.php, model_mysql.php). The PHP Acme application deploys this technique in module control.php to call database-specific model.php(s), as Figure 6 shows.

The functions in control.php don't take parameters because all of the data is held in a global $_DS array. Normally, all variables inside a PHP function are scoped local to the function and cannot be seen outside of the function brackets. To make sure the global $_DS array is used, we must declare it with the "global" keyword in each function. The PHP Acme application global array $_DS session data can save and restore to the session file easily as data is added in each called function.

PHP Views for HTML

I slightly modified the original Acme HTML to include a common PHP coding idiom to redisplay screen data input fields on each screen transition. The small PHP script additions allow the HTML form to redisplay any customer data-entry errors or added items in the order between trips from the server to the browser.

PHP provides the global array $_POST to allow our program to see all the named input/output fields in the HTML form. The associative index name of any $_POST array variable will be the same as the HTML element (name="shipname"), as in Figure 7.

In order items HTML, we need to repeat the order item form element many times, so I created a PHP variable $template for the basic HTML format and used simple string substitution to fill in the correct data. The PHP code searches for &1 and replaces it with the server name, searches for &2 and replaces it with the "msg" variable, and so forth (Figure 8). After we modify the Acme template HTML, we simply include it in each of the view PHP modules (Figure 9).

The premise of the Acme experiment is to eliminate HTML glitz and glamor and write about PHP coding techniques. However, I believe the Acme order-entry HTML design has a few bad practices you should avoid in production code.

First, don't hard-code HTML attributes such as color, font, align, and background. This is the job of style sheets. I recommend Cascading Style Sheets (CSS). CSS lets you change HTML business décor to match customer desire without changing the HTML, even at an individual user level (Sally likes blue. Lisa likes green. Web programmers don't count).

Second, the HTML design for order items form(s) is a human-factors nightmare. (Warning: screen use may result in anxiety, heart attack, or stroke.) I recommend a single HTML form that resembles a spreadsheet with buttons for OK, Cancel, More, and Help that apply to the entire form.

Apache/PHP/ibm_db2 and the Library List

I have selected ibm_db2 to act as the model for the Acme project because it consistently performs better than the other PHP extensions choices. Before we continue with the Acme application, we need to talk about the library list used by traditional i5/OS green-screen applications and how to manage this issue when using Apache/PHP/ibm_db2.

There is no direct ibm_db2 API to set the library list. In fact, changing the library list in an already-running Apache/PHP process is unnatural at best (such as changing directory paths on other platforms), and we can never truly understand the impact of a possible leftover library list change on the next unrelated PHP application that reuses the Apache process.

Many existing i5/OS applications are dependent on the library list, so if we can't change the existing code (RPG), we must bend the Apache stateless rules to make allowances for i5/OS legacy code. Fortunately, i5/OS offers a number of ways to work with library lists that we can use for our Apache/PHP/ibm_db2 front end to the Acme application (Figure 10).

I tested all of the library list options successfully with Acme, but I decided to use Option 3: Change the library list with a stored procedure. The PHP Acme application help.php module has global variable $INSTALL="yes" to create the stored procedure "cmdcall" interface to QSYS/QCMDEXC in the Acme legacy library (specified in config.php) when you click the Help button on the first order screen.

The PHP Acme application calls stored procedure "cmdcall" with "addlible acmelib" before using the db2 stored procedure model to avoid errors in the existing Acme application. Conversely, the PHP Acme application calls stored procedure "cmdcall" with "rmvlible acmelib" to undo the state change in the Apache job on the way out of the server.

To activate the library list, the PHP Acme app uses the system naming option on the db2 connection (array("i5_naming"=>DB2_I5_NAMING_ON)) to avoid ibm_db2 default SQL naming, which will not work well with our existing library list-sensitive Acme code.

Note: If the Help button cannot create the "cmdcall" stored procedure, you will have to manually change GRTOBJAUT *RWX to *PUBLIC and/or *NOBODY to the Acme legacy library and retry the Help button.

db2_pconnect or db2_connect?

To achieve acceptable ibm_db2 performance for any site experiencing heavy browser demand (many hits per second), you will have to use db2_pconnect(). The "p" in db2_pconnect() stands for persistent connection, which simply means this connection will never close as long as the Apache site is running (no matter which PHP application uses the persistent connection).

Conversely, db2_connect() will open a connection each time a PHP application wants to use the database and will close the connection at exit. Therefore, db2_connect puts a tremendous strain on i5 resources with database opens/closes at subsecond request demand speed from the browser clients (ouch), while subsequent demands to db2_pconnect bypasses all db2 connection work.

You cannot mix db2_connect and db2_pconnect under the same user profile connection, as the db2_pconnect will eventually consume all the connections and hold them hostage for the life of the Apache site. The persistent feature of ibm_db2 is absolutely required for performance, but it does come with the price of knowing how to plan your ibm_db2 site strategy to avoid having the whole site become unresponsive to db2 requests.

As you may have guessed, db2_pconnect() is a stateful connection; therefore, it violates the Apache stateless programming rules. DB2_I5_NAMING_ON expects all SQL statements to have library/file syntax (native/system naming), and DB2_I5_NAMING_OFF expects all SQL statements to have library.name (SQL naming). Due to the rules of CLI connections, we can't switch between the two modes while a connection is active (i.e., db2_pconnect is always active).

The PHP Acme application is configured to take over the default profile NOBODY connection (config.php), so this means that many of your other test scripts (or other production db2 code) that try to mix db2_pconnect/connect("", "", "") and db2_pconnect("", "", "", array("i5_naming"=>DB2_I5_NAMING_ON)) will fail. (Or if your other PHP application already holds hostage the persistent connection, the Acme application will fail.)

The short answer to this problem is to simply choose different profiles for each general class of stateful ibm_db2 persistent connection: one for DB2_I5_NAMING_ON and another for DB2_I5_NAMING_OFF. I recommend that you have at least two *USER user profiles that cannot sign on to the System i (such as NOBODY and NOBODYP) to run both styles of db2_pconnect for PHP applications, and then remember to connect with the matching profile.

Note: Any Apache stateless violation deal with the devil extracts a price you must pay (change the RPG code is a better solution from Apache's point of view). So if you must mix SQL and system/native naming and library lists, carefully choose your site's ibm_db2 persistent connection strategy (db2_pconnect is a powerful but dangerous tool in your web handyman belt).

PHP Model with db2 Stored Procedures

After we debug the session, control, and view code for our PHP application by using generic PHP global variables $_GET, $_DS, $_SESSION, and $_POST, adding the database model becomes a much easier task. Following the pattern of PHP global variables, we will use a global model array $_M to provide the database, user ID, and password for our interactions with the database model (config.php). If you want to allow the application administrator to change the default values of a PHP application, using an edit configuration such as config.php is a common technique.

All of the db2 model interactions in the Acme application are similar stored procedures. Each of the db2 model stored procedures have input parameters and output a result set. Therefore, we can use common PHP functions to call stored procedures and retrieve results. Using simple PHP string concatenation ($p.= "parm1,parm2"), we can build stored procedure calls for db2_exec(), thereby having much less ibm_db2-specific code spread throughout the model (Figure 11).

Using a common code technique for db2_exec() enables us to write simpler call code to the stored procedures by specifying the type of data in an array ($types) and the array of values ($values), as in Figure 12.

We also provide a simple error routine to remember the last error by keeping a global $_ERROR variable in the model. When our view programs ask control ORDER_error() for the last error, model_ORDER_error() will simply hand back the text in $_ERROR['last'], as in Figure 13.

Tip: I generally do not use db2_prepare(), db2_bind_param(), and db2_execute() because PHP has outstanding string manipulation capabilities for input-only parameter substitution. Also, I am an advocate of using results sets for stored procedure output (Acme application) because there are fewer complex i5/OS PHP anomalies dealing with EBCDIC databases (future db2 PTFs not withstanding). Finally, the db_exec() API is generally faster for most tasks in the stateless Apache/PHP job.

Take Control with PHP

I believe one of the overlooked elements of choosing a web language is the fun you have programming. For me, PHP is fun. I find each page I write gives immediate visual feedback to my browser. The debug tool Zend Studio is easy to use once you master the remote setup (although I rarely need the debug tools at all for PHP programs).

I am a big fan of the traditional non-object-oriented PHP scripting program model with its low learning curve, rapid development, and good performance. There is a PHP extension for nearly any web task (.xml, .xslt, .db2, .mysql, .rest, .soap). Most importantly, I enjoy being in complete intellectual control of my PHP code without undue reliance on complex frameworks for simple web tasks.

PHP is secure and widely deployed, but it's only as secure as the web programmer experience. So take the time to understand your PHP code, and then correctly set access permissions to web IFS /www directory structures and library objects. Many PHP security articles are available; just google and read.

If you've had less-than-satisfying experiences with other web languages, I encourage you to download the Acme application and try the PHP interface for yourself. (See "PHP Acme Installation Notes," page ProVIP 22.) I am confident you will find better ways of doing things over my Acme order-entry example. PHP puts you firmly in control of your web programming tasks.

Tony Cairns is a senior programmer for IBM in Rochester, Minnesota. Tony’s career includes many positions ranging from management to technical. He is currently a member of the i5/OS PASE team.


PHP Acme Installation Notes

You need the following products installed on your i5/OS to run the PHP Acme application:

PASE (Option 33)

Zend Core for i5/OS (zend.com)

  • Please check the Zend Core README for additional products.

RPG version of the Acme application

  • Default upload.bat library is RPGWEB, but the stored procedures are hard-coded to ISNMAG/ORDERR4. You need to change this to get the stored procedures to work.
  • Depending on how you did your installation and what profile you are using for PHP to call your stored procedures, you will also have to grant authority to the Acme library, various database files, stored procedures, and SRVPGM (*PUBLIC or *NOBODY *RWX).

PHP Acme source

  • Go to systeminetwork.com/code.
  • Download and install the code in IFS directory /www/zendcode/htdocs/acme.
  • Change the owner to NOBODY.
  • Give NOBODY *RWX access to use zzedit1.php browser/editor.
  • To help you remember customer numbers, order numbers, and item information on the Acme screens, I added a Help pop-up button. (Your browser will need to allow pop-ups for this application.) The Help button also creates stored procedure "cmdcall" and recommends authority changes to the legacy Acme library.

Best performance practices for an Apache/PHP site

  • Keep Apache jobs up and running (default httpd.conf settings).
  • Avoid application-induced process spawn (use db2_pconnect() and not db2_connect(), and avoid the PHP shell_exec("command")).
  • Keep the application transitions stateless (limit globals, locks, transactions, open files).
  • Fully qualify PHP include names (include_once("/www/../myinc.php")).

— T.C.

ProVIP Sponsors

ProVIP Sponsors