Nov 28

Capturing the return value from a function

As a C programmer I have become used to using the return values set by a function as an indicator of the success of the function to carry out its intended role, so when I created Service programs I used the same process. This raised a question about how I could capture that return value and use it within my PHP scripts as an indicator. I have read through the manuals and could not find a way to capture that value so I had resulted to creating a parameter that I would pass in and have that set on return instead of relying on the return value from the function. It seems that there is a way to capture the return value from the function, although it is not documented Easycom provides a simple way to capture the return value via a “virtual parameter”. Why is this important, well we use C a lot, so it is important that when we write a module that we can use it across multiple programs. Before we found this gem we had to write a function which we could call from our C programs and use the return value and another function with exactly the same process that used the input parameters as a way of determining the functions sucess.

Before we go whole hog into this we wanted to check to make sure that our understanding was correct, its not documented so we had to work out some of the details ourselves although Aura did give us the basic structure. The C function we will test with is a service program with a single function, it will return the index of the passed in character within an array of characters (very simple check but provided us with all we need). Here is the C program we compiled as a service Program.


#include<stdio.h>
#include<string.h>

int retvaltst(char *tstchar) {
int i = 0;
char sample[10] = "ABCDEFGHIJ";

for(i = 0; i < 10; i++) {
if(sample[i] == *tstchar)
break;
}
return ++i;
}

As you can see all it does is look at the character passed as looks for it in the sample array, if it finds the character it breaks and returns the index of the character (we increment the counter before we return it). This is the code that we used in a PHP function to test the theory, the connection and everything else is a hack of the connection routines etc we created in previous tests using the PHPTST website..


function get_ret_val(&$conn,$tstchar) {

$description = Array (array ("Name" => "tstchar", "IO" => I5_IN, "Type" => I5_TYPE_CHAR, "Length" => "1"),
array ("Name" => "retval", "IO" => I5_RETVAL | I5_OUT, "Type" => I5_TYPE_INT ));
$prog = i5_program_prepare("CHLIB/RETVALTST(retvaltst)", $description );
if ($prog == FALSE) {
$errorTab = i5_error ();
echo "Program prepare failed RETVALTST(retvaltst)";
var_dump($errorTab);
var_dump($conn);
die ();
}
$parameter = array('tstchar' => $tstchar);
$parmOut = array('retval' => 'retval');
$ret = i5_program_call($prog, $parameter,$parmOut);
if (!$ret) {
throw_error("i5_program_call failed");
exit();
}
// close the program call
i5_program_close($prog);
echo($retval);
return;
}

The function takes a character which is passed to the page as part of the call and sends it to the service program function. The set up of the program prepare shows how we set the retval parameter as a “I5_RETVAL | I5_OUT” type, Easycom knows that this is where the return value from the function is to be set. The function call only accepts a character so the i5_program_prepare() understands that the “I5_RETVAL | I5_OUT” is only used to carry the result and will not be passed into the function. We have to set the $parmOut variable to reflect the retval content as this is where it will be stored by the program. Then we call the program function and echo out the result.

Here are a couple of test results.

http://www.phptst.shield.local/retvaltst.php?tstchar=D = 4
http://www.phptst.shield.local/retvaltst.php?tstchar=F = 6
http://www.phptst.shield.local/retvaltst.php?tstchar=Z = 11 (remember we only have 10 characters so anything above 10 says we did not find it! Its case sensitive as well)

So we can now reduce the coding effort and utilize the same functions in many cases whether they are called from ILEC or PHP.. Now we need to look at the possibility of picking up other return values such as strings and structures…. There are a number of other undocumented features which we will try out in later test, particularly the ability to pass by Value or by Reference.

Happy PHP’ing.

Chris…

Nov 23

A Bit more fun with the File display and update web pages.

OK so we thought we would have a bit more fun with the previous programs which allowed you to move around and update file records. This is very basic code so don’t expect too much, but as we always say it’s free and you can do what you want with it (or not!). We were thinking about what we could add to make it a bit more of a complete project, so we decided to add a few more pages and functions which would allow you to drill down to the data you were interested in updating. REMEMBER this code took us about 1 hour to create so it is not anything like production ready, but it does show some of the power you can use to add new interfaces over your existing programs and products using PHP and the i5_toolkit().

The first thing we did was moved the process back a few steps so we could let the user sign on and then pick the library they were interested in and then displaying the files it contained.

This is the index.php file which is the start of the process. Very simple and all it does is force you to pass in some credentials (sign on info) which it uses to get a list of the libraries on the target system. This is the code which generates the first page.


<?php
/*
Copyright © 2010, Shield Advanced Solutions Ltd
All rights reserved.

http://www.shieldadvanced.ca/

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.

- Neither the name of the Shield Advanced Solutions, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior written
permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

*/
// start the session to allow session variables to be stored and addressed
session_start();
require_once("scripts/functions.php");
// get the previous return URL
$ret_url = $_SESSION['ret_url'];
// store this page as return URL
$_SESSION['ret_url'] = $_SERVER['PHP_SELF'];
if(!isset($_SESSION['server'])) {
load_config();
}
?>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Display File Content</title>
<!-- add the main CSS -->

</head>

<body>
<?php
// if valid user is set connect using Private connection
if(isset($_SESSION['valid_usr'])) {
$conn = 0;
if(!connect($conn)) {
if(isset($_SESSION['Err_Msg'])) {
echo($_SESSION['Err_Msg']);
$_SESSION['Err_Msg'] = "";
}
echo("Failed to connect");
}
else {
Dsp_Lib_Obj($conn);
}
}
else {?>
<form name=login method="post" action="scripts/login.php">
<table width="20%" align="center" border="1" cellpadding="1">
<tr><td><label>User ID :</label></td><td><input type="text" name="usr" /></td></tr>
<tr><td>Password:</td><td><input type="password" name="pwd" /></td></tr>
<tr><td colspan="2"><?php if(isset($_SESSION['Err_Msg'])) { echo($_SESSION['Err_Msg']); $_SESSION['Err_Msg'] = ''; } ?></td></tr>
<tr><td><label>Connection type</label></td><td><select id="conn_type" name="conn_type"><option value="decrypt" selected="selected">Decrypted</option><option value="encrypt">Encrypted</option></select></td></tr>
<tr><td colspan="2" align=center><input type="submit" value="Log in" /></td></tr><?php
if(isset($_SESSION['Pwd_Err'])) {
if($_SESSION['Pwd_Err'] == 1) { ?>
<tr><td colspan="2" align=center>Sorry the credentials were rejected by the <?php echo($_SESSION['sys']); ?> System</td></tr><?php
$_SESSION['Pwd_Err'] = 0;
}
} ?>
</table>
</form><?php
} ?>
</body>
</html>

The config file is loaded at the beginning of the process which contains the target server to use. The function which does that is contained in the functions.php file which is shown next. this also contains all of the functions used to connect to the server and display the content for other pages. (I hope I cut and pasted everything we used). I did not copy the config file but you should be able to work out the layout and content based on the load_config() function.


<?php
/*
Copyright © 2010, Shield Advanced Solutions Ltd
All rights reserved.

http://www.shieldadvanced.ca/

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.

- Neither the name of the Shield Advanced Solutions, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior written
permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/

function connect(&$conn) {
// reset the the ErrMsg variable
unset($_SESSION['ErrMsg']);
// connect to the i5
$conId = 0;
if (isset($_SESSION['ConnectionID'])) {
$conId = $_SESSION['ConnectionID'];
}
$server = $_SESSION['server'];
$addlibl = array($_SESSION['install_lib']);
// options array for the private connection
$options = array(
I5_OPTIONS_PRIVATE_CONNECTION => $conId,
I5_OPTIONS_IDLE_TIMEOUT => $_SESSION['timeout'],
I5_OPTIONS_JOBNAME => 'PHPTSTSVR');
// connect to the system
if($_SESSION['conn_type'] == 'encrypt')
$conn = i5_pconnect($server,$_SESSION['usr'],d_pwd($_SESSION['pwd']),$options);
else
$conn = i5_pconnect($server,$_SESSION['usr'],$_SESSION['pwd'],$options);
// if connect failed
if(is_bool($conn) && $conn == FALSE) {
$errorTab = i5_error();
if ($errorTab['cat'] == 9 && $errorTab['num'] == 285){
$_SESSION['ConnectionID'] = 0;
$_SESSION['Err_Msg'] = "Failed to connect";
return -1;
}
else {
//set the error message
$_SESSION['Err_Msg'] = "Connection Failed " .i5_errormsg();
// send back to the sign on screen
$_SESSION['ConnectionID'] = 0;
return -1;
}
}
else {
// add the library list
add_to_librarylist($addlibl,$conn);
$ret = i5_get_property(I5_PRIVATE_CONNECTION, $conn);
if(is_bool($ret) && $ret == FALSE) {
$_SESSION['Err_Msg'] = "Failed to retrieve connection ID " .i5_errormsg();
$_SESSION['ConnectionID'] = 0;
return -1;
}
else {
$_SESSION['ConnectionID'] = $ret;
}
}
return 1;
}

/*
* Function to add the install library to the library list
* @parms
* libraries to add to the library list
* connection to use
*/

function add_to_librarylist($libraries,&$conn) {
$curlibl = "";
// get current librarylist
if (!i5_command("rtvjoba",array(),array("usrlibl"=>"curlibl"),$conn))
die("Could not retrieve current librarylist:" . i5_errormsg($conn));
// add our libraries to the librarylist
$curlibl .= " " .implode(" ",$libraries);
// if library list already set a CPF2184 message will be generated, treat as warning and by pass.
if(!i5_command("chglibl",array("libl"=>$curlibl),array(),$conn)) {
if(i5_errormsg($conn) != 'CPF2184')
echo("Could not change current librarylist:" . i5_errormsg($conn));
}
return 1;
}

/*
* function Dsp_Pfm()
* display the contents of a file
* @parms
* File
* File Library
* returns the number of errors.
*/

function Dsp_Recs($conn,$file,$flib) {

// query to get the data frm the file
$query = "SELECT rrn(a) as RRN, a.* FROM " .rtrim($flib) ."/" .rtrim($file) ." a";
$result = i5_query($query,$conn);
if(!$result){
echo("Failed to get the data $_SESSION['ErrMsg'] = "Error code: " .i5_errno($result) ." Error message: " .i5_errormsg($result);
}
$rec = i5_fetch_assoc($result);
echo("
// the assoc array contains the field names so we can use those as the headers
foreach($rec as $key => $value) {
echo(" }
echo(" // now just write out the data again and get the next record to the end
do {
echo(" foreach($rec as $key => $value) {
echo(" }
echo(" } while($rec = i5_fetch_assoc($result));
echo(" i5_free_query($result);
return;
}

/*
* function Dsp_Pfm()
* display the contents of a file
* @parms
* File
* File Library
* returns the number of errors.
*/

function Dsp_Pfm_2($conn,$file,$flib,$id) {

// query to get the data frm the file
$query = "SELECT rrn(a) as RRN, a.* FROM " .rtrim($flib) ."/" .rtrim($file) ." a WHERE RRN(a) = '" .$id ."' FETCH FIRST ROW ONLY";
$result = i5_query($query,$conn);
if(!$result){
echo("Failed to get the data<br>" .$query);
$_SESSION['ErrMsg'] = "Error code: " .i5_errno($result) ." Error message: " .i5_errormsg($result);
}
$rec = i5_fetch_assoc($result);
echo("<table><form name=updent method=post action=scripts/update_pfm.php?file=" .$file ."&flib=" .$flib ."&id=" .$rec['RRN'] ." >");
// the assoc array contains the field names so we can use those as the headers
$i = 0;
foreach($rec as $key => $value) {
echo("<tr><td>" .$key ."</td><td><input type=text name=" .$key ." id=" .$key ." value='" .$value ."'");
if($key == "RRN")
echo(" readonly=readonly");
echo(" /></td></tr>");
$i++;
}
echo("<tr><td colspan=2><input type=submit value=Update /></td</tr></form></table>");
// button to get the next record
echo("<table><tr><td>");
if($id > 0) {
$id--;
echo("<input type=button value=PREV OnClick=location='dsp_pfm.php?file=" .$file ."&flib=" .$flib ."&id=" .$id ."' /></td><td>");
$id++;
}
$id++;
echo("<input type=button value=NEXT OnClick=location='dsp_pfm.php?file=" .$file ."&flib=" .$flib ."&id=" .$id ."' /></td></tr></table>");
echo("<input type=button value='Log Out' OnClick=location='scripts/logout.php' ?>");
// break the reference to the last element as per the manual
unset($value);
i5_free_query($result);
return;
}

/*
* function load_config()
* @parms
* *NONE
*/

function load_config() {
$config_file = "scripts/config.conf";
$comment = "#";
// open the config file
$fp = fopen($config_file, "r");
if(!$fp) {
echo("Failed to open file");
return 0;
}
// loop through the file lines and pull out variables
while(!feof($fp)) {
$line = trim(fgets($fp));
if ($line && $line[0] != $comment) {
$pieces = explode("=", $line);
$option = trim($pieces[0]);
$value = trim($pieces[1]);
$config_values[$option] = $value;
}
}
fclose($fp);
$_SESSION['server'] = $config_values['server'];
$_SESSION['install_lib'] = $config_values['install_lib'];
$_SESSION['timeout'] = $config_values['timeout'];
$_SESSION['refresh'] = $config_values['refresh'];
$_SESSION['logo'] = $config_values['company_logo'];
$_SESSION['dft_logo'] = $config_values['default_logo'];
$_SESSION['co_name'] = $config_values['company_name'];
if(isset($config_value['valid_usr'])) {
if($config_values['valid_usr'] != '') {
if(($config_values['usr'] != '') && ($config_values['pwd'] != '')) {
$_SESSION['valid_usr'] = $config_values['valid_usr'];
$_SESSION['usr'] = $config_values['usr'];
$_SESSION['pwd'] = $config_values['pwd'];
$_SESSION['auto_signon'] = 1;
}
}
}
return 1;
}

/*
* function to retrieve a list of libraries
*/

function Dsp_Lib_Obj(&$conn) {
$HdlList = i5_objects_list('QSYS', '*ALL', '*LIB',$conn);
if (is_bool($HdlList)) trigger_error('i5_objects_list error : '.i5_errormsg(), E_USER_ERROR);
echo("

    while($list = i5_objects_list_read($HdlList)){
    echo(" if($list['DESCRIP'])
    echo(str_pad($list['DESCRIP'],50," ",STR_PAD_RIGHT));
    else
    echo("-");
    echo(" echo(" }
    echo(" $ret = i5_objects_list_close($HdlList);
    if (!$ret) trigger_error('i5_object_list_close error : '.i5_errormsg(), E_USER_ERROR);
    return;
    }

    /*
    * function to retrieve a list of Files in a Library
    */

    function Dsp_Pf_Obj(&$conn,$lib) {
    $HdlList = i5_objects_list($lib, '*ALL', '*FILE',$conn);
    if (is_bool($HdlList)) trigger_error('i5_objects_list error : '.i5_errormsg(), E_USER_ERROR);
    echo("

      while($list = i5_objects_list_read($HdlList)){
      // check if a PF-DTA
      if($list['EXT_ATTR'] == "PF") {
      echo(" if($list['DESCRIP'])
      echo(str_pad($list['DESCRIP'],50," ",STR_PAD_RIGHT));
      else
      echo("-");
      echo(" echo(" }
      }
      $ret = i5_objects_list_close($HdlList);
      if (!$ret) trigger_error('i5_object_list_close error : '.i5_errormsg(), E_USER_ERROR);
      return;
      }
      ?>

      The next page called provides a list of the *FILE objects in the library which have a EXT_ATTR of "PF", this allow you to select a file and display all of the records from that file. Again all we do is use the i5_toolkit function i5_list_objects() to provide the list of objects in the library and then filter based on the extended attribute.


      <?php
      /*
      Copyright © 2010, Shield Advanced Solutions Ltd
      All rights reserved.

      http://www.shieldadvanced.ca/

      Redistribution and use in source and binary forms, with or without
      modification, are permitted provided that the following conditions
      are met:

      - Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.

      - Neither the name of the Shield Advanced Solutions, nor the names of its
      contributors may be used to endorse or promote products
      derived from this software without specific prior written
      permission.

      THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
      "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
      LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
      FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
      COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
      INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES INCLUDING,
      BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
      LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
      CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
      LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
      ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
      POSSIBILITY OF SUCH DAMAGE.

      */
      // start the session to allow session variables to be stored and addressed
      session_start();
      require_once("scripts/functions.php");
      // if not signed in just force to sign in page
      if(!isset($_SESSION['valid_usr'])) {
      header("Location: /index.php");
      exit(0);
      }
      // get the previous return URL
      $ret_url = $_SESSION['ret_url'];
      // store this page as return URL
      $_SESSION['ret_url'] = $_SERVER['PHP_SELF'];
      ?>

      <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
      <html xmlns="http://www.w3.org/1999/xhtml">
      <head>
      <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
      <title>List Files in Library</title>
      <!-- add the main CSS -->
      <link rel="stylesheet" href="css/tst.css" type="text/css">
      </head>

      <body>
      <?php
      $conn = 0;
      if(!connect($conn)) {
      if(isset($_SESSION['Err_Msg'])) {
      echo($_SESSION['Err_Msg']); $_SESSION['Err_Msg'] = "";
      }
      }
      Dsp_Pf_Obj($conn,$_REQUEST['lib']); ?>
      </body>
      </html>

      From this list you will display all of the records in the file, we only used a small file to test so the amount of data coming back was not important, if this was production and you had files with millions of records in them it could blow up the page as it would be too big! If you want to add something consider paging the file in sets of records or passing in a range of records at the start so only a limited number of records are retrieved and displayed. We have written plenty of posts and forums entries about how to achieve this, Google should bring up enough information to allow you to do it. This is the code for the page to display all of the records, it simply displays them and has a button which allows you to edit specific records. We just left the display for this as it was so once you start to edit you will have the ability to move around the records as previously.


      <?php
      /*
      Copyright © 2010, Shield Advanced Solutions Ltd
      All rights reserved.

      http://www.shieldadvanced.ca/

      Redistribution and use in source and binary forms, with or without
      modification, are permitted provided that the following conditions
      are met:

      - Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.

      - Neither the name of the Shield Advanced Solutions, nor the names of its
      contributors may be used to endorse or promote products
      derived from this software without specific prior written
      permission.

      THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
      "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
      LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
      FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
      COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
      INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES INCLUDING,
      BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
      LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
      CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
      LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
      ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
      POSSIBILITY OF SUCH DAMAGE.

      */
      // start the session to allow session variables to be stored and addressed
      session_start();
      require_once("scripts/functions.php");
      // if not signed in just force to sign in page
      if(!isset($_SESSION['valid_usr'])) {
      header("Location: /index.php");
      exit(0);
      }
      // get the previous return URL
      $ret_url = $_SESSION['ret_url'];
      // store this page as return URL
      $_SESSION['ret_url'] = $_SERVER['PHP_SELF'];
      ?>

      <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
      <html xmlns="http://www.w3.org/1999/xhtml">
      <head>
      <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
      <title>Edit File Content</title>
      <!-- add the main CSS -->
      <link rel="stylesheet" href="css/tst.css" type="text/css">
      </head>

      <body>
      <?php
      $conn = 0;
      if(!connect($conn)) {
      if(isset($_SESSION['Err_Msg'])) {
      echo($_SESSION['Err_Msg']); $_SESSION['Err_Msg'] = "";
      }
      }
      Dsp_Pfm_2($conn,$_REQUEST['file'],$_REQUEST['flib'],$_REQUEST['id']); ?>
      </body>
      </html>

      An that is it, took us about 1 hour to complete the set of functions and pages (lots of cut and paste). I thought I would add a few pictures to show you just what it produces. Remember this uses the Aura tollkit functions so they must be available for it to work. Again we did not add any fancy page sizing CSS etc so the images are very basic.

      This is the inital page which requires the user to pass in valid credentials to connect to the IBMi.

      Uses the sign in information to access the IBMi

      Initial signon screen

      Once you have signed in you should see a list of the libraries available on the system. You could provide filters or use the IBMi security to only allow the user to display the available libraries.

      List of Libraries

      The list of libraries from the system

      When you select the library you will be provided with a list of Physical Files.

      List of files in library

      A list of files in the library for selection

      Select the option to display the records and the following page is displayed.

      Records contained in the file

      A list of the records contained in a file select to edit

      Select a record to start editing from and the following page is displayed we moved the record up one to show the movement buttons.

      Edit a file

      Editing records in a file

      That is it, plenty of improvements I can think of just as I write this post but you get the idea. IBMi is the perfect hub for delivering data via web pages, we prefer to keep the web page generation off the IBMi but you have the option to use the iAMP server or Zend Server etc. to provide the same functionality. Stop thinking about how to move off the platform and start thinking about how you can harvest all of the good business logic and data you have built into the system.

      Hope you find this useful, for us its just a bit of fun and provides us with the ability to show what we can do with PHP. You should take up the challenge and start your investment in PHP now. Call us if you need more information or help.

      Happy PHP'ing..

      Chris...

Nov 23

Interesting discussion about XMLSERVICE and big data

I sometimes worry about how we perceive the Open Source products and what we as developers should expect from it. I like to keep a watch on what is happening within the IBMi/PHP eco system so I tend to watch the various forums looking at what people are doing. I had not been following the Zend forums for sometime as I was told that my opinions were not welcome, but had a bit of time to spare so took a quick look at what is going on. I came across the following post which raised a couple of questions Working with multiple occurrence data structures.

This seems to be the source of another post, Toolkit errors after update where at the bottom of the thread is a comment which had the following statement.

Frankly, I find your whole rant tiresome, but very well …

You are completely missing the point here Timo. Unlike some other toolkits, XMLSERVICE does NOT REQUIRE proprietary software “connection”, therefore you can use all manner of 1-tier (IBM i-2-IBM i) and 2-tier (any-2-IBM i) connection transports. PHP Toolkit / XMLSERVICE cannot control the behaviour of each and every connection possible, and in fact, there are 2-tier connections that don’t have any idea what a LIBL would be because this is a truly unique feature of IBM i. IN ANY EVENT, you can simply call CHGLIBL in any staeless or staefull XMLSERVICE job and change the state of the LIBL.

I read through the whole thread looking for the OP’s rant? I could not find it so I went through the previous post from the same OP which is where I found the possible source of the irritation. Basically the OP’s had mentioned that they did not have the performance issues when they ran with the Easycom Toolkit from Aura, I did not think it was said in a bad way, but simply that they saw better performance from the Aura toolkit than they were seeing with the new XMLSERVICE despite many improvements. That is not the point of this post as we have already said in many previous posts what our feelings are about the performance, instead I would like to mention a few things I would take away from this.

1. This is open source and as such if you have any problems with the way it runs you should be willing to pitch in and develop it to meet your needs. Alan and Roger have done a great job so far.
2. Don’t blame the test data, the problem is in the XMLSERVICE technology not the data or the amount generated. We all see applications we feel could be better designed and developed.
3. Comparison should be expected from clients, they took a decision based on the information they were given. Zend/IBM have said the XMLSERVICE is the way forward for PHP on IBMi we don’t but oh well! :-).
4. What is the cost of the effort so far in making the migration? Would it not have been more cost effective to stick with the original toolkit and paid for Aura to work it out?
5. Emotional responses should be avoided, I did not see any significant reason from the OP to justify the responses. But its free so don’t expect anything else.

Open source is a great option as long as you have the ability to change it to meet your requirements. Before you charge into a project which uses open source technology make sure it will meet all of your requirements before making it the standard, or be prepared to spend a lot of time adjusting the code to meet your needs. Sometimes paying for someone else to maintain and develop new features for the technology is much more cost effective than doing it yourself. Aura may seem to be forcing your hand with the original i5_toolkit functions by requiring you to pay for it, but if you look at the technology and what it offers it’s a very small price to pay for the benefits it brings. Plus you can always ask for improvements under the maintenance agreement which Aura would develop for free if they deemed it worthy.

We are still working with Aura and offer support and licensing for their products here in North America. If you need help in licensing the product or would like to know more about how we have implemented the PHP technology in our products let us know, we are very happy to help guide you to the light :-)

Happy PHP’ing

Chris…

Nov 23

Next stage of the DB2 File updater

Previously we created a simple file output page which we said could be used to update a DB2 file in much the same way as the PHPMyAdmin interface allows where a single record is displayed which allows the user to move around the records and update fields where necessary. That example did not allow the update or moving around of the records so we thought we would add some simple code to allow that to happen.

First of all we needed to change the function which displays the data to allow that data to be sent off to another script to be updated. We also needed to add a couple of buttons which allows the user to move back and forth around the records. The buttons to move around the file were simple as they just call the same page with updated relative record numbers, updating the file required us to wrap the content in a form and have a submit button to call the target script.

Here is the updated function, the index.php and initial page did not require updtaing.


/*
* function Dsp_Pfm()
* display the contents of a file
* @parms
* File
* File Library
* returns the number of errors.
*/

function Dsp_Pfm_2($conn,$file,$flib,$id) {

// query to get the data frm the file
$query = "SELECT rrn(a) as RRN, a.* FROM " .rtrim($flib) ."/" .rtrim($file) ." a WHERE RRN(a) > '" .$id ."' FETCH FIRST ROW ONLY";
$result = i5_query($query,$conn);
if(!$result){
echo("Failed to get the data<br>" .$query);
$_SESSION['ErrMsg'] = "Error code: " .i5_errno($result) ." Error message: " .i5_errormsg($result);
}
$rec = i5_fetch_assoc($result);
echo("<table><form name=updent method=post action=scripts/update_pfm.php?file=" .$file ."&flib=" .$flib ."&id=" .$rec['RRN'] ." >");
// the assoc array contains the field names so we can use those as the headers
$i = 0;
foreach($rec as $key => $value) {
echo("<tr><td>" .$key ."</td><td><input type=text name=" .$key ." id=" .$key ." value='" .$value ."');
if(&key == "RRN")
echo(" readonly=readonly");
echo(" /></td></tr>");
$i++;
}
echo("<tr><td colspan=2><input type=submit value=Update /></td</tr></form></table>");
// button to get the next record
echo("<table><tr><td>");
if($id > 0) {
$id--;
echo("<input type=button value=PREV OnClick=location='dsp_pfm.php?file=" .$file ."&flib=" .$flib ."&id=" .$id ."' /></td><td>");
$id++;
}
$id++;
echo("<input type=button value=NEXT OnClick=location='dsp_pfm.php?file=" .$file ."&flib=" .$flib ."&id=" .$id ."' /></td></tr></table>");
echo("<input type=button value='Log Out' OnClick=location='scripts/logout.php' ?>");
// break the reference to the last element as per the manual
unset($value);
i5_free_query($result);
return;

As the update is carried out using the RRN as the key to the file we call the target script with the “id” parameter set to the current RRN. Once the target script is called it will convert the form data to a SQL script that can be run against the database. Here is the update script. Just to complete things we added a button that would call another script to log out the user and clean up the sessions variables. You will notice that we omit the RRN field from the update as it does not exist in the actual DB, we have also marked the field in the display page to be read only as it would be pretty messy if the users were allowed to change it.


<?php
/*
Copyright © 2010, Shield Advanced Solutions Ltd
All rights reserved.

http://www.shieldadvanced.ca/

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.

- Neither the name of the Shield Advanced Solutions, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior written
permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/

session_start();
include('functions.php');
// if failed to connect set the $_SESSION variables to empty
if(connect($conn) == -1) {
$_SESSION['Pwd_Err'] = 1;
$_SESSION['usr'] = "";
$_SESSION['pwd'] = "";
$_SESSION['valid_usr'] = NULL;
header('Location: /index.php');
exit(0);
}
// The post variables have the culumn headings and values
$query = "UPDATE " .$_REQUEST['flib'] ."." .$_REQUEST['file'] ." as a SET ";
foreach($_POST as $key => $value) {
// skip the RRN field
if($key != "RRN")
$query .= $key ." = '" .$value ."',";
}
// trim off the last ,'
$query = rtrim($query,",");
$query .= " WHERE RRN(a)='" .$_REQUEST['id'] ."'";
// break the reference to the last element as per the manual
unset($value);
// update the file
$result = i5_query($query,$conn);
if(!$result){
echo("Failed to get the data<br>" .$query);
$_SESSION['ErrMsg'] = "Error code: " .i5_errno($result) ." Error message: " .i5_errormsg($result);
$prev = $_REQUEST['id'];
$prev--;
header("Location: ../dsp_pfm.php?file=" .$_REQUEST['file'] ."&flib=" .$_REQUEST['flib'] ."&id=" .$prev);
}
header("Location: ../dsp_pfm.php?file=" .$_REQUEST['file'] ."&flib=" .$_REQUEST['flib'] ."&id=" .$_REQUEST['id']);
exit(0);
?>
}

The above code will format the SQL string and submit it for action using the i5_query() function. If the request is successful it will call the dsp_pfm page again with the current RRN as the id value, if it fails it will call the dsp_pfm page with a number less than the current RRN so the same RRN is displayed. you can change this to meet what ever requirements you have such as returning to the same record on the page with the update completed.

As you can see creating simple yet effective tools for the IBMi database are very easy, we have not added much in the way of error checking and corrective actions but you can see the possibilities are endless. We are using the Aura iAMP server and Zend server on a PC for testing the scripts and running a fully licensed Easycom server on the IBMi which allows us to access the i5_toolkit() functions from either system without any changes to the PHP code. The iAMP/Linux/Windows HTTP server requires no Easycom license to call the i5_toolkit() functions as all of the licensing is managed from the IBMi Easycom Server installation. If you are running the Zend Core/Server Easycom toolkit you will not be able to run the i5_toolkit() from a remote HTTP server.

If you are interested in doing more with PHP and your IBMi talk with us! We can guide you through the setting up of a very effective PHP based environment for both testing and production purposes. Licensing of Easycom allows you greater freedom and flexibility plus it brings many more features to your PHP solution at a cost which is a fraction of the other solutions out there. If you need to speak with some of the clients we have already helped move to a fully functioning Easycom environment let us know, I am sure they will be more than happy to discuss their experiences when dealing with us.

Happy PHP’ing.

Chris…

Nov 22

Walking through a DB2 PF data and allow update.

I was thinking about some of the tools I would like to see on the IBMi and written in PHP that allow you to carry out the same functionality as the PHPMyAdmin tool on MySQL. Not that I wanted to write the entire product but at times I would like the ability to just review and/or update the data in a particular file. Sometime ago we started to publish sample programs in PHP that show the very effective capabilities of running PHP against IBMi data an objects, one such project was being able to display file data and the file column names in a table see this post. So we decided to take that one step further and add the ability to output the data to a web interface and then allow you to update that data or move to the next record.

The final solution is very simple in terms of code required to generate the interface (we did not make it pretty just functional) but it is a good stepping stone to take it to the next stage. This first iteration simply retrieves the file data and displays the column heading along-side the actual data in the field. We have provided a button to move to the next entry but not added the move back or update buttons although these will be very simple to add. The process works by having a sign in screen (index.php) fronting the process, if a request is made that has no valid credentials a sign on screen is displayed. If Valid credentials are supplied it will automatically pass in a file name and start point to the called page which in-turn calls the function shown below.

/*
* function Dsp_Pfm()
* display the contents of a file
* @parms
* File
* File Library
* Starting record
* returns the number of errors.
*/

function Dsp_Pfm_2($conn,$file,$flib,$id) {

// query to get the data frm the file
$query = "SELECT rrn(a) as RRN, a.* FROM " .rtrim($flib) ."/" .rtrim($file) ." a WHERE RRN(a) > '" .$id ."' FETCH FIRST ROW ONLY";
$result = i5_query($query,$conn);
if(!$result){
echo("Failed to get the data<br>" .$query);
$_SESSION['ErrMsg'] = "Error code: " .i5_errno($result) ." Error message: " .i5_errormsg($result);
}
$rec = i5_fetch_assoc($result);
echo("<table>");
// the assoc array contains the field names so we can use those as the headers
$i = 0;
foreach($rec as $key => $value) {
echo("<tr><td>" .$key ."</td><td><input type=text name=data" .$i ." id=data" .$i ." value='" .$value ."' /></td></tr>");
$i++;
}
echo("</table>");
// button to get the next record
$id++;
echo("<input type=button value=NEXT OnClick=location='dsp_pfm.php?file=" .$file ."&flib=" .$flib ."&id=" .$id ."' />");
i5_free_query($result);
return;
}

That code will produce something similar to the following.

Sample output of edit panel

Output from the function

As I have said we have not added the ability to edit and update the data in the file, but that would be a fairly simple option to add.

I would encourage those developers out there who have not experimented with PHP yet to start! The power provided by the ability to call SQL and provide interfaces which are not limited by 10 character column names and 80 column displays has to be something you want to use.. Our HA4i High Availability product is using some of the technology we have demonstrated in the various Blog posts to it best advantage.

Happy PHP’ing..

Chris…

Nov 15

iAMP server looping due to ending TCP/IP services

One of our clients just called because the iAMP server which runs on their IBMi had taken over the CPU! The problem was due to their ending of the TCP/IP subsystems, this caused the iAMP jobs to go into a loop with the message “[error] (42)Protocol driver not attached: apr_socket_accept: (client socket)” being logged into the error_log constantly. We looked around on the net for any similar issues and found that a similar problem had been logged by IBM when the SSHD server had been configured to run in its own subsystem and the TCP/IP services had been ended and a save started. The IBM solution was to restart the subsystem once the TCP/IP services had been restarted. There are also a number of Apache related problems which still seem to be outstanding but they are more about the graceful ending of the server.

So if you are running ANY TCP/IP based programs that could fall into this category make sure you end them before ending the TCP/IP services. This might be overkill because we also notice that some other TCP/IP programs which are running sockets did not display the same effects but its probably a better option to end all of them than trying to find out exactly which programs are effected.

Chris…

Nov 14

Finding the last day of the month.

While installing a scheduler we came across the need to create a scheduler that would readily create the date for the last day of the month. We needed this so the scheduler could be passed a date range which would be the current date to the last day of the month. There are lots of ways of finding the last day of the month such as creating an array of the last days of the month and then addressing that array based on the current month. The only problem with this is the leap year adjustment which has a number of rules to follow such as a year that is a multiple of by 4 is a leap year, a Year that is a multiple of 100 is not a leap year, but a year that is a multiple of 400 is a leap year. Programming the logic would be fairly simple so we thought that would be the route we would take until we came across another approach, the suggestion was to find the first day of next month and subtract 1 day from it. So that is what we did and here is the code which we used.


#include
#include
#include

int main(int argc, char** argv) {
struct tm conv,*curr; // date structure and pointer
time_t lastday,now; // long variables for seconds since epoch

// set the conversion structure to first day of month and 00:00:00
conv.tm_hour = 0;
conv.tm_min = 0;
conv.tm_sec = 0;
conv.tm_mday = 1;

now = time(NULL);
curr = gmtime(&now);
// if december increment the year and set to january
if(curr->tm_mon == 12) {
conv.tm_mon = 0;
conv.tm_year = curr->tm_year + 1;
}
else {
conv.tm_mon = curr->tm_mon + 1;
conv.tm_year = curr->tm_year;
}
//convert the date to seconds
lastday = mktime(&conv);
// Subtract 1 day
lastday -= 68400;
// Convert back to date and time
conv = *localtime(&lastday);
printf("%.4d%.2d%.2d",conv.tm_year+1900,conv.tm_mon+1,conv.tm_mday);
exit(0);
}

That was it, a very simple piece of code which allows us to determine the last day of the month.

Chris…

Nov 13

IBMi eco system.

I was reading a number of articles in the press this morning about the IBMi (i5,iSeries,AS/400 and the rest) and the possible install base. The articles suggests that there are around 35,000 “active” IBM customers but around 110,000 customers who are still running the system but not on any maintenance or support? The articles also suggests that this number can be doubled in terms of systems because the average customer has 2 systems.

The articles then goes on to ask why are these customers who are loyal to the platform still running old releases of the software/hardware and suggests that this could be in part be due to the fact that the system is so robust and secure they have no need to do anything with it. I think some of that has merit, but in the same breath I think the pricing practices of IBM have contributed to that position. The second hand market is still very strong and many customers are still changing up their systems to later ones without any maintenance or support from IBM, so maybe this may point to the pricing of support by IBM? I stopped hardware maintenance simply because it did not make financial sense for the size of system we run! It was better to throw out the system and get another one if a major component failed (not that they do that often).

Here is a suggestion for IBM, I have a number of older systems which I do not run. What about allowing those customers who are running on systems where the CPU(s) was pegged at a certain percentage have the ability to upgrade these old system to run the FULL CPU capabilities. I have a 515 and 520 which are limited to 20% of the CPU. The processing power of these system was a lot less than my new system yet they cost me a lot more to purchase, if IBM allowed that processor to be opened up as long as I had them on maintenance maybe I and some other customers would take up such an offer? Maybe you could even make it an annual fee so you have to keep up with the changes in the OS, maybe that would remove the “if it aint broke don’t fix it” mentality. It would also add value to paying for maintenance which customers could relate to, and it would be IBM maintenance not third party..

So you ask why would IBM do that, after all they wont get much revenue even if a large proportion took them up? Well maybe it would help those customers who are sitting in the dark ages move towards the new technology. They could stipulate a minimum requirement in terms of OS to get the new keys which would force many to look at the system they run today. Maybe it would even get those customers who see the system as being old in a new light (what other system offers the ability to get 5X the processing power just by upgrading the OS?). It will enable them to look at the newer capabilities which were not available because the CPU restriction made them too slow and cumbersome. How many customers who are putting up with multi second response times use this as a confirmation that this system is old and needs replacing? Short term IBM does not make a lot of money because the customers will only pay a small fee to get the upgrade, but those customers may then see the system in a new light and develop the system further? If you are not having to invest in something it has no value, that is the problem with the IBMi.

If you are running crippled systems that have a lot more power than IBM has released talk to your IBM representative, maybe if enough ask IBM may sit up and listen? But expect to pay something even if it is a requirement to have that system on maintenance.

Chris…

PS: I am talking about opening up those P05 systems which were crippled at a % of the CPU, today’s P05 systems have much higher CPW rates for less cost, just allowing the CPU to reach its full potential without matching the newer systems capabilities is what I am asking for. There should be plenty of other reasons to move to the latest hardware technology.