Configuration (Magic) Data

From IHRIS Wiki
Revision as of 11:26, 28 September 2013 by Karla Matus (talk | contribs)

Los Datos Magic son un mecanismo para manejar datos dinámicos de congifuracion a nivel del sitio. Es la base de mucha de la funcionalidad que brinda el Intrahealth Informatics Core Engine (I2CE) incluyendo como se sirven las páginas y como se realizan los informes personalizados.

Advertencia: El API para los datos Magic se ha actualizado de manera significativa de la version 3.1 a la versión 4.0. Aunque este artículo aplica a la versión API 4.0, mucho de esta es relevante para la version 3.1. Vea algunos de los Cambios

¿Qué son los Datos Magic?

Los Datos Magic son una estructura de arbol con raíz con beneficios. Si usted lo desea, puede pensar en ellos como el análogo del Registro de Windows para su aplicación en la red. También los puede ver como un arreglo anidado.

Los datos magic tienen dos partes. La clase del nodo de magic data, definido en I2CE/lib/I2CE_MagicDataNode y los mecanismos de almacenamiento para los datos magic. Usted puede utilizar los datos magic sin utilizar un mecanismo de almacenamiento, en tal caso los datos magic guardados no persisten a lo largo de todas las sesiones. Por defecto utiliza los siguientes mecanismos de almacenamiento para los datos Magic:

  • Base de datos: Los datos se guardan en una table en la base de datos. En I2CE, esta se establece como la table config .
  • APC: Los datos se almacenan en un caché de memoria proporcionado por http://pecl.php.net/package/APC apc] que persiste a lo largo de las sesiones apache.

Dado que APC es más rápido, las lecturas se realizan primero desde APC. Si los datos no se encuentran ahí, se lee desde la base de datos. Los datos se escriben primero en la base de datos y después en APC.

Escalar y No-Escalar

Hay dos tipos principals de nodos escalares y un nodo primario

Un nodo escalar no tiene nodos secundarios. Tiene un valor que es posiblemente una cadena vaciía. Un nodo escalar uede marcarse como localizado. En tal caso el valor que regresa depende de las preferencias de localización del usuario.

Un nod primario puede tener tantos nodos secundarios como quiera. Cada nodo secundario de un primario debe tener names diferentes, no tiene un valor y no es localozable.

Nombres y Rutas

Con la exepción del nodo raíz, cada nodo de datos magic tiene un nombre. Para el nombre se permite cualquier valor numeric. Cualquier cadena que no esté vacía es válida siempre y cuando no empiece con '=' no contenga una '/' y no sea '.' o '..'. Sin embargo, es mejor si se limita a que las cadenas contengan solamente los siguientes caractéres:

_-+.0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ

Rutas absolutas

Los nodos de los Datos Magic pueden referenciarse por su ruta, la que es una concatenación de sus nombres por '/.'

El nodo raíz tiene la ruta '/'.

Si el nodo raíz tiene un secundario con el nombre 'alguna', se referencia por la ruta '/alguna'

Si alguna es un nodo primario con un secundario de nombre 'cosa', entonces ese secundario se referencia por la ruta '/alguna/cosa'

Rutas Relativas

Las rutas también pueden ser relativas. en el ejemplo anterior, si estuviera en el nodo '/alguna' entonces podría referenciar a los otros nodos con:

  • './' referencia '/alguna'
  • '../' referencia '/'
  • './cosa' referencia al nodo '/alguna/cosa'

Definición de Datos Magic en los Archivos de Configuración

Una forma conveniente de cargar datos magic en el sistema es hacerlo a través de un module configuration file especificando un nodo de <configurationGroup> después del nodo de <metadata>. Esta sección tiene un doble propósito. Le permite especificar datos magic asi como proporcionar una manera de editar los datos del módulo.

El nodo de <configurationGroup> es opcional. Si está presente debe tener el nombre del atributo que tiene el mismo valor que el nombre del modulo, que es el nombre del atributo en la etiqueta que contiene <I2CEConfiguration>.

Todos los datos magic son relativos a la ruta definida por este configurationGroup. Hay tres opciones:

  • La ruta del atributo no esta presente. en el siguiente ejempli,los datos magic se guardan bajo /modules/mercury_javascript_path.
Ejemplo:
 <configurationGroup name='mercury_javascript_popup'>
   SOME STUFF GOES HERE
</configurationGroup>
  • La ruta del atributo está pressente. En el siguiente ejemplo, los datos magic se guardan en /algun/otro/lugar.
Ejemplo:
<configurationGroup name='mercury_javascript_popup' path='/algun/otro/lugar'>
  SOME STUFF GOES HERE
</configurationGroup> 
  • El módulo es 'I2CE'. Los datos magic se guardan relativos a /I2CE

En el resto de esta sección decribiremos WHAT STUFF GOES THERE que son <configurationGroup> y <configurations> etiquetas.

<configurationGroup>

Una <configurationGroup> puede tener varias subetiquetas en este orden:

  • Un <displayName> opcional. Un nombre mostrado en la configuración del módulo para esta agrupación de datos
  • Una <description> opcional. Una descripción de la funcionalidad de la agrupación.
  • Una <version> opcional de la etiqueta.
  • Cualquier número (incluyendo al cero) de etiquetas de <status>.
  • Cualquier número (incluyendo al cero)de <configurationGroup> o etiquetas de <configuration>.

<configuration>

Una <configuration> puede tener varias subetiquetas en este orden:

  • Un <displayName> opcional. Un nombre mostrado en la configuración de este modulo para esa agrupación de datos
  • Una <description> opcional. Una descripción de la funcionalidad de la agrupación.
  • Una etiqueta de [#<version>|<version>]] opcional.
  • Cualquier número (incluyendo al cero) de etiquetas de <value>.

Attributos

Hay varios atributos que pueden tener una<configuration> y una <configurationGroup>:

  • name: Este es un atributo requerido. Cada <configuration> o <configurationGruop> secundaria de un <configurationGroup> debe tener un nombre distinto. Si la ruta del atributo no se ha establecido, también dice que este nodo de configuración no debe aplicarse al nodo de los datos magic con el nombre dado y que es un nodo secundario de datos magic del nodo primario de configurationGroup.
  • path: Esto es opcional Puede ser una ruta absoluta u relative en los datos magic y describe los datos magic en ls cuales se debe guarder este valor. Si esta es una ruta relativa, es relative a la ruta de datos magic de su nodo primario.
  • locale: Esto es opcional. Si esta establecido, significa que el valor bajo este nodo debe considerarse como localizable.
  • config: Esto es opcional. Si esta establecido, establece el objeto de I2CE_Swiss que se utiliza par mostrar los datos en el menu para configurar los módulos.

Una <configuration> puede también tener los siguientes atributos:

  • type: Por defecto es establece como 'string' y describe el tipo de datos establecidos por las etiquetas de <value> de este nodo.
  • values: Por defecto se establece como 'single' y describe si los datos establecidos por este nodo Deben ser un arreglo de valores o un único valor establecido en lo que se almacena en el nodo de <values>

<value>

La etiqueta de <value> es ua subetiqueta de una etiqueta de <configuration> . Contiene el o los valores que se almacenan en los datos magic y depende de los attributes de type y values.

Algunos tipos y valores communes son:

  • type='string' values='single': The magic data node is a scalar type with value the contents of the single <value> tag.
  • type='string' values='many': The magic data node is a parent type. The magic data node has a child node of scalar type for each <value> tag.
  • type='delimited': The magic data node is a parent type. The value tags are expected to be of the form <value>'key':'value'</value> in which case a child magic data node is created of scalar type with name 'key' and value 'value'
  • type='boolean': The values in the <value> tag are interpreted as booleans: F,f,False,false,0, etc. are stored in magic data is 0. Otherwise the value stored is 1.


<version>

The same magic data can be accessed by multiple modules configuration files. Suppose that a module moduleA requires a module moduleB and that they both set /some/data to have values valA and valB respectively. Suppose that both modules have version 1.0.

Excerpt from moduleA's configuration file
<configurationGroup name='some' path='/some'>
 <configuration name='data' >
   <value>valA</value>
 </configuration>
 <configuration name='data2'> 
   <value>valA2</value>
 </configuration>
</configurationGroup>
Excerpt from moduleB's configuration file
<configurationGroup name='some' path='/some'>
 <configuration name='data'>
   <value>valB</value>
 </configuration>
 <configuration name='data2'> 
   <value>valB2</value>
 </configuration>
</configurationGroup>


On site intialization, since moduleA requires moduleB, the value is first set to valB is first set by moduleB. It is then overwritten to be the value valA by moduleA. Similarly, after initialization, the value of '/some/data2' is 'valA2'

Suppose that version of moduleB is increased to version 1.1 but there are no other changes to the configuration file. This will cause the configuration file to be reprocessed. The configurator will remember that has already processed all the data up to and including version 1.0. Thus it will not re-read the or overwrite what is already stored in magic data.

Suppose now that moduleB wants to change the value it stores at /some/data to be newValB, as well as create a new value at /some/other_data We would need to increase the version number of the module to 1.2 and add a <version> tag so that the configurator knows that in upgrading the module to version 1.2, we should reread the configuration data for anything greater than the previously loaded version of 3.1:

<configurationGroup name='some' path='/some'>
 <configuration name='data'>
   <version>1.2</version>
   <value>newValB</value>
 </configuration>
 <configuration name='other_data'>
   <version>1.2</version>
   <value>The new stuff</value>
 </configuration>
 <configuration name='data2'> 
   <value>valB2</value>
 </configuration>
</configurationGroup>

Now the value at /some/data will be updated to be 'newValB' and we will add in the value 'The new stuff' at '/some/other_data.' The value at '/some/data2' remains unchanged and is 'valA2.'

<status>

A status tag consists of key value pairs:

<status>key:value</status>

Although you can use anything for the key (as long as it does not have a ':' in it!), the keys which have meaning are:

  • version: Functions just like <version>
  • overwrite: The value can be either true of false. Defaults to false. If true the magic data will be overwritten even if the version has not.
  • merge: The value can be either true of false. Defaults to false. If true, the values read in are merged into the existing values by array_merge()
  • mergerecursive: The value can be either true of false. Defaults to false. If true, the values read in are merged into the existing by I2CE_Util::merge_recursive().
  • uniquemerge: The value can be either true of false. Defaults to false except in the case where a <configuration> node has type='string' and values='many.' If true, the values read in are merged into the existing values by I2CE_Util::merge_recursive() and only the unique values are kept by I2CE_Util::array_unique().
  • visible: The value can be either true of false. Defaults to true. If true, this node is displayed in the module configuration menu.
  • advanced: The value can be either true of false. Defaults to false. If true, this is considered an an advanced option for the module configuration menu
  • required: The value can be either true of false. Defaults to true. It says the the resulting values at the <configuration> node must be set
  • showIndex: The value can be either true of false. Defaults to true. In which case we show the index in the module configuration menu


The values of the status keys are inherited as you go down a node.

Using Magic Data in PHP

Each node of magic data is an instance of the class I2CE_MagicDataNode. The "public variables" of a node are its child nodes which is done by making use of the __get() method. I2CE_MagicDataNode implements the RecusriveIterator, ArrayAccess, SeekableIterator, and Countable Interfaces.


I2CE has a root magic data instance which can be retrieved by:

$config=I2CE::getConfig();

Basic Access

Suppose $data is a magic data node, with a children named 'my_list' and 'amount' which are parent type and scalar type respectively. Suppose that the child node 'amount' has value 10. Suppose that there is no child named 'bad.' The children can be accessed in many ways:

Access Method Result Notes
$data->my_list I2CE_MagicDataNode This is the 'my_list' node
$data->amount 10
$data->bad I2CE_MagicDataNode The node did not exist, so it was created.
It has an indeterminate type at the moment.
$data['my_list'] I2CE_MagicDataNode the 'my_list' node
$data['amount'] 10 the value of the 'amount' node
$data['bad'] I2CE_MagicDataNode We created the non-existent 'bad' node and returned it

Refined Access

To get more refined access to magic data nodes you may use the traverse($path,$create=false,$return_value=true) function:

$data->traverse('my_list',false,false) I2CE_MagicDataNode This is the 'my_list' node
$data->traverse('bad',false,false) null The second argument says not to create a node that doesn't exist
$data->traverse('amount',false,false) I2CE_MagicDataNode The is the 'amount' node
$data->traverse('my_list',false,true) I2CE_MagicDataNode This is the 'my_list' node
$data->traverse('amount',false,true) 10 The value of the 'amount' node
$data->traverse('bad',false,true) null The second argument says not to create a node that doesn't exist
$data->traverse('my_list',true,true) I2CE_MagicDataNode This is the 'my_list' node
$data->traverse('amount',true,true) 10 The value of the 'amount' node
$data->traverse('bad',true,true) I2CE_MagicDataNode We created the non-existent 'bad' node and returned it

If a node has scalar type, you can get its value by getValue(). If you call getAsArray() on it, it will also return its value.

If a node has parent or indeterminate type, calling getValue() returns the node itself. If you call getAsArray() on it it will return a nested array. The keys at each depth are the child node's names. The values are either an array or a sting, depending on if that child is scalar or not.

Checking Existence and Type

You can use the following method to see if a Magic Data node, exists and what its type is:

  • pathExists($path)
  • is_scalar($path=null)
  • is_parent($path=null)
  • is_indeterminate($path=null)
  • is_root($path=null)

Here, we the path defaults to null, the value that method is called on the node itself (this would be the same as calling it on $path='./').

You may do something like:

function set_node_to_scalar($node) {
  if (!$node instanceof I2CE_MagicDataNode) {
    echo "Why are you giving me garbage data?\n";
    return false;
  } 
  if ($node->is_scalar()) {
    echo "This node is already a scalar.  It has a value " . $node->getValue(). "\nI don't need to do anything.\n";
    return true; 
  } else {
    echo "This node is a parent node.  Although it may or may not have children, I can't set it to be scalar.\n";
    return false;
  } else{
    //$node->is_indeterminate() will return true
    echo "This node is indeterminate. Setting it to be scalar\n";
    $node->set_scalar();
    return true;
  }
}

Two other useful functions are

  • getAsArray($path=null) which return the node and all of its children (recursively) as an array
  • setIfIsSet(&$var,$path,$as_array=false) which will check to see if $path exists. If it does not, it returns false. If it does, it returns true and calls either getValue() or getAsArray() on the node referred to by the path.

Child Names and Iterators

To get the names of the child nodes of a node, we use the getKeys() method. Suppose that we magic data set up as follows:

Path Type Value
/ parent NONE
/color scalar red
/modules parent NONE
/modules/modA parent NONE
/modules/modB parent NONE
/modules/modA/favorite_clay_animation scalar Mr. Bill

You may also something similar as:

echo "I like the color " . $config->color . "\n";
$keys = $config->getKeys();
foreach ($keys as $key) {
  if ($config->is_parent($key)) {
    echo "The node named $key under at " . $config->getPath(false) . " is a parent node.  It has children " . implode(',', $config->$key->getKeys()) . ".\n";
  } else if ($config->is_scalar($key)) {
   echo "The node named $key under at " . $config->getPath(false) . " is a scalar with value " . $config->$key ".\n";
  }
}

which would result in:

I like the color red.
The node named modules under / is a parent node.  It has children modA,modB.
The node named color under / is a scalar node with value red.


As a magic data node is an iterator, we can do things like:

foreach ($config as $key=>$node) {
  if ($node instanceof I2CE_MagicDataNode) {
   echo "The node named $key under at " . $config->getPath(false) . " is a parent node.  It has children " . implode(',', $node->getKeys()) . ".\n";
  } else {
   echo "The node named $key under at " . $config->getPath(false) . " is a scalar with value " . $node .".\n";
  }
}

which would result in:

The node named modules under / is a parent node.  It has children modA,modB.
The node named color under / is a scalar node with value red.

or:

$modules = $config->modules;
foreach ($modules as $module=>$data) {
  if ($data->is_scalar('favorite_clay_animation')) {
     echo "The module $module thinks " . $data->favorite_clay_animation . " is a super star!\n";
  }
}

would result in:

 The module modA things Mr. Bill is a super star!

Changes from 3.1

  • Removed the __ from method calls.
  • Relaxed the rules for the names of Magic Data nodes.
  • Implemented the various interfaces
  • Added in support for localization of values