Guru: The PHP Path To Victory, Part 1
March 6, 2017 Chris Ringer
If you read my two previous PHP articles, you may be tempted to make the leap to use PHP on your IBM i. But you may also have reservations because how in the world will you technically support those apps post implementation?
This article discusses the path a PHP request takes as it travels through various subsystems up to your RPG code and how to do basic troubleshooting if something goes wrong.
Tag Team Match
Often greatness is achieved with the help of someone else. Michael Jordan had Scottie Pippen. Babe Ruth had Lou Gehrig. And Abbott had Costello. In terms of PHP, ZENDSVR6 has QSQSRVR. Let me explain.
To be useful, PHP (or any web server language) must access the backend databases with SQL statements, but PHP doesn’t natively know DB2 so it passes that task to another job.
This story contains code, which you can download here.
Type WRKACTJOB SBS(QHTTPSVR) JOB(ZENDSVR6) to list the QHTTPSVR subsystem ZENDSVR6 jobs. Then type 5 next to a PHP CGI job (Figure 1). Display the Call Stack with option 11, cursor down to the php-cgi.bin program in the call stack, and press F22 (Figure 2). This program is the PHP binary in the IFS root system waiting for an incoming HTTP request.
When this job determines that the requested PHP script uses SQL, it passes that task to a QSQSRVR (SQL server) job in subsystem QSYSWRK. Type WRKACTJOB SBS(QSYSWRK) JOB(QSQSRVR) to list those SQL jobs. Look for a job with current user QTMHHTTP, if you have one (Figure 3).
Type 5 next to that job and display the job log. A message there references the upstream ZENDSVR6 job:
SERVER MODE CONNECTING JOB IS 449661/QTMHHTTP/ZENDSVR6.
Similarly, the job log of that ZENDSVR6 job references the downstream QSQSRVR job that is processing the SQL with message ID SQL7908:
Job 449779/QUSER/QSQSRVR used for SQL server mode processing.
For a given HTTP PHP request, these messages inform the developer that these two jobs were linked.
The Triple Play
Let’s look at a third subsystem, the ZENDSVR6 subsystem. Type WRKACTJOB SBS(ZENDSVR6) (Figure 4). This subsystem should also be active for PHP requests to be processed.
The Zend Server menu controls the QHTTPSVR subsystem ZENDSVR6 jobs (Figure 1), and also the ZENDSVR6 subsystem and its various jobs (Figure 4). Type GO ZENDSVR6/ZSMENU, then use the “Service Management” option 5 to navigate to the ZSVMENU menu (Figure 5).
Options 1 and 2 control the ZENDSVR6 subsystem (Figure 4), whereas option 3 displays Figure 4. Options 5, 6, and 7 control the QHTTPSVR subsystem ZENDSVR6 jobs (Figure 1). The takeaway here is that the ZENDSVR6 subsystem and the Apache server instances must be active to process incoming HTTP PHP requests.
Options 9, 10, and 11 respectively start and stop the ZSDAEMON, ZSDEPLMNG and ZSMONMNG jobs in the ZENDSVR6 subsystem (Figure 4). Our sample PHP script for this article does not require these jobs to be running.
Running The Fast Break
Now let’s run a PHP script that connects to the IBM i and calls a backend RPG stored procedure that returns a result set plus some output parameters and echoes those values back to the browser.
Our PHP script is named cust600.php and is stored on the IFS in folder /www/zendsvr6/htdocs. This is the default document root folder where Apache finds PHP scripts. I won’t go into detail on this PHP script. It consists of a few functions to get a DB2 connection, calls an RPG stored procedure, and then loops through the result set to list customer addresses in an HTML table.
Our RPG program on the backend is named CUST600SP and is wrapped in an SQL stored procedure definition. This SQL statement creates our external stored procedure, also named CUST600SP. The four parameter definitions must match those in both the PHP script and the RPG program.
CREATE PROCEDURE YOURLIB/CUST600SP (INOUT ioPgmSts CHAR(10), IN iSortSeq CHAR(10), OUT oPgmCount INTEGER, OUT oJobInfo CHAR(100)) SPECIFIC CUST600SP RESULT SETS 1 READS SQL DATA LANGUAGE RPGLE NOT DETERMINISTIC EXTERNAL NAME CUST600SP /*Finds *PGM in *LIBL */ PARAMETER STYLE GENERAL
Using the downloadable code, compile the RPG program into a library somewhere in your standard library list, copy the PHP script to the IFS document root folder, and create the SQL stored procedure (replacing “YOURLIB” with the name of a library in your library list). Check that the QHTTPSVR apache instances and the ZENDSVR6 subsystem are active. The browser URL to run the PHP script is http://YourSystem.com:10080/cust600.php?sort=zip, where the sort value can be name or state or zip. The default sort order is the customer number.
Look at the sample output in Figure 6 below.
The first line shows the current time and the sort order.
The second line shows the RPG counter, program name, current user, and the QSQSRVR job for the PHP DB2 connection. The HTML table is self-explanatory. The random number column is just there to show you that the browser output is indeed being refreshed when the PHP script is rerun since the customer data is static until you change the sort order in the URL.
You can watch the file I/O count increase in the QSQSRVR job too via WRKJOB option 14 “Open Files” then “F11=I/O Details” each time the browser is refreshed (Figure 7). In this case, the RPG counter is 3 and the customer table has 12 rows, equaling 36 totals rows selected so far.
The RPG program stays active in this QSQSRVR job because 1) the db2_pconnect() function establishes a persistent connection for the connecting user profile, QTMHHTTP in our case; and 2) the RPG program exits with *INLR=*OFF when the RPG counter is less than 10. You can watch the RPG counter increase in the browser output as the browser is refreshed. Just know that a given QSQSRVR job may service many IBM i jobs during the day, not just a single QHTTPSVR ZENDSVR6 job. It is not always a one-to-one relationship. If you open a second tab in the browser for the same URL and alternate refreshing each tab, the RPG counter in a tab may increase by 2 if both tabs get linked to the same QSQSRVR job (the first tab counter progression is 1,3,5,7,9 and second tab is 2,4,6,8,10).
When the RPG counter reaches 10, *INLR is set *ON and status parameter value END is passed back to the PHP script, signaling the db2_pclose() function to be called.
if ( rtrim($oStatus) == 'END' ) { db2_pclose($conn) ; }
This close resets the QSQSRVR prestart job to a state when the job was first created. The next time the PHP script is run, the RPG counter begins at 1 again. Even if db2_pclose() were not called, I still like to have the RPG programs periodically shut down in case a newer version is installed higher up in the library list during production hours.
You can also put the QSQSRVR job listed in the browser output in debug with STRSRVJOB/STRDBG or a Service Entry Point. Again, a rerun PHP script is not guaranteed to connect to the same backend QSQSRVR job. If our two-tabs scenario above did connect to the same QSQSRVR job, then this is easy to prove. Put that job in debug and set a breakpoint in the RPG code. Refresh the first tab and let execution stop at the breakpoint. Refresh the second tab and IBM i will detect that the job is busy and route that request to another QSQSRVR job, clearly evident in the changed job name in the browser output.
The alternative to db2_pconnect() is db2_connect(), which is similar except that an implicit db2_close() occurs every time the PHP script ends (every request). The corresponding QSQSRVR job is always reset, and that could mean overall slower performance.
Gearing Up For The Second Half
So how did that go? Even if you had zero problems, we IT developers still like to have a map of the potential road blocks ahead. Part 2 of this article will lay out different types of errors that our PHP request could encounter during its round trip between the browser and our RPG code.
Chris Ringer has been coding in RPG since 1989, focusing primarily on order fulfillment, pharmaceutical, and manufacturing environments. In his spare time he enjoys running and doing triathlons. “I’m not fast, usually placing in the middle of the pack, but we’re just as competitive back there.”
RELATED STORIES
Excellent article, Chris, to show the key parts of PHP and DB2 working together.