• The Four Hundred
  • Subscribe
  • Media Kit
  • Contributors
  • About Us
  • Contact
Menu
  • The Four Hundred
  • Subscribe
  • Media Kit
  • Contributors
  • About Us
  • Contact
  • 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:

    • Calls clearMessages() to clear any stored messages.
    • Calls addMessage() twice to add two messages (note the use of the named constants to identify the required message IDs).
    • Calls fillMessages() to add three filler messages.
    • Based on the value returned by messageCount(), the program loops through calls to getMessage() and display the message text returned for each message.
     /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:

    • Makes a connection to the i ($conn) .
    • Calls clearMessages() to clear any stored messages.
    • Calls addMessage() twice to add two messages (note the use of the named constants to identify the required message IDs).
    • Calls fillMessages() to add three filler messages.
    • Based on the value returned by messageCount(), the script loops through calls to getMessage() and display the message text returned for each message.
    • Closes the connection to the i.
    <!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:

    • addMessageText() allows you to directly store message text as opposed to using a message ID/message file.
    • setMessageFile() allows you to change the default message file and/or library being used.
    • sendFileError() sends file messages based on a status code passed as a parameter. This subprocedure would be called based on an I/O error being received on a file operation and being trapped with an error extender.
    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

    Getting the Message, Part 1



                         Post this story to del.icio.us
                   Post this story to Digg
        Post this story to Slashdot

    Share this:

    • Reddit
    • Facebook
    • LinkedIn
    • Twitter
    • Email

    Tags:

    Sponsored by
    WorksRight Software

    Do you need area code information?
    Do you need ZIP Code information?
    Do you need ZIP+4 information?
    Do you need city name information?
    Do you need county information?
    Do you need a nearest dealer locator system?

    We can HELP! We have affordable AS/400 software and data to do all of the above. Whether you need a simple city name retrieval system or a sophisticated CASS postal coding system, we have it for you!

    The ZIP/CITY system is based on 5-digit ZIP Codes. You can retrieve city names, state names, county names, area codes, time zones, latitude, longitude, and more just by knowing the ZIP Code. We supply information on all the latest area code changes. A nearest dealer locator function is also included. ZIP/CITY includes software, data, monthly updates, and unlimited support. The cost is $495 per year.

    PER/ZIP4 is a sophisticated CASS certified postal coding system for assigning ZIP Codes, ZIP+4, carrier route, and delivery point codes. PER/ZIP4 also provides county names and FIPS codes. PER/ZIP4 can be used interactively, in batch, and with callable programs. PER/ZIP4 includes software, data, monthly updates, and unlimited support. The cost is $3,900 for the first year, and $1,950 for renewal.

    Just call us and we’ll arrange for 30 days FREE use of either ZIP/CITY or PER/ZIP4.

    WorksRight Software, Inc.
    Phone: 601-856-8337
    Fax: 601-856-9432
    Email: software@worksright.com
    Website: www.worksright.com

    Share this:

    • Reddit
    • Facebook
    • LinkedIn
    • Twitter
    • Email

    Sponsored Links

    Infor:  Visit the first System i Virtual Conference hosted by Infor and IBM. View on-demand Webinar.
    CCSS:  Need Pro-Active Management of Your IBM® i Server? We can help.
    Patrick Townsend Security Solutions:  Get a customized state privacy law compliance report

    IT Jungle Store Top Book Picks

    Easy Steps to Internet Programming for AS/400, iSeries, and System i: List Price, $49.95
    The iSeries Express Web Implementer's Guide: List Price, $49.95
    The System i RPG & RPG IV Tutorial and Lab Exercises: List Price, $59.95
    The System i Pocket RPG & RPG IV Guide: List Price, $69.95
    The iSeries Pocket Database Guide: List Price, $59.00
    The iSeries Pocket SQL Guide: List Price, $59.00
    The iSeries Pocket Query Guide: List Price, $49.00
    The iSeries Pocket WebFacing Primer: List Price, $39.00
    Migrating to WebSphere Express for iSeries: List Price, $49.00
    Getting Started With WebSphere Development Studio Client for iSeries: List Price, $89.00
    Getting Started with WebSphere Express for iSeries: List Price, $49.00
    Can the AS/400 Survive IBM?: List Price, $49.00
    Chip Wars: List Price, $29.95

    Kronos Acquires Time and Attendance Software from Paychex IBM Rolls Up an i 6.1.1 Dot Release

    Leave a Reply Cancel reply

Volume 9, Number 38 -- October 21, 2009
THIS ISSUE SPONSORED BY:

Halcyon Software
ProData Computer Services
Twin Data

Table of Contents

  • Prompting CL in Run SQL Scripts
  • Publish Result Sets Using Web Services and IWS
  • Admin Alert: The Ins and Outs of IBM Business Partners
  • Getting the Message, Part 2
  • Passing an Entire Result Set as a Parameter, Part 2
  • Setting Up SNTP Time Synchronization on an i5/OS Box

Content archive

  • The Four Hundred
  • Four Hundred Stuff
  • Four Hundred Guru

Recent Posts

  • POWERUp 2025 –Your Source For IBM i 7.6 Information
  • Maxava Consulting Services Does More Than HA/DR Project Management – A Lot More
  • Guru: Creating An SQL Stored Procedure That Returns A Result Set
  • As I See It: At Any Cost
  • IBM i PTF Guide, Volume 27, Number 19
  • IBM Unveils Manzan, A New Open Source Event Monitor For IBM i
  • Say Goodbye To Downtime: Update Your Database Without Taking Your Business Offline
  • i-Rays Brings Observability To IBM i Performance Problems
  • Another Non-TR “Technology Refresh” Happens With IBM i TR6
  • IBM i PTF Guide, Volume 27, Number 18

Subscribe

To get news from IT Jungle sent to your inbox every week, subscribe to our newsletter.

Pages

  • About Us
  • Contact
  • Contributors
  • Four Hundred Monitor
  • IBM i PTF Guide
  • Media Kit
  • Subscribe

Search

Copyright © 2025 IT Jungle