Getting the Message, Part 2
October 21, 2009 Paul Tuohy
In Part 1 of this series, we saw the definition of a number of message routines (clearMessages(), addMessage(), messageCount(), and getMessage()). In this article, we will see how the routines may be used in both an RPG and a PHP environment. We will also look at another couple of message routines that may be useful. Remember, a library containing the code used in these articles may be downloaded here. A Test Procedure Figure 1 shows a small subprocedure (fillMessages()) used to demonstrate that messages may be added/stored at any level in the job. The subprocedure simply adds a filler message the number of times requested on the passed parameter. p fillMessages B Export D PI D timesToSend 10i 0 Const D i s 10i 0 /free for i = 1 to timesToSend; addMessage(APP_FILLER : 'FILL' : %char(i)); endFor; /end-Free p E Figure 1: Test procedure fillMessages(). In RPG Figure 2 shows an RPG program used to demonstrate the message routines. The program performs the following:
/Copy QCpySrc,StdHSpec // To create the program... // Current library set to MESSAGES // CRTBNDRPG PGM(SHOWMSGS) /Copy QCpySrc,BaseInfo D message DS LikeDS(Def_MsgFormat) D i s 10i 0 /free clearMessages(); addMessage(ERR_NOTFOUND : 'TEST1'); addMessage(ERR_CHANGED : 'TEST2'); fillMessages(3); for i = 1 to messageCount(); getMessage(i : message); dsply %subst(message.msgText :1 :40); endFor; *InLR = *on; /end-Free Figure 2: Program SHOWMSGS demonstrates the use of message procedures in RPG. The expected results from SHOWMSGS are shown in Figure 3: DSPLY An expected record was not found for upd DSPLY Record already altered. Update/Delete ig DSPLY This is filler message 1 DSPLY This is filler message 2 DSPLY This is filler message 3 Figure 3: Result of calling SHOWMSGS. Of course, this program simply shows the format of the calls. The real key is what the program wants to do with the messages it retrieves. If this were a green-screen program, the returned messages could be sent to a message subfile. If it were a CGIDEV2 program, the returned messages might be used to populate message information on a Web page. For example, when I am using CGIDEV2, I identify errors on a Web page using two divisions/variables. ERRTEXT contains the message text to be display and ERRVARS (a non-display division on the page) contains the names of the fields in error. A Javascript routine that runs on page load uses the contents of ERRVARS to highlight the fields in error. Figure 4 shows the setCGIMessages() routine used to set the errors on a Web page. P setCGIMessages B Export D PI D i s 10i 0 D msgFormat DS LikeDs(Def_MsgFormat) D errVars s 32767a Varying D errText s 32767a Varying /free errVars = ' '; errText = ' '; if messageCount() > 0; for i = 1 to messageCount(); getMessage(i:msgFormat); if (i > 1); errVars = errVars + '%%'; errText = errText + '<br />'; endIf; errVars = errVars + %Trim(msgFormat.ForField); errText = errText + %Trim(msgFormat.MsgText); endFor; endIf; updHTMLvar( 'ErrVars': errVars); updHTMLvar( 'ErrText': errText); /end-Free P E Figure 4: Setting errors with CGIDEV2. In PHP Let me start by saying that I am still finding my way with PHP, so those of you who know better, please don’t be too harsh on my attempts. Since I am running this on my i, of course I am using ZendCore to do all the hard work for me. Whenever one decides to interface between two technologies, there is always a little bit of work to be done, and PHP is no exception. The key point to remember is that no changes are made to the underlying subprocedures. The first issue with the PHP routines is that they have issues calling subprocedures that don’t accept parameters. For that reason, I wrote a wrapper routine (php_clearMessages()), shown in Figure 5, that accepts a dummy parameter and issues a call to the original clearMessages() subprocedure. P php_clearMessages... P B Export D PI D dummy 1a /free clearMessages(); /end-Free P E Figure 5: Wrapper subprocedure for call to clearMessages() from PHP. The next step was to write PHP functions that would issue calls to the corresponding subprocedures in the UTILITY service program. Figure 6 shows the source of func_messages.php. Each of these functions is simply a PHP function that issues a call to the corresponding subprocedure in the UTILITY service program. In other words, these are PHP wrappers to call the RPG subprocedures. <?php define('APP_ERR_NOTFOUND','ALL9001'); define('APP_ERR_CHANGED','ALL9002'); define('APP_ERR_DUPLICATE','ALL9003'); define('APP_ERR_CONSTRAINT','ALL9004'); define('APP_ERR_TRIGGER','ALL9005'); define('APP_ERR_UNKNOWN','ALL9006'); define('APP_ERR_NOT_NUMBER','ALL9007'); define('APP_ERR_NOT_DATE','ALL9008'); function clearMessages($conn) { $desc = array (array ("name" => "dummy", "io" => I5_INOUT, "type" => I5_TYPE_CHAR, "length" => "1" ) ); $prog1 = i5_program_prepare ( "MESSAGES/UTILITY(php_clearMessages)", $desc, $conn ); if (! $prog1) { die ( "Prepare for clearMessages() did not work" ); } $parameter = array ("dummy" => " " ); $parmOut = array ("dummy" => "dummy" ); if (! i5_program_call ( $prog1, $parameter, $parmOut )) { die ( "Call to clearMessages() did not work" ); } i5_program_close ( $prog1 ); } function addMessage($conn, $msgId, $forField = " ", $msgData = " ") { $desc = array (array ("name" => "msgId", "io" => I5_INOUT, "type" => I5_TYPE_CHAR, "length" => "7" ), array ("name" => "forFieldIn", "io" => I5_INOUT, "type" => I5_TYPE_CHAR, "length" => "25" ), array ("name" => "msgData", "io" => I5_INOUT, "type" => I5_TYPE_CHAR, "length" => "500" ) ); $prog1 = i5_program_prepare ( "MESSAGES/UTILITY (addMessage)", $desc, $conn ); if (! $prog1) { die ("Prepare for addMessage() did not work"); } $parameter = array ("msgId" => $msgId, "forFieldIn" => $forField, "msgData" => $msgData ); $parmOut = array ("msgId" => "msgId", "forFieldIn" => "forField", "msgData" => "msgData" ); if (! i5_program_call ( $prog1, $parameter, $parmOut ) ) { die ("Call to addMessage() did not work"); } i5_program_close ( $prog1 ); } function messageCount($conn) { $desc = array ( array ("name"=>"numMessages", "io"=>I5_INOUT, "type"=>I5_TYPE_LONG, "length"=>"4"), ); $prog1 = i5_program_prepare("MESSAGES/UTILITY(php_messageCount)", $desc, $conn); if (!$prog1) { die("Prepare for messageCount() did not work"); } $parameter = array("numMessages"=>0); $parmOut = array("numMessages"=>"msgCount"); if (! i5_program_call($prog1, $parameter, $parmOut)){ die("Call to messageCount() did not work"); } i5_program_close ( $prog1 ); return $msgCount; } function getMessage($conn, $forMessage, $forField=" ", $msgId=" ", $help=" ", $severity=0) { $desc = array ( array ("name"=>"forMessage", "io"=>I5_INOUT, "type"=>I5_TYPE_LONG, "length"=>"4"), array("DSName"=>"msgFormat", "DSParm"=>array( array("Name"=>"msgId", "IO"=>I5_INOUT, "Type"=>I5_TYPE_CHAR, "Length"=>"7"), array("Name"=>"msgText", "IO"=>I5_INOUT, "Type"=>I5_TYPE_CHAR, "Length"=>"80"), array("Name"=>"severity", "IO"=>I5_INOUT, "Type"=>I5_TYPE_LONG, "Length"=>"4"), array("Name"=>"help", "IO"=>I5_INOUT, "Type"=>I5_TYPE_CHAR, "Length"=>"500"), array("Name"=>"forField", "IO"=>I5_INOUT, "Type"=>I5_TYPE_CHAR, "Length"=>"25")) ) ); $prog1 = i5_program_prepare("MESSAGES/UTILITY(getMessage)", $desc, $conn); if (!$prog1){ die("Prepare for getMessage() did not work"); } $parameter = array("forMessage"=>$forMessage, "msgFormat"=>array("msgId"=>" ")); $parmOut = array("forMessage"=>"forMessage", "msgFormat"=>"msgFormat"); if (! i5_program_call($prog1, $parameter, $parmOut)){ die("Call to getMessage() did not work"); } i5_program_close($prog1); $forField = $msgFormat["forField"]; $msgId = $msgFormat["msgId"]; $help = $msgFormat["help"]; $severity = $msgFormat["severity"]; return $msgFormat["msgText"]; } function fillMessages($conn, $numMessages) { $desc = array ( array ("name"=>"numMessages", "io"=>I5_INOUT, "type"=>I5_TYPE_LONG, "length"=>"4"), ); $prog1 = i5_program_prepare("MESSAGES/UTILITY(fillMessages)", $desc, $conn); if (!$prog1) { die("Prepare for fillMessages() did not work"); } $parameter = array("numMessages"=>$numMessages); $parmOut = array("numMessages"=>"msgCount"); if (! i5_program_call($prog1, $parameter, $parmOut)){ die("Call to fillMessages() did not work"); } i5_program_close ( $prog1 ); return; } ?> Figure 6: PHP functions, func_messages.php. Note that each of the functions is passed a parameter ($conn), which identifies the connection to be used on the call to the subprocedure. This parameter identifies the job that actually calls the subprocedures. Also note the definition of constants for the message IDs. Figure 7 shows the PHP script phpmessage.php that corresponds to the SHOWMSGS program. The script performs the following:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html > <head> <title>Playing with PHP Program Calls</title> </head> <body> <h1>Playing with PHP Messages</h1> <p>This example demonstrates use of the message routines</p> <?php ini_set('display_errors', 1); error_reporting(E_ALL & ~E_NOTICE); require 'func_messages.php'; ($conn = i5_pconnect("localhost", "", "",array("I5_OPTIONS_JOBNAME" =>"PAULPHP"))) or trigger_error("Could not make connection", E_USER_ERROR); echo "Clear Messages <br />"; clearMessages($conn); echo "Add Messages <br />"; addMessage($conn, APP_ERR_NOTFOUND, 'TEST1'); addMessage($conn, APP_ERR_CHANGED, 'TEST2'); echo "Fill 3 Messages <br />"; fillMessages($conn, 3); echo "Get Message count <br />"; $msgCount = messageCount($conn); echo "Returned message count is ".$msgCount." <br />"; echo "Get Messages <br />"; for ($i = 1; $i <= $msgCount; $i++) { echo getMessage($conn, $i)." <br />"; } i5_close($conn); ?> <p> Page complete </p> </body> </html> Figure 7: Script phpmessage.php, use of message procedures in PHP. The expected results from phpmessage.php are shown in Figure 8: Figure 8: Result of calling phpmessage.php. Other Message Subprocedures But we don’t have to stop with just these subprocedures. Figure 9 lists some other message subprocedures that you might find useful:
P addMessageText B Export D PI D msgText 80a Const D forFieldIn 25a Const D Options(*Omit:*NoPass) D severity 10i 0 Const D Options(*NoPass) D forField S like(forFieldIn) /free msgCount += 1; messages(msgCount).msgText = msgText; if %Parms() > 2; messages(msgCount).severity = severity; endIf; if %Parms()> 1; if %Addr(forFieldIn) <> *Null; forField = forFieldIn; endIf; endIf; messages(msgCount).forField = forField; /end-Free P E P setMessageFile... P B Export D PI D newMsgf 10A Const D newMsgLib 10A Const D Options(*NoPass) /free msgFile = newMsgF; if %Parms()> 1; msgFileLib = newMsgLib; endIf; return; /end-Free P E P sendFileError B Export D PI n D status 5i 0 Const /free select; // Duplicate when status = STAT_DUPLICATE; addMessage(ERR_DUPLICATE); // Referential Constraint when status = STAT_CONSTRAINT_1 or status = STAT_CONSTRAINT_2; sendConstraintMsg(); // Trigger when status = STAT_TRIGGER_1 or status = STAT_TRIGGER_2; addMessage(ERR_TRIGGER); // Other other; addMessage(ERR_UNKNOWN); return *On; endSl; return *Off; /end-Free P E Figure 9: Some other useful message procedures. There You Have It Hopefully these two articles have given you some food for thought. Remember that your RPG code does not have to be confined to RPG-only applications. All that great code can be opened up to wider use. Paul Tuohy is CEO of ComCon, an iSeries consulting company, and is one of the co-founders of System i Developer, which hosts the RPG & DB2 Summit conferences. He is an award-winning speaker who also speaks regularly at COMMON conferences, and is the author of “Re-engineering RPG Legacy Applications,” “The Programmers Guide to iSeries Navigator,” and the self-study course called “iSeries Navigator for Programmers.” Send your questions or comments for Paul to Ted Holt via the IT Jungle Contact page. RELATED STORY
|