PHP Crash Course For RPG Developers
February 23, 2016 Chris Ringer
Suppose your boss tasks you with grabbing and processing some XML from an HTTP request to create a new customer order. If you have a “deer in the headlights” expression on your face, don’t fear, you don’t have to punt this request over the cubicle wall to the web developers. Without weeks or months of training, you can build a PHP script to retrieve the XML data that RPG can parse. What is PHP? PHP is a full-featured scripting language that runs on a web server. Just type your PHP code in a text editor (even Notepad), save it on the server, and point your browser to the script (“program”). The web server side compiles the script on-the-fly, runs it, and sends your script’s response back to the browser. Yes, this “on-the-fly every time” part sounds slow, but the PHP engine may cache commonly-used functions and scripts. Facebook uses PHP and can handle millions of hits per minute. The reality is that PHP is fast. Very fast. So how does PHP help the RPG developer? PHP can be a thin layer between the client (typically a browser or even another PHP script) and your existing proven RPG code on the IBM i, to get your business plugged into the web. In other words, in the MVC architecture, PHP is the controller, the router if you will, between the view (browser) and the model (RPG/DB2). PHP scripts can also be written to run from a command line, outside the context of a web server. This means PHP can perform such tasks as sending emails, generating barcode images, creating PDF documents, and consuming web services in an interactive or batch environment. And in case you didn’t know, the PHP engine is already pre-loaded on your IBM i, waiting for you to explore it. PHP vs. RPG When I first studied PHP back in 2007, I immediately noticed the syntax similarities of PHP and Java. But with PHP, I don’t have to ponder such questions as “Is my code threadsafe?” and “Are strings really immutable?” So my goal here is to help you learn PHP from an RPG perspective. I won’t be regurgitating basic PHP syntax, since hundreds of such articles and tutorials already exist and do a much better job than I ever could. How does PHP compare to ILE RPG? Look at Figure 1. Both can do database I/O. And PHP is supported on many operating systems, but if your focus is the IBM i, that may not be important to you. Furthermore, PHP supports object-oriented programming, but also allows RPG-style coding with procedures, so you do not need to understand OOP to use begin using PHP. However, in terms of built-in functionality, RPG will never match PHP. Even if you consider that about 50 RPG opcodes, such as CHAIN, might be considered function-like, PHP dwarfs RPG in this regard. PHP even has a function that returns the number of functions installed on your system. But then again, the volume of your proven existing RPG logic will always exceed what you begin coding now in PHP. The trick here is to marry the two, allowing PHP to tap into your existing RPG programs and service programs, and vice versa. I confess to not being a PHP guru, probably knowing about 300 functions. With PHP, you have to be careful to not reinvent the wheel and not write a function that already exists natively. To start learning basic PHP syntax, go to w3schools and bounce back here as you examine our sample scripts below. If you want to learn some advanced PHP, navigate to Hacking with PHP. What’s in a Name? Knowing you are a seasoned RPG professional, I will forgo the usual “Hello World” example. Let’s begin with a script file named genDocName.php containing a function named genDocName() that generates and returns a unique document name. A function in PHP is like an RPG subprocedure that allows parameters and optionally returns a value. NOTE: The numbers that begin each line of this example are not part of the PHP script. They are just references for this article. 1 <?php 2 function genDocName($prefix , $suffix = '', $extension = 'xml', $folder='/tmp') { 3 // set file extension and folder 4 $extension = trim($extension) ; 5 if ( $extension == '' ) { 6 $extension = 'xml' ; 7 } 8 $folder = trim($folder) ; 9 if ( $folder == '' ) { 10 $folder = '/tmp' ; 11 } 12 // grab a value from HTTP headers 13 $requestID = '' ; 14 if ( isset($_SERVER['UNIQUE_ID']) ) { // Vnrx4X8AAAEAAAXQEgQAAAAj 15 $requestID = $_SERVER['UNIQUE_ID'] ; 16 } elseif ( isset($_SERVER['REMOTE_PORT']) ) { // 52844 17 $requestID = $_SERVER['REMOTE_PORT'] ; 18 } 19 // build unique doc name and exit when not found on the IFS 20 do { 21 $rand = $requestID . mt_rand() ; 22 $docID = uniqid($rand, true) ; 23 $docName = $folder . '/' . 24 trim($prefix) . '_' . 25 hash('sha1',$docID) . '_' . 26 trim($suffix) . '.' . $extension ; 27 } while ( file_exists($docName) ) ; 28 return $docName ; 29 } 30 ?> Breaking this down line by line: 1 <?php The <?php tag indicates the beginning of the script. 2 function genDocName($prefix , $suffix = '', $extension = 'xml', $folder='/tmp') { The function name is genDocName().The body of the function is between the open brace that ends this line and the closing brace in line 29. Four string parameters are defined. In RPG terms, all are passed by value. You can change a parameter value inside the function, but that value is not passed back to the caller. $suffix, $extension, and $folder are optional parameters. If not passed, they are assigned the default values that follow the equal signs. As in RPG, once an optional parameter is defined, the subsequent parameters must also be defined as optional. However, unlike RPG procedures, PHP scripts can reference unpassed parameters. A similar RPG prototype is: D genDocName... D Pr 500a Varying D iPrefix 100a Varying Value D iSuffix 100a Varying Value Options(*NoPass) D iExtension 20a Varying Value Options(*NoPass) D iFolder 100a Varying Value Options(*NoPass) 3 // set file extension and folder As with RPG, double slashes begin a comment that extends through the remainder of the line. 4 $extension = trim($extension) ; trim() whitespace from the extension parameter. 5 if ( $extension == '' ) { 6 $extension = 'xml' ; 7 } If no document extension value was passed, assign the default value ‘xml’. The closing brace indicates the end of the “if” statement. 8 $folder = trim($folder) ; 9 if ( $folder == '' ) { 10 $folder = '/tmp' ; 11 } This is similar code for setting the folder path for a document. This script assumes you have a /tmp folder defined on your system. 12 // grab a value from HTTP headers 13 $requestID = '' ; In line 13, the $requestID variable is set to a default value, an empty string, in case none of the “if” statements are true. I prefer this method to coding a final else condition because I think it makes the code more readable and maintainable. 14 if ( isset($_SERVER['UNIQUE_ID']) ) { // Vnrx4X8AAAEAAAXQEgQAAAAj 15 $requestID = $_SERVER['UNIQUE_ID'] ; 16 } elseif ( isset($_SERVER['REMOTE_PORT']) ) { // 52844 17 $requestID = $_SERVER['REMOTE_PORT'] ; 18 } Now things are getting interesting. This code grabs some value from the HTTP request to increase the odds of the document name being unique. The isset() function returns true if a variable has both a memory address and a value (i.e., it isn’t null). Similar RPG code is: If (%Addr(parm) <> *NULL ) ; // for an *OMIT parameter If ( Not %NullInd(DB2Field) ) ; // for a nullable table column $_SERVER is a predefined prepopulated array that contains HTTP headers for the current request. I’ve always thought of HTTP headers as the RPG program status data structure (PSDS) and Retrieve Job Attributes (RTVJOBA) of the web. One of the values that may be in that array is a unique request identifier. Some web servers are configured to generate this value, so if this value is available, use it. Otherwise, try to use the port number of the client making the HTTP request. Example values can be seen in the comments in the script above. 19 // build unique doc name and exit when not found on the IFS 20 do { This is the start of a do/while loop, and it will execute at least once. In RPG this is a DOU loop. 21 $rand = $requestID . mt_rand() ; Here the $requestID is concatenated to a random integer. The function mt_rand() returns a random integer, such as 45310420. In PHP, a dot is the string concatenator, whereas RPG uses the plus sign. Perhaps you are expecting a compiler error on this line of code. After all, in RPG we have to use %Char() or %EditC() to convert (“cast”) numbers to strings before concatenating them. PHP casts variables of all types to strings automatically when needed. 22 $docID = uniqid($rand, true) ; The uniqid(”, true) function generates a predominantly unique string of 23 characters based on the current system time in microseconds. (This is not guaranteed to be unique if multiple processes call it at exactly the same time). The “$rand” value is prepended to this “unique” value, resulting in a string value similar to this one:
Vnrx4X8AAAEAAAXQEgQAAAAj453104205654d1f04c5054.09600590
23 $docName = $folder . '/' . Now we build the full document name, beginning with the folder. Remember that the dot is the string concatenator in PHP. Lines 23 to 26 are one statement in this script. 24 trim($prefix) . '_' . Trim the blanks (whitespace) from the document name prefix and append an underscore. 25 hash('sha1',$docID) . '_' . The hash() function creates a message digest, a one-way string, from the $docID using the sha1 algorithm, which returns a 40-byte hexadecimal string. Again, append an underscore onto the end. Now, could I have just used the $docID in line 22 in the document name? Yes, but hash() has advantages. It generates a string that is safe to be used in a file name because: 1) it will never contain characters like /:*?<>; and 2) it will not accidentally generate inappropriate words. 26 trim($suffix) . '.' . $extension ; Append the trimmed suffix, a period, and the extension. 27 } while ( file_exists($docName) ) ; This closing brace completes the body of the do loop. The condition follows. If a document of the generated name does happen to be in that folder, file_exists() returns true and the loop repeats. I would expect this condition to be false and exit the loop on the first pass. 28 return $docName ; Returns a string containing a document name to the caller. The document name will look something like this: /tmp/OrderSubmit_b2268af34d2d4d9deb4dd744a255e7d1c4e8d0f6_Request.xml 29 } The closing brace of the function. 30 > The end of the PHP script. This is optional. In fact, some editors complain if you type it. If the closing tag is omitted, the PHP compiler will assume this when it reaches the end of file. Test the Shiny New Function In order to test this nice little function, we are going to write some “mainline” code in a text file named testGenDocName.php in the same IFS folder. See Figure 2 for the folder structure. 1 <?php 2 require_once 'genDocName.php' ; 3 define('PREFIX','OrderSubmit') ; 4 define('BR','<br />') ; 5 for ($cnt = 1; $cnt <= 10; $cnt++) { 6 $docName = genDocName(PREFIX, 'Request') ; 7 echo $docName, BR ; 8 } 9 echo BR, 'All Done!' ; Breaking this down line by line: 1 <?php Again the script code begins after the <?php tag. 2 require_once 'genDocName.php' ; This copies our function into our mainline script. RPG developers know it better as “/INCLUDE” or “/COPY”: /INCLUDE *LIBL/QCYPSRC,GENDOCNAME 3 define('PREFIX','OrderSubmit') ; 4 define('BR','<br />') ; We define two constants, PREFIX and an HTML br line break. Think of these as D-Spec Const statements: D PREFIX c Const('OrderSubmit') D BR c Const('<br />') 5 for ($cnt = 1; $cnt <= 10; $cnt++) { This is a for loop, counting from 1 to 10. In RPG we code: For $cnt = 1 by 1 to 10 ; 6 $docName = genDocName(PREFIX, 'Request') ; Call the genDocName() function. The returned string is stored in $docName. Notice that I don’t have to predefine the variable $docName in this code. It’s implied to be a string because the function returns a string. This $docName is not the same variable as the one on line 23 of the genDocName() function. When a functions returns, all of its variables no longer exist. 7 echo $docName, BR ; Send the document name and a carriage return/line feed back to the browser (client) as output. echo accepts a list of strings separated by commas. You can also echo integers, floats, and boolean data types, which automatically cast to strings. 8 } The closing brace of the for loop. 9 echo BR, 'All Done!' ; Just a confirmation that it all worked. To check the uniqueness of the $docName returned by genDocName(), I modified the test script slightly to insert the $docName into a DB2 table in a 200,000 count loop. I opened four Chrome browser windows with six tabs each and ran the 24 test scripts simultaneously, which overall inserted 19,400 rows per second. The table had zero duplicates. To run this test, point your browser to this URL: http://YourSys:10088/test/testGenDocName.php The “A-ha!” Moment Hopefully you understand these sample scripts, but you can’t exactly run to your manager and exclaim that they will help your company ship product out the door. Or can you? Study Figure 3 to grasp the high-level flow from the HTTP request to the response. Our goal is to pull the XML from the body of an HTTP request. With only a few more lines of PHP code, we can complete this task. Our final script file is named CreateSalesOrder.php. A given client will post the HTTP request to this script, located in the same test folder as in Figure 2. Here is the script. 1 <?php 2 require_once 'genDocName.php' ; 3 $docName = genDocName('OrderSubmit','Request') ; 4 $rawPostXmlData = file_get_contents('php://input') ; 5 file_put_contents($docName, $rawPostXmlData) ; 6 $connArray = array('i5_naming'=>DB2_I5_NAMING_ON, // Use *LIBL 7 'i5_commit'=>DB2_I5_TXN_NO_COMMIT) ; // SQL Commitment *NONE 8 $conn = db2_connect('*LOCAL','','', $connArray) ; // User QTMHHTTP 9 // Pseudo-code here to call RPG passing the document name 10 // CALL YourStoredProc($docName, $OutResponse) ; 11 echo $OutResponse ; Breaking this down line by line: 1 <?php 2 require_once 'genDocName.php' ; 3 $docName = genDocName('OrderSubmit','Request') ; This code is the same as before to call the genDocName() function. 4 $rawPostXmlData = file_get_contents('php://input') ; This retrieves the body of the HTTP request, XML in our case (see Figure 3), and stores that string in variable $rawPostXmlData. How easy is that? 5 file_put_contents($docName, $rawPostXmlData) ; The XML string is saved into a new document in the /tmp folder of the IFS. Again, how easy is that? 6 $connArray = array('i5_naming'=>DB2_I5_NAMING_ON, // Use *LIBL 7 'i5_commit'=>DB2_I5_TXN_NO_COMMIT) ; // SQL Commitment *NONE 8 $conn = db2_connect('*LOCAL','','', $connArray) ; // User QTMHHTTP Connect to the local IBM i. When the second parameter of db2_connect is an empty string, the connection user profile is QTMHHTTP and the third parameter, the password, is ignored. Please conform to your company’s security policy here. 9 // Pseudo-code here to call RPG passing the document name 10 // CALL YourStoredProc($docName, $OutResponse) ; Your RPG program, probably defined as an external SQL Stored Procedure, is called to parse the IFS XML, passing in the name of the new document and passing back a response string as an OUT parameter. See example 2 here. If you will be promoting this RPG program through various test libraries on a development box, you may want to execute the SQL “Create Procedure” statement with option “External Name MYPGM” instead of “External Name MYLIB/MYPGM” so the *PGM object can be found via the library list. Otherwise, you may need to create the stored procedure in each test library. 11 echo $OutResponse ; The response string is sent back to the caller as an HTTP response. And there you have it. Using a few dozen lines of PHP code, your RPG is connected to the web. Hopefully this article has demonstrated the power of PHP and piqued your interest to learn more! In my next article, I plan to show you how to install the PHP engine and Apache web server on your laptop, including an IDE editor and debugger, so you can wow your friends at family gatherings. Until then if you want to kick the tires yourself, websites like this allow you enter and execute PHP 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.”
|