From YiPs Wiki (i powered)

I5: Data Base 101 I 5 Call

(click to open)

Quick Page Table of Contents

Scanning…

Database 101

Introduction

This module takes you through the basics of using a PHP front-end to call an existing RPG back-end. We avoid fancy “view” HTML, or style-sheet tricks, and focus on the “control” elements of PHP to the “model” in our RPG program.

The PHP program will be calling the previous RPG 5250 sample. Remember that the 5250 program’s first screen asks the user for DVD search criteria, followed by a 5250 list of DVDs screen (subfile). This PHP program will have to provide the same service as the display file for the RPG program, but in an Apache “stateless” environment.

Why MVC?

We are using a handy PHP technique of includes that allow this application to switch out any of the MVC components. The ability to quickly change any MVC element in your program can make rapid prototyping a very easy task.

<?php
define('APP_BASE_DIR', '');
include_once APP_BASE_DIR.'101Config.php';      /* Site:    site configuration         */
include_once APP_BASE_DIR.'101Connecti5.php';   /* Model:   i5 toolkit connect         */
include_once APP_BASE_DIR.'101RPGModel.php';    /* Model:   i5 toolkit call RPG SRVPGM */
include_once APP_BASE_DIR.'101HtmlView.php';    /* View:    plain old generated html   */
include_once APP_BASE_DIR.'101HtmlControl.php'; /* Control: main loop html forms       */
?>

PHP Get Records (view simple)

Although this browser interface to our RPG back-end is ascetically a boor, we are using a few good techniques. We are using common headers and footers via include_once, so all company style can be controlled from one file. This a common market “branding” technique used many PHP applications, that allows for consistent style, color, company logo on each browser panel.

include_once './101HtmlView.php';
function anyform()
{ ds_html_header("DVD Search");
  :
  ds_html_footer();
}

If you are an old hand at view elements like html, css, etc., you probably want to skip ahead to the control section. If you are new to html here is a brief introduction to some techniques used to mix dynamic PHP code with static html.

How does HTML and DSPF compare?

We of course can not teach you all the ins and outs of html in these brief tutorials, but we can give you a feel for html and related style sheet technologies. The html syntax may look a bit alien, but then again your first display file was likely cut / paste as well.

In short, RPGers can think of html as the display file (*DSPF) of the browser world. Just like a display file contains many formats like “R DVDSDSPF3″, the html can contain many forms “<form> panel </form>”, except the browser is not limited to a form with a subfile, but may have many <form> panels all on one browser panel. As you may have guessed, here in lies one problem when one tries to put an existing screen by screen 5250 application on the web. After all, once you master html you may find that your prefered interface to your loved application could have multiple 5250 screens up all at the same time in a single bowser view. This is one reason among many that modern RPG experts will recommend the dreaded phrase “re-factor your application”. This is of course impossible at times and that is why PHP offers various techniques to leave things pretty much as they are, but that is a topic yet to come.

     A* ***************************************************
     A          R DVDSDSPF3
     A                                      CA12(12)
     A                                  1 33'DVD Search'
     A                                      DSPATR(HI)
     A                                  1 70'Search'
     A                                      DSPATR(HI)
     A                                  2 10'Select Type of Search:'
     A                                      DSPATR(HI)
     A                                  3 10'Title:'
     A            SCSTITLE      50A  B  3 25CHECK(LC)
     A                                  4 10'Actor:'
     A            SCSACTOR      50A  B  4 25CHECK(LC)
     A                                  5 10'Category:'
     A            SCSCAT        30A  B  5 25CHECK(LC)
     A                                  6 10'Number:'
     A            SCSMAX         3  0B  6 25
     A                                  8 10'Categories:'
     A                                      DSPATR(HI)
     A            SCCAT1        70   O  9 10
     A            SCCAT2        70   O 10 10
     A            SCCAT3        70   O 11 10
     A            SCCAT4        70   O 12 10
     A            SCERRMSG      78   O 23  2DSPATR(HI)
     A                                      DSPATR(BL)
     A                                 24  2'F12=Cancel'
     A* ***************************************************
// ------------------------
// View: DVD browse form
// ------------------------
function browseForm($max) 
{ global $HANDLER, $CATEGORIES;
  $check_title="";
  $check_actor="";
  $check_category="";
  switch($_POST ['browsetype'])
  { case 'title':
      $check_title = "CHECKED";
      break;
    case 'actor':
      $check_actor = "CHECKED";
      break;
    case 'category':
      $check_category = "CHECKED";
      break;
    default:
      break;
  }
  // output form
  ds_html_header("DVD Search");
  echo 
  "<p>Select Type of Search</p>";
  echo
  "\n<!-- form to be handled by $HANDLER -->";
  echo
  "\n<FORM  ACTION='$HANDLER' METHOD='GET' >";
  echo
  "\n   <!-- radio buttons select title, actor, category -->
   <INPUT NAME='browsetype' TYPE=RADIO VALUE='title' $check_title>Title
   <INPUT NAME='browse_title' VALUE='{$_POST['browse_title']}' TYPE=TEXT SIZE=15>
   (H = wildcard Happy, D = Death, C = Creek, A, R, S)
   <BR>
   <INPUT NAME='browsetype' TYPE=RADIO VALUE='actor' $check_actor>Actor
   <INPUT NAME='browse_actor' VALUE='{$_POST['browse_actor']}' TYPE=TEXT SIZE=15>
   (C = wildcard Chris Wock, F = Fred Flinflam, B, G)
   <BR>
   <INPUT NAME='browsetype' TYPE=RADIO VALUE='category' $check_category>Category";
  echo
  "\n    <!-- category array supplied by caller -->";
  echo ds_html_select("browse_category",$CATEGORIES);
  echo
  "\n   <!-- max number search results to return supplied by caller -->
   <BR>
   Number of search results to return";
  echo ds_html_select_nbr("limit_num", $max);
  echo
  "\n   <!-- form action button(s) -->
   <BR>
   <INPUT TYPE=SUBMIT VALUE='Search'>";
  echo
  "\n</FORM>";
  echo
  "\n<!-- error message -->
   <H2>{$_POST ['error']}</H2>";
  ds_html_footer();
}

In keeping with the MVC pattern we have built callable php interfaces to display our html in a separate view module, so that we may switch this element out in favor of something much better, without re-writing our control or model (RPG).

We have a <form> and a callable php interface for each panel our user clicks as they move though our DVD search application. We are using the html traditional method of <form> specified ACTION=“$HANDLER” that allows our web server to find the PHP control logic to handle each <form>. In this case we are using the same bit of control code, which you will see in the control section.

The mix of PHP variables within html like ACTION=‘$HANDLER’ allows for flexibility of design or different control code to handle the same form in some other context.

  echo
  "\n<FORM  ACTION='$HANDLER' METHOD='GET' >";

When to use HTML form METHOD=‘GET’ or METHOD=‘POST’?

You have a choice of using the METHOD=‘GET’ or METHOD=‘POST’ for your <form>, and most folks do not know why you pick one or the other. So here is a cut / paste from the following site that may help:

http://www.cs.tut.fi/~jkorpela/forms/methods.html. If the processing of a form is idempotent (i.e. it has no lasting observable effect on the state of the world), then the form method should be GET. Many database searches have no visible side-effects and make ideal applications of query forms. If the service associated with the processing of a form has side effects (for example, modification of a database or subscription to a service), the method should be POST.

From a PHP point of view data will be parsed into the super global $_REQUEST, which contains the contents of $_GET, $_POST and $_COOKIE. You will note that these examples tend to use super global $_POST[] with a special technique in the control module (next sections).

How to make reusable PHP/HTML components?

You may find that you are using some html components over and over, that is a reason to put a generic service in your common company view include (101View.php). The code below allows the html <select></select> pattern to be reused consistently in many forms. In this case when the user selects an option from the pull down “menu”, a keyword VALUE (1, 2, 3, etc.), will be returned to the PHP control logic. The $values is a simple array of possible choices array(“apple”, “orange”).

function ds_html_select($name,$values)
{ $i = 1;
  $form = "\n    <SELECT NAME='".$name."'>\n";
  foreach ( $values as $val ) 
  { if ($_POST[$name]== $i)
    { $form .= '      <OPTION VALUE=' . $i ++ . ' SELECTED>' . $val . '</OPTION>' . "\n"; 
    } 
    else
    { $form .= '      <OPTION VALUE=' . $i ++ . '>' . $val . '</OPTION>' . "\n";
    }
  }
  $form .= "    </SELECT>\n";
  return $form;
}

There are all manner of html gadgets that can decorate your view, but before you think html is the display file example, look at some of the later examples using CSS style sheets. However, for now you have the traditional html technique of moving from <form> to <form> using ACTION=‘./browse.php’.

PHP Get Records (control)

As we showed in the previous view section the html <form> ACTION=‘$HANDLER’ keyword will send all user browser “button” clicks to this program. So you can consider this small program to be the equivalent of the RPG cycle of a 5250 main program, except in this case the RPG “exfmt record” is a trip to the browser with the html <form></form> for write then read which is then sent back to this program via the ACTION=‘$HANDLER’ keyword.

It takes very little code to control the movement from screen to screen.

if ($_POST ['browsetype']) 
{ $browse_category="";
  if ($_POST ['browsetype']=="category") 
  { $browse_category=$CATEGORIES[$_POST ['browse_category']-1];
  }
  if (! model_search 
        ( $_POST ['browsetype'], 
          $_POST ['browse_title'], 
          $_POST ['browse_actor'], 
          $browse_category, 
          $_POST ['limit_num'], 
          $search_item 
         ))
  { $_POST ['error'] = model_error ();
    browseForm ($MAX);
  }
  else
  { searchForm ($search_item);
  }
}
else
{ browseForm ($MAX);
}

What are PHP super globals ($_REQUEST, $_POST, etc.)?

PHP automatically parses all the user html <form> data into the special super global array $_REQUEST[] (also $_POST[] if METHOD=‘POST’). This is tremendously handy compared with user code parsing of raw html structures for GET,POST, COOKIE, and may be one of the key reasons you consider mixing PHP with RPG.

Many first time PHP coders tend to copy out the $_POST[] elements into other single variables $thing=$_POST[‘thing’] (even some old hands), I generally find if I leave the data in one location $_POST[] i don’t have to write much code to get everything working.

How to avoid unset PHP array variables in $_POST?

To avoid unset $POST[] array elements I add a “mandatory” function to assure the $_POST variables exist. This technique avoids view code clutter of if (isset($_POST[‘userdata’])) { // do something }. Many 1st time production PHP code sites are writing to the PHP log and slowing down performance to a crawl due to unset variable warnings (check the log or use a technique like this one).

function post_mandatory($keys) 
{ foreach ( $keys as $key ) 
  { if (isset ( $_REQUEST [$key] ))
    { $_POST [$key] = $_REQUEST [$key];
    } 
    else if (! isset ( $_POST [$key] ))
    { $_POST [$key] = "";
    }
  }
}
function post_mandatory_array($keys) 
{ foreach ( $keys as $key ) 
  { if (isset ( $_REQUEST [$key] ))
    { $_POST [$key] = $_REQUEST [$key];
    }
    else if (! isset ( $_POST [$key] ))
    { $_POST [$key] = array ( );
    }
  }
}

This trick also allows control of <form>s to switch methods between METHOD=‘GET’ or METHOD=‘POST’ without code change.

post_mandatory 
( array 
  ( 'error', 'action', 'browsetype', 
    'browse_title', 'browse_actor', 
    'browse_category', 'limit_num' 
  )
);
post_mandatory_array 
( array 
  ( 'selected_item', 'item' 
  ) 
);

You are probably well on your way to PHP practitioner at this point, so let’s move on to the model code, where PHP meets RPG.

PHP Get Records Connect (i5 toolkit model)

At last, we have arrived at the original question, how do I call RPG from my PHP front-end? Well, it turns out there are multiple techniques for to get to RPG back-ends, but in this case we are going to use the popular Zend Core i5 toolkit.

The first thing to do is to get a connection to the i5 toolkit. We are using the i5_(p)connect function or persistent connections, to avoid costly IBM i job startup and termination associated with i5_connect/i5/close.

In general connection is considered a common activity for a PHP application site, so we have a site common include.

function model_connect() 
{ global $MODEL;
  $db_options = array(I5_OPTIONS_JOBNAME=>"DVDSEARCH");
  $MODEL ['conn'] = i5_pconnect 
  ( $MODEL ['database'],
    $MODEL ['db_user'], 
    $MODEL ['db_password'],
    $db_options
  );
  if (! $MODEL ['conn']) 
  { model_error_i5("Connect fail");
    return False;
  }
  return model_chglibl();

Every browser button click runs back through the i5_pconnect code, but the connection is cached for each Apache worker job, so that actual time is minimal. However, each button click in this example will not find it’s way back to the same worker job (see below).

How do I set the libl for my RPG program?

The library list drives many applications in RPG. However, in a Apache “stateless” environment like this sample, we can never count on the library list being set correctly. So, we have added chglibl code to the generic connection for i5 toolkit call.

function model_chglibl()
{ global $MODEL;
  $rc = i5_command
  ("chglibl",
    array("libl"=>$MODEL ['libl']),
	array()
  );
  if(!$rc)
  { model_error_i5("chglibl fail");
    return False;
  }
  return True;
} 

How do I call my *SRVPGM from PHP?

The i5 toolkit has few steps to call our *SRVPGM from the PHP model code:

most of the other code is the PHP version of the familiar setting up of RPG data structures for the input / output parameters.

There exists RPG pcml generated short cut for the parameter descriptions, but this author believes you are much better off doing a few examples by hand before you turn your coding life over to “hidden” tooling.

  // *** layout of procedure call parameters
  //     D SEARCH_t        ds                  qualified
  //     D                                     based(Template)
  //     D   TITLE                       50a
  //     D   ACTOR                       50a
  //     D   CATEGORY                    50a
  //     D   MAX                          3p 0
  //     D SEARCH_ITEM_t   ds                  qualified
  //     D                                     based(Template)
  //     D   DVDADD                       1a
  //     D   PROD_ID                     10i 0
  //     D   TITLE                       50a
  //     D   ACTOR                       50a
  //     D   PRICE                       12p 2
  //     D ORDER_loadSearch...
  //     D                 PR             1N   extproc(*CL:'ORDER_loadSearch')
  //     D  Search_k                           likeds(SEARCH_t)
  //     D  Count                         3p 0
  //     D  Item                               likeds(SEARCH_ITEM_t) dim(999)
  $description = 
  array
  ( // SEARCH_t Search_k 
    array
    ( "DSName"=>"key",
      "Count"=>1,
      "DSParm"=>
      array
      ( array
        ( "Name"=>"title", "IO"=>I5_IN,"Type"=>I5_TYPE_CHAR, "Length"=>"50"),
        array
        ( "Name"=>"actor", "IO"=>I5_IN, "Type"=>I5_TYPE_CHAR, "Length"=>"50"),
        array
        ( "Name"=>"cat", "IO"=>I5_IN, "Type"=>I5_TYPE_CHAR, "Length"=>"50"),
        array
        ( "Name"=>"max", "IO"=>I5_IN,"Type"=>I5_TYPE_PACKED, "Length"=>"3.0"),
      )
    ),
    // Count
    array
    ( "Name"=>"count", "IO"=>I5_IN|I5_OUT, "Type"=>I5_TYPE_PACKED, "Length"=>"3.0"),
    // SEARCH_ITEM_t Item(999)
    array
    ( "DSName"=>"item",
      "Count"=>999,
      "DSParm"=>
      array
      ( array
        ( "Name"=>"add","IO"=>I5_IN|I5_OUT,"Type"=>I5_TYPE_CHAR,"Length"=>"1"),
        array
        ( "Name"=>"id","IO"=>I5_IN|I5_OUT,"Type"=>I5_TYPE_LONG,"Length"=>"4"),
        array
        ( "Name"=>"title","IO"=>I5_IN|I5_OUT,"Type"=>I5_TYPE_CHAR,"Length"=>"50"),
        array
        ( "Name"=>"actor","IO"=>I5_IN|I5_OUT,"Type"=>I5_TYPE_CHAR,"Length"=>"50"),
        array
        ( "Name"=>"price","IO"=>I5_IN|I5_OUT,"Type"=>I5_TYPE_PACKED,"Length"=>"12.2"),
      )
    ),
  );
  $pgm = i5_program_prepare("QIWIKI/DVDSSERV(ORDER_loadSearch)",  $description);

After we set up the parameter description for our call we need to provide any input parameter data for the call. In this example the RPG *SRVPGM routine ORDER_loadSearch is expecting an input data structure “D Search_k likeds(SEARCH_t)”, therefore we will create a matching PHP array structure with the elements expected, In this case, the control code browse.php will pass a type of search and that we can use to supply title, actor or category to ORDER_loadSearch.

function model_search($browsetype, $browse_title, $browse_actor, $browse_category, $limit_num, &$items) 
{ global $MODEL;
  // *** parameter list allocation
  $listk=
  array
  ( "title"=>$browse_title,
    "actor"=>$browse_actor,
    "cat"  =>$browse_category,
    "max"  =>$limit_num
  );
  $list=array();
  for ($i=0;$i<$limit_num;$i++)
  { array_push
    ( $list, 
      array
      ( "add"=>"hole$i",  
        "id"=>"hole$i", 
        "title"=>"hole$i",
        "actor"=>"hole$i",
        "price"=>"hole$i"
      )
    );
  }
  // *** parameter values passed to procedure
  $in = 
  array
  ( "key"=>$listk,
    "count"=>0,
    "item"=>$list,
  );
  // *** name of variables created for out parameters
  $out = 
  array
  ( "count"=>"count",
    "item"=>"item",
  );
  $rc=i5_program_call($pgm, $in, $out);

  // ---------------------
  // new toolkit or old toolkit
  // ---------------------
  if (function_exists('i5_output')) extract(i5_output()); // new toolkit compatibility (Alan)

On the output end of the call we will receive back a array of items “D Item likeds(SEARCH_ITEM_t) dim(999)” of count “D Count 3p 0″ size.

  $rc=i5_program_call($pgm, $in, $out);

  // ---------------------
  // new toolkit or old toolkit
  // ---------------------
  if (function_exists('i5_output')) extract(i5_output()); // new toolkit compatibility (Alan)

  if ($rc != false && $count) 
  { for($i=0;$i<$count;$i++) 
    { $it=$item[$i];
      array_push
      ( $items,
        array
        ( $it["id"], 
          $it["title"], 
          $it["actor"], 
          $it["price"]
        )
      );
    }
  } 
  else 
  { model_error ( "<H2>No DVDs Found</H2>" );
    return False;
  }
  return True;
}

That’s it.

RPG Program (RPG *SRVPGM model)

Lastly, our RPG *SRVPGM handles all the heavy lifting, working with the DB2 files using native I/O. The key thing to remember about this SRVPGM is that it is written in a Apache “stateless” fashion, so either the 5250 PGM or the PHP fornt-end can share this common RPG back-end.

I will skip comment on this program, because it is likely that you are a better RPG program than I. If you want to read my 5250 RPG tutorial for PHP programmers, just go back to the start of the tutorials and take a look (it’s pretty serviceable code for not doing RPG production code for some time).

      *****************************************************
      * Template for products search
      *****************************************************
     D SEARCH_t        ds                  qualified
     D                                     based(Template)
     D   TITLE                       50a
     D   ACTOR                       50a
     D   CATEGORY                    50a
     D   MAX                          3p 0

      *****************************************************
      * Template for products search result
      *****************************************************
     D SEARCH_ITEM_t   ds                  qualified
     D                                     based(Template)
     D   DVDADD                       1a
     D   PROD_ID                      9b 0
     D   TITLE                       50a
     D   ACTOR                       50a
     D   PRICE                       12p 2

      *****************************************************
      *  ORDER_loadSearch(): Routine to load the search items
      *     on disk.
      *
      *     Search_k  = (input)  Search to load from disk
      *     Count     = (output) number of items
      *     Item      = (output) array of items
      *
      * Returns *ON when successful, *OFF otherwise.
      *****************************************************
     P ORDER_loadSearch...
     P                 B                   export
     D ORDER_loadSearch...
     D                 PI             1N
     D  Search_k                           likeds(SEARCH_t)
     D  Count                         3p 0
     D  Item                               likeds(SEARCH_ITEM_t) dim(999)

      /free
          OpenFiles();
          select;
          when   Search_k.CATEGORY<>*blanks;
            return ORDER_loadSearch_Cat(Search_k:Count:Item);
          when   Search_k.TITLE<>*blanks;
            return ORDER_loadSearch_Title(Search_k:Count:Item);
          when   Search_k.ACTOR<>*blanks;
            return ORDER_loadSearch_Actor(Search_k:Count:Item);
          other;
            SetError( ORDER_ERROR_BAD_SEARCH
                  : 'Bad search request');
          endsl;
          return *OFF;
      /end-free
     P                 E
Retrieved from http://youngiprofessionals.com/wiki/index.php/I5/DataBase101I5Call
Page last modified on January 10, 2013, at 01:27 PM EST