Published on InterSystems Developer Community (https://community.intersystems.com)

Home > PHP module for working with hierarchical data in InterSystems IRIS

Article
Sergey Kamenev · May 23, 2019 9m read
Open Exchange

PHP module for working with hierarchical data in InterSystems IRIS

PHP, from the beginning of its time, is renowned (and criticized) for supporting integration with a lot of libraries, as well as with almost all the DB existing on the market. However, for some mysterious reasons, it did not support hierarchical databases on the globals.

Globals are structures for storing hierarchical information. They are somewhat similar to key-value database with the only difference being that the key can be multi-level:


Set ^inn("1234567890", "city") = "Moscow"
Set ^inn("1234567890", "city", "street") = "Req Square"
Set ^inn("1234567890", "city", "street", "house") = 1
Set ^inn("1234567890", "year") = 1970
Set ^inn("1234567890", "name", "first") = "Vladimir"
Set ^inn("1234567890", "name", "last") = "Ivanov"

In this example, multi-level information is saved in the global ^inn using the built-in ObjectScript language. Global ^inn is stored on the hard drive (this is indicated by the “^” sign in beginning).

In order to work with globals from PHP, we will need new functions that will be added by the PHP module, which will be discussed below.

Globals support many functions for working with hierarchies: traversal tree on fixed level and in depth, deleting, copying and pasting entire trees and individual nodes. And also ACID transactions - as is done in any quality database. All this happens extremely quickly (about 105-106 inserts per second on regular PC) for two reasons:

 

  1. Globals are a lower level abstraction when compared to SQL,
  2. The bases have been in production on the globals for decades, and during this time they were polished and their code was thoroughly optimized.

Learn more about globals in the series of articles titled "Globals Are Magic Swords For Managing Data.":

Part 1.
Trees. Part 2.
Sparse arrays. Part 3.

In this world, globals are primarily used in storage systems for unstructured and sparse information, such as: medical, personal data, banking, etc.

I love PHP (and I use it in my development work), and I wanted to play around with globals. There was no PHP module for IRIS and Caché. I contacted InterSystems and asked them to create it. InterSystems sponsored the development as part of an educational grant and my graduate student and I created the module.

Generally speaking, InterSystems IRIS is a multi-model DBMS, and that's why from PHP you can work with it via ODBC using SQL, but I was interested in globals, and there was no such connector.

So, the module is available for PHP 7.x (was tested for 7.0-7.2). Currently it can only work with InterSystems IRIS and Caché installed on the same host.

Module page on OpenExchange (a directory of projects and add-ons for developers at InterSystems IRIS and Caché).

There is a useful DISCUSS section where people share their related experiences.

Download here:

https://github.com/intersystems-community/php_ext_iris

Download the repository from the command line:

git clone https://github.com/intersystems-community/php_ext_iris

 

Installation instructions for the module in English and Russian.

Module Functions:

PHP function Description
Working with data
iris_set($node, value)
 
Setting a node value.
  1. iris_set($global, $subscript1, ..., $subscriptN, $value); iris_set($global, $value);
    Returns: true or false (in the case of an error). All parameters of this function are strings or numbers. The first one is the name of the global, then there are the indexes, and the last parameter is the value.  
    iris_set('^time',1);
    iris_set('^time', 'tree', 1, 1, 'value');
    ObjectScript equivalent:  
    Set ^time = 1
    Set ^time("tree", 1, 1) = "value"
  2. iris_set($arrayGlobal, $value);
    There are just two parameters: the first one is the array in which the name of the global and all its indexes are stored, and the second one is the value.  
    $node = ['^time', 'tree', 1, 1];
    iris_set($node,'value');
iris_get($node)
 
Getting a node value.
Returns: a value (a number or a line), NULL (the value is not defined), or FALSE (in the event of an error).

 

  1. iris_get($global, $subscript1, ..., $subscriptN); iris_get($global);
    All parameters of this function are lines or numbers. The first one is the name of the global, and the rest are subscripts. The global may not have subscripts.  
    $res = iris_get('^time');
    $res1 = iris_get('^time', 'tree', 1, 1);
  2. iris_get($arrayGlobal);
    The only parameter is the array in which the name of the global and all its subscripts are stored.  
    $node = ['^time', 'tree', 1, 1];
    $res = iris_get($node);
iris_zkill($node)
 
Deleting a node value.
Returns: TRUE or FALSE (in the event of an error).

 

It is important to note that this function only deletes the value in the node and does not affect lower branches.

 

  1. iris_zkill($global, $subscript1, ..., $subscriptN); iris_zkill($global);
    All parameters of this function are lines or numbers. The first one is the name of the global, and the rest are subscripts. The global may not have subscripts.  
    $res = iris_zkill('^time'); // Lower branches are not deleted.
    $res1 = iris_zkill('^time', 'tree', 1, 1);
  2. iris_zkill($arrayGlobal);
    The only parameter is the array in which the name of the global and all its subscripts are stored.  
    $a = ['^time', 'tree', 1, 1];
    $res = iris_zkill($a);
iris_kill($node)
 
Deleting a node and all descendant branches.
Returns: TRUE or FALSE (in the case of an error).

 

  1. iris_kill($global, $subscript1, ..., $subscriptN); iris_kill($global);
    All parameters of this function are lines or numbers. The first one is the name of the global, and the rest are indexes. The global may not have indexes, in which case it is deleted in full.  
    $res1 = iris_kill('^example', 'subscript1', 'subscript2');
    $res = iris_kill('^time'); // The global is deleted in full.
    
  2. iris_kill($arrayGlobal);
    The only parameter is the array in which the name of the global and all its subscripts are stored.  
    $a = ['^time', 'tree', 1, 1];
    $res = iris_kill($a);
iris_order($node)
 
Traversal the branches of the global on a given level
Returns: the array in which the full name of the previous node of the global on the same level is stored or FALSE (in the case of an error).

 

  1. iris_order($global, $subscript1, ..., $subscriptN);
    All parameters of this function are strings or numbers. The first one is the name of the global, and the rest are subscripts. Form of usage in PHP and ObjectScript equivalent:  
    iris_order('^ccc','new2','res2'); // $Order(^ccc("new2", "res2"))
  2. iris_order($arrayGlobal);
    The only parameter is the array in which the name of the global and the subscripts of the initial node are stored.
    $node = ['^inn', '1234567890', 'city'];
    for (; $node !== NULL; $node = iris_order($node))
    {
      echo join(', ', $node).'='.iris_get($node)."\n";
    }
    Returns:
    
    ^inn, 1234567890, city=Moscow
    ^inn, 1234567890, year=1970
iris_order_rev($node)
 
Traversal the branches of the global on a given level in reverse order
Returns: the array in which the full name of the previous node of the global on the same level is stored or FALSE (in the case of an error).

 

  1. iris_order_rev($global, $subscript1, ..., $subscriptN);
    All parameters of this function are lines or numbers. The first one is the name of the global, and the rest are subscripts.   Form of usage in PHP and ObjectScript equivalent:
    iris_order_rev('^ccc','new2','res2'); // $Order(^ccc("new2", "res2"), -1)
  2. iris_order_rev($arrayGlobal);
    The only parameter is the array in which the name of the global and the subscripts of the initial node are stored.  
    $node = ['^inn', '1234567890', 'name', 'last'];
    for (; $node !== NULL; $node = iris_order_rev($node))
    {
      echo join(', ', $node).'='.iris_get($node)."\n";
    }
    Returns:
    
    ^inn, 1234567890, name, last=Ivanov
    ^inn, 1234567890, name, first=Vladimir
iris_query($CmdLine)
 
Traversal of the global in depth
Returns: the array in which the full name of the lower node (if available) or the next node of the global (if there is no embedded node) is contained.

 

  1. iris_query($global, $subscript1, ..., $subscriptN);
    All parameters of this function are strings or numbers. The first one is the name of the global, and the rest are subscripts.   Form of usage in PHP and ObjectScript equivalent:
    iris_query('^ccc', 'new2', 'res2'); // $Query(^ccc("new2", "res2"))
  2. iris_query($arrayGlobal);
    The only parameter is the array in which the name of the global and the indexes of the initial node are stored.  
    $node = ['^inn', 'city'];
    for (; $node !== NULL; $node = iris_query($node))
    {
      echo join(', ', $node).'='.iris_get($node)."\n";
    }
    Returns:
    ^inn, 1234567890, city=Moscow
    ^inn, 1234567890, city, street=Req Square
    ^inn, 1234567890, city, street, house=1
    ^inn, 1234567890, name, first=Vladimir
    ^inn, 1234567890, name, last=Ivanov
    ^inn, 1234567890, year=1970
The order differs from the order in which we established it because everything is automatically sorted in ascending order in the global during insertion.
Service functions
iris_set_dir($FullPath)
 
Setting up a directory with a database
Returns: TRUE or FALSE (in the case of an error).

 

iris_set_dir('/InterSystems/Cache/mgr');
This must be performed before connecting to the database.
iris_exec($CmdLine)
 
Execute database command
Returns: TRUE or FALSE (in the case of an error).
iris_exec('kill ^global(6)'); // The ObjectScript command for deleting a global
iris_connect($login, $pass) Connect to database
iris_quit() Close connection with DB
iris_errno() Get error code
iris_error() Get text description of error

 

 
If you want to play around with the module, check e.g. docker container implementation

git clone https://github.com/intersystems-community/php_ext_iris
cd php_ext_iris/iris
docker-compose build
docker-compose up -d
Test the demo page on localhost:52080 in the browser.

PHP files that can be edited and played with are in the php/demo folder which will be mounted to inside the container.

To test IRIS use the admin login with the SYS password.

To get into the IRIS settings, use the following URL:
http://localhost:52773/csp/sys/UtilHome.csp

To get into the IRIS console of this container, use the following command:


docker exec -it iris_iris_1 iris session IRIS
 

Especially for DC and those who wants to use we run a virtual machine with a Caché php-module was set up.

Demo page on english. Demo page on russian. Login: habr_test Password: burmur#@8765
 
For self-installation of the module for InterSystems Caché
  1. Have Linux. I tested for Ubuntu, the module should also compiled and work under Windows, but I didn’t test it.
  2. Download the free version:
    • InterSystems Caché (registration required). As to Linux, Red Hat and Suse are supported out of the box, but you can also install them on other distribution packages.
  3. Install the cach.so module in PHP according to the instructions..

Just out of interest, I ran two primitive tests to check the speed of inserting new values into the database in the docker container on my PC (AMD FX-9370@4700Mhz 32GB, LVM, SATA SSD).

  • Insertion of 1 million new nodes into the global took 1.81 seconds or 552K inserts per second.
  • Updating a value in the same global 1,000,000 times took 1.98 seconds or 505K updates per second. An interesting fact is that the insertion occurs faster than the update. Apparently this is a consequence of the initial optimization of the database aimed at quick insertion.

Obviously, these tests cannot be considered 100% accurate or useful, since they are primitive and are done in the container. On more powerful hardware with a disk system on a PCIe SSD, tens of millions of inserts per second can be achieved.

What else can be completed and the current state

  1. Useful functions for working with transactions can be added (you can still use them with iris_exec).
  2. The function of returning the whole global structure is not implemented, so as not to traverse the global from PHP.
  3. The function of saving a PHP array as a subtree is not implemented.
  4. Access to local database variables is not implemented. Only using iris_exec, although it's better with iris_set.
  5. Global traversal in depth in the opposite direction is not implemented.
  6. Access to the database via an object using methods (similar to current functions) is not implemented.

The current module is not quite yet ready for production: not tested for high loads and memory leaks. However, should someone need it, please feel free to contact me at any time (Sergey Kamenev sukamenev@gmail.com).

Bottom line

For a long time, the worlds of PHP and hierarchical databases on globals practically did not overlap, although globals provide strong and fast functionality for specific data types (medical, personal).

I hope that this module will motivate PHP programmers to experiment with globals and ObjectScript programmers for simple development of web interfaces in PHP.

P.S. Thank you for your time!

#Key Value #ObjectScript #InterSystems IRIS #Open Exchange
Check the related application on InterSystems Open Exchange

Source URL:https://community.intersystems.com/post/php-module-working-hierarchical-data-intersystems-iris