Config
[ class tree: Config ] [ index: Config ] [ all elements ]

Source for file Container.php

Documentation is available at Container.php

  1. <?php
  2. // +---------------------------------------------------------------------+
  3. // | PHP Version 4                                                       |
  4. // +---------------------------------------------------------------------+
  5. // | Copyright (c) 1997, 1998, 1999, 2000, 2001 The PHP Group            |
  6. // +---------------------------------------------------------------------+
  7. // | This source file is subject to version 2.0 of the PHP license,      |
  8. // | that is bundled with this package in the file LICENSE, and is       |
  9. // | available at through the world-wide-web at                          |
  10. // | http://www.php.net/license/2_02.txt.                                |
  11. // | If you did not receive a copy of the PHP license and are unable to  |
  12. // | obtain it through the world-wide-web, please send a note to         |
  13. // | license@php.net so we can mail you a copy immediately.              |
  14. // +---------------------------------------------------------------------+
  15. // | Author: Bertrand Mansion <bmansion@mamasam.com>                     |
  16. // +---------------------------------------------------------------------+
  17. //
  18. // $Id: Container.php,v 1.32 2004/06/04 09:57:02 mansion Exp $
  19.  
  20. require_once('Config.php');
  21.  
  22. /**
  23. * Interface for Config containers
  24. *
  25. @author   Bertrand Mansion <bmansion@mamasam.com>
  26. @package  Config
  27. */
  28.  
  29.     /**
  30.     * Container object type
  31.     * Ex: section, directive, comment, blank
  32.     * @var  string 
  33.     */
  34.     var $type;
  35.  
  36.     /**
  37.     * Container object name
  38.     * @var  string 
  39.     */
  40.     var $name = '';
  41.  
  42.     /**
  43.     * Container object content
  44.     * @var  string 
  45.     */
  46.     var $content = '';
  47.  
  48.     /**
  49.     * Container object children
  50.     * @var  array 
  51.     */
  52.     var $children = array();
  53.  
  54.     /**
  55.     * Reference to container object's parent
  56.     * @var  object 
  57.     */
  58.     var $parent;
  59.     
  60.     /**
  61.     * Array of attributes for this item
  62.     * @var  array 
  63.     */
  64.     var $attributes;
  65.  
  66.     /**
  67.     * Unique id to differenciate nodes
  68.     *
  69.     * This is used to compare nodes
  70.     * Will not be needed anymore when this class will use ZendEngine 2
  71.     *
  72.     * @var  int 
  73.     */
  74.     var $_id;
  75.  
  76.     /**
  77.     * Constructor
  78.     *
  79.     * @param  string  $type       Type of container object
  80.     * @param  string  $name       Name of container object
  81.     * @param  string  $content    Content of container object
  82.     * @param  array   $attributes Array of attributes for container object
  83.     */
  84.     function Config_Container($type 'section'$name ''$content ''$attributes = null)
  85.     {
  86.         $this->type       = $type;
  87.         $this->name       = $name;
  88.         $this->content    = $content;
  89.         $this->attributes = $attributes;
  90.         $this->parent     = null;
  91.         $this->_id        uniqid($name.$typetrue);
  92.     // end constructor
  93.  
  94.     /**
  95.     * Create a child for this item.
  96.     * @param  string  $type       type of item: directive, section, comment, blank...
  97.     * @param  mixed   $item       item name
  98.     * @param  string  $content    item content
  99.     * @param  array   $attributes item attributes
  100.     * @param  string  $where      choose a position 'bottom', 'top', 'after', 'before'
  101.     * @param  object  $target     needed if you choose 'before' or 'after' for where
  102.     * @return object  reference to new item or Pear_Error
  103.     */
  104.     function &createItem($type$name$content$attributes = null$where 'bottom'$target = null)
  105.     {
  106.         $item =new Config_Container($type$name$content$attributes);
  107.         $result =$this->addItem($item$where$target);
  108.         return $result;
  109.     // end func &createItem
  110.     
  111.     /**
  112.     * Adds an item to this item.
  113.     * @param  object   $item      a container object
  114.     * @param  string   $where     choose a position 'bottom', 'top', 'after', 'before'
  115.     * @param  object   $target    needed if you choose 'before' or 'after' in $where
  116.     * @return mixed    reference to added container on success, Pear_Error on error
  117.     */
  118.     function &addItem(&$item$where 'bottom'$target = null)
  119.     {
  120.         if ($this->type != 'section'{
  121.             return PEAR::raiseError('Config_Container::addItem must be called on a section type object.'nullPEAR_ERROR_RETURN);
  122.         }
  123.         if (is_null($target)) {
  124.             $target =$this;
  125.         }
  126.         if (strtolower(get_class($target)) != 'config_container'{
  127.             return PEAR::raiseError('Target must be a Config_Container object in Config_Container::addItem.'nullPEAR_ERROR_RETURN);
  128.         }
  129.  
  130.         switch ($where{
  131.             case 'before':
  132.                 $index $target->getItemIndex();
  133.                 break;
  134.             case 'after':
  135.                 $index $target->getItemIndex()+1;
  136.                 break;
  137.             case 'top':
  138.                 $index = 0;
  139.                 break;
  140.             case 'bottom':
  141.                 $index = -1;
  142.                 break;
  143.             default:
  144.                 return PEAR::raiseError('Use only top, bottom, before or after in Config_Container::addItem.'nullPEAR_ERROR_RETURN);
  145.         }
  146.         if (isset($index&& $index >= 0{
  147.             array_splice($this->children$index0'tmp');
  148.         else {
  149.             $index count($this->children);
  150.         }
  151.         $this->children[$index=$item;
  152.         $this->children[$index]->parent =$this;
  153.  
  154.         return $item;
  155.     // end func addItem
  156.  
  157.     /**
  158.     * Adds a comment to this item.
  159.     * This is a helper method that calls createItem
  160.     *
  161.     * @param  string    $content        Object content
  162.     * @param  string    $where          Position : 'top', 'bottom', 'before', 'after'
  163.     * @param  object    $target         Needed when $where is 'before' or 'after'
  164.     * @return object  reference to new item or Pear_Error
  165.     */
  166.     function &createComment($content ''$where 'bottom'$target = null)
  167.     {
  168.         return $this->createItem('comment'null$contentnull$where$target);
  169.     // end func &createComment
  170.  
  171.     /**
  172.     * Adds a blank line to this item.
  173.     * This is a helper method that calls createItem
  174.     *
  175.     * @return object  reference to new item or Pear_Error
  176.     */
  177.     function &createBlank($where 'bottom'$target = null)
  178.     {
  179.         return $this->createItem('blank'nullnullnull$where$target);
  180.     // end func &createBlank
  181.  
  182.     /**
  183.     * Adds a directive to this item.
  184.     * This is a helper method that calls createItem
  185.     *
  186.     * @param  string    $name           Name of new directive
  187.     * @param  string    $content        Content of new directive
  188.     * @param  mixed     $attributes     Directive attributes
  189.     * @param  string    $where          Position : 'top', 'bottom', 'before', 'after'
  190.     * @param  object    $target         Needed when $where is 'before' or 'after'
  191.     * @return object  reference to new item or Pear_Error
  192.     */
  193.     function &createDirective($name$content$attributes = null$where 'bottom'$target = null)
  194.     {
  195.         return $this->createItem('directive'$name$content$attributes$where$target);
  196.     // end func &createDirective
  197.  
  198.     /**
  199.     * Adds a section to this item.
  200.     *
  201.     * This is a helper method that calls createItem
  202.     * If the section already exists, it won't create a new one.
  203.     * It will return reference to existing item.
  204.     *
  205.     * @param  string    $name           Name of new section
  206.     * @param  array     $attributes     Section attributes
  207.     * @param  string    $where          Position : 'top', 'bottom', 'before', 'after'
  208.     * @param  object    $target         Needed when $where is 'before' or 'after'
  209.     * @return object  reference to new item or Pear_Error
  210.     */
  211.     function &createSection($name$attributes = null$where 'bottom'$target = null)
  212.     {
  213.         return $this->createItem('section'$namenull$attributes$where$target);
  214.     // end func &createSection
  215.  
  216.     /**
  217.     * Tries to find the specified item(s) and returns the objects.
  218.     *
  219.     * Examples:
  220.     * $directives =& $obj->getItem('directive');
  221.     * $directive_bar_4 =& $obj->getItem('directive', 'bar', null, 4);
  222.     * $section_foo =& $obj->getItem('section', 'foo');
  223.     *
  224.     * This method can only be called on an object of type 'section'.
  225.     * Note that root is a section.
  226.     * This method is not recursive and tries to keep the current structure.
  227.     * For a deeper search, use searchPath()
  228.     *
  229.     * @param  string    $type        Type of item: directive, section, comment, blank...
  230.     * @param  mixed     $name        Item name
  231.     * @param  mixed     $content     Find item with this content
  232.     * @param  array     $attributes  Find item with attribute set to the given value
  233.     * @param  int       $index       Index of the item in the returned object list. If it is not set, will try to return the last item with this name.
  234.     * @return mixed  reference to item found or false when not found
  235.     * @see &searchPath()
  236.     */
  237.     function &getItem($type = null$name = null$content = null$attributes = null$index = -1)
  238.     {
  239.         if ($this->type != 'section'{
  240.             return PEAR::raiseError('Config_Container::getItem must be called on a section type object.'nullPEAR_ERROR_RETURN);
  241.         }
  242.         if (!is_null($type)) {
  243.             $testFields['type';
  244.         }
  245.         if (!is_null($name)) {
  246.             $testFields['name';
  247.         }
  248.         if (!is_null($content)) {
  249.             $testFields['content';
  250.         }
  251.         if (!is_null($attributes&& is_array($attributes)) {
  252.             $testFields['attributes';
  253.         }
  254.  
  255.         $itemsArr = array();
  256.         $fieldsToMatch count($testFields);
  257.         for ($i = 0$count count($this->children)$i $count$i++{
  258.             $match = 0;
  259.             reset($testFields);
  260.             foreach ($testFields as $field{
  261.                 if ($field != 'attributes'{
  262.                     if ($this->children[$i]->$field == ${$field}{
  263.                         $match++;
  264.                     }
  265.                 else {
  266.                     // Look for attributes in array
  267.                     $attrToMatch count($attributes);
  268.                     $attrMatch = 0;
  269.                     foreach ($attributes as $key => $value{
  270.                         if (isset($this->children[$i]->attributes[$key]&&
  271.                             $this->children[$i]->attributes[$key== $value{
  272.                             $attrMatch++;
  273.                         }
  274.                     }
  275.                     if ($attrMatch == $attrToMatch{
  276.                         $match++;
  277.                     }
  278.                 }
  279.             }
  280.             if ($match == $fieldsToMatch{
  281.                 $itemsArr[=$this->children[$i];
  282.             }
  283.         }
  284.         if ($index >= 0{
  285.             if (isset($itemsArr[$index])) {
  286.                 return $itemsArr[$index];
  287.             else {
  288.                 return false;
  289.             }
  290.         else {
  291.             if ($count count($itemsArr)) {
  292.                 return $itemsArr[$count-1];
  293.             else {
  294.                 return false;
  295.             }
  296.         }
  297.     // end func &getItem
  298.  
  299.     /**
  300.     * Finds a node using XPATH like format:
  301.     * 
  302.     * search format = array ( item,item,item,item)
  303.     * item = 'string' .. will match the <string of the xml element
  304.     * item = array('string',array('name'=>'xyz'))
  305.     *       .. will match <string name="xyz">
  306.     * 
  307.     * @param    mixed   Search path and attributes
  308.     * 
  309.     * @return   mixed   Config_Container object, array of Config_Container objects or false on failure.
  310.     * @access   public
  311.     */
  312.     function &searchPath($args)
  313.     {
  314.         if ($this->type != 'section'{
  315.             return PEAR::raiseError('Config_Container::searchPath must be called on a section type object.'nullPEAR_ERROR_RETURN);
  316.         }
  317.  
  318.         $arg array_shift($args);
  319.  
  320.         if (is_array($arg)) {
  321.             $name $arg[0];
  322.             $attributes $arg[1];
  323.         else {
  324.             $name $arg;
  325.             $attributes = null;
  326.         }
  327.         // find all the matches for first..
  328.         $match =$this->getItem(null$namenull$attributes);
  329.  
  330.         if (!$match{
  331.             return false;
  332.         }
  333.         if (!empty($args)) {
  334.             return $match->searchPath($args);
  335.         }
  336.         return $match;
  337.     // end func &searchPath
  338.  
  339.     /**
  340.     * Returns how many children this container has
  341.     *
  342.     * @param  string    $type    type of children counted
  343.     * @param  string    $name    name of children counted
  344.     * @return int  number of children found
  345.     */
  346.     function countChildren($type = null$name = null)
  347.     {
  348.         if ($this->type != 'section'{
  349.             return PEAR::raiseError('Config_Container::getChildrenNum must be called on a section type object.'nullPEAR_ERROR_RETURN);
  350.         }
  351.         if (is_null($type&& is_null($name)) {
  352.             return count($this->children);
  353.         }
  354.         $count = 0;
  355.         if (isset($name&& isset($type)) {
  356.             for ($i = 0$children count($this->children)$i $children$i++{
  357.                 if ($this->children[$i]->name == $name && 
  358.                     $this->children[$i]->type == $type{
  359.                     $count++;
  360.                 }
  361.             }
  362.             return $count;
  363.         }
  364.         if (isset($type)) {
  365.             for ($i = 0$children count($this->children)$i $children$i++{
  366.                 if ($this->children[$i]->type == $type{
  367.                     $count++;
  368.                 }
  369.             }
  370.             return $count;
  371.         }
  372.         if (isset($name)) {
  373.             // Some directives can have the same name
  374.             for ($i = 0$children count($this->children)$i $children$i++{
  375.                 if ($this->children[$i]->name == $name{
  376.                     $count++;
  377.                 }
  378.             }
  379.             return $count;
  380.         }
  381.     // end func &countChildren
  382.  
  383.     /**
  384.     * Deletes an item (section, directive, comment...) from the current object
  385.     * TODO: recursive remove in sub-sections
  386.     * @return mixed  true if object was removed, false if not, or PEAR_Error if root
  387.     */
  388.     function removeItem()
  389.     {
  390.         if ($this->isRoot()) {
  391.             return PEAR::raiseError('Cannot remove root item in Config_Container::removeItem.'nullPEAR_ERROR_RETURN);
  392.         }
  393.         $index $this->getItemIndex();
  394.         if (!is_null($index)) {
  395.             array_splice($this->parent->children$index1);
  396.             return true;
  397.         }
  398.         return false;
  399.     // end func removeItem
  400.  
  401.     /**
  402.     * Returns the item index in its parent children array.
  403.     * @return int  returns int or null if root object
  404.     */
  405.     function getItemIndex()
  406.     {
  407.         if (is_object($this->parent)) {
  408.             // This will be optimized with Zend Engine 2
  409.             $pchildren =$this->parent->children;
  410.             for ($i = 0$count count($pchildren)$i $count$i++{
  411.                 if ($pchildren[$i]->_id == $this->_id{
  412.                     return $i;
  413.                 }
  414.             }
  415.         }
  416.         return;
  417.     // end func getItemIndex
  418.  
  419.     /**
  420.     * Returns the item rank in its parent children array
  421.     * according to other items with same type and name.
  422.     * @return int  returns int or null if root object
  423.     */
  424.     function getItemPosition()
  425.     {
  426.         if (is_object($this->parent)) {
  427.             $pchildren =$this->parent->children;
  428.             for ($i = 0$count count($pchildren)$i $count$i++{
  429.                 if ($pchildren[$i]->name == $this->name &&
  430.                     $pchildren[$i]->type == $this->type{
  431.                     $obj[=$pchildren[$i];
  432.                 }
  433.             }
  434.             for ($i = 0$count count($obj)$i $count$i++{
  435.                 if ($obj[$i]->_id == $this->_id{
  436.                     return $i;
  437.                 }
  438.             }
  439.         }
  440.         return;
  441.     // end func getItemPosition
  442.  
  443.     /**
  444.     * Returns the item parent object.
  445.     * @return object  returns reference to parent object or null if root object
  446.     */
  447.     function &getParent()
  448.     {
  449.         return $this->parent;
  450.     // end func &getParent
  451.  
  452.     /**
  453.     * Returns the item parent object.
  454.     * @return mixed  returns reference to child object or false if child does not exist
  455.     */
  456.     function &getChild($index = 0)
  457.     {
  458.         if (!empty($this->children[$index])) {
  459.             return $this->children[$index];
  460.         else {
  461.             return false;
  462.         }
  463.     // end func &getChild
  464.  
  465.     /**
  466.     * Set this item's name.
  467.     * @return void 
  468.     */
  469.     function setName($name)
  470.     {
  471.         $this->name = $name;
  472.     // end func setName
  473.  
  474.     /**
  475.     * Get this item's name.
  476.     * @return string    item's name
  477.     */
  478.     function getName()
  479.     {
  480.         return $this->name;
  481.     // end func getName
  482.  
  483.     /**
  484.     * Set this item's content.
  485.     * @return void 
  486.     */
  487.     function setContent($content)
  488.     {
  489.         $this->content = $content;
  490.     // end func setContent
  491.     
  492.     /**
  493.     * Get this item's content.
  494.     * @return string    item's content
  495.     */
  496.     function getContent()
  497.     {
  498.         return $this->content;
  499.     // end func getContent
  500.  
  501.     /**
  502.     * Set this item's type.
  503.     * @return void 
  504.     */
  505.     function setType($type)
  506.     {
  507.         $this->type = $type;
  508.     // end func setType
  509.  
  510.     /**
  511.     * Get this item's type.
  512.     * @return string    item's type
  513.     */
  514.     function getType()
  515.     {
  516.         return $this->type;
  517.     // end func getType
  518.  
  519.     /**
  520.     * Set this item's attributes.
  521.     * @param  array    $attributes        Array of attributes
  522.     * @return void 
  523.     */
  524.     function setAttributes($attributes)
  525.     {
  526.         $this->attributes = $attributes;
  527.     // end func setAttributes
  528.  
  529.     /**
  530.     * Set this item's attributes.
  531.     * @param  array    $attributes        Array of attributes
  532.     * @return void 
  533.     */
  534.     function updateAttributes($attributes)
  535.     {
  536.         if (is_array($attributes)) {
  537.             foreach ($attributes as $key => $value{
  538.                 $this->attributes[$key$value;
  539.             }
  540.         }
  541.     // end func updateAttributes
  542.  
  543.     /**
  544.     * Get this item's attributes.
  545.     * @return array    item's attributes
  546.     */
  547.     function getAttributes()
  548.     {
  549.         return $this->attributes;
  550.     // end func getAttributes
  551.     
  552.     /**
  553.     * Get one attribute value of this item
  554.     * @param  string   $attribute        Attribute key
  555.     * @return mixed    item's attribute value
  556.     */
  557.     function getAttribute($attribute)
  558.     {
  559.         if (isset($this->attributes[$attribute])) {
  560.             return $this->attributes[$attribute];
  561.         }
  562.         return null;
  563.     // end func getAttribute
  564.  
  565.     /**
  566.     * Set a children directive content.
  567.     * This is an helper method calling getItem and addItem or setContent for you.
  568.     * If the directive does not exist, it will be created at the bottom.
  569.     *
  570.     * @param  string    $name        Name of the directive to look for
  571.     * @param  mixed     $content     New content
  572.     * @param  int       $index       Index of the directive to set,
  573.     *                                 in case there are more than one directive
  574.     *                                 with the same name
  575.     * @return object    newly set directive
  576.     */
  577.     function &setDirective($name$content$index = -1)
  578.     {
  579.         $item =$this->getItem('directive'$namenullnull$index);
  580.         if ($item === false || PEAR::isError($item)) {
  581.             // Directive does not exist, will create one
  582.             unset($item);
  583.             return $this->createDirective($name$contentnull);
  584.         else {
  585.             // Change existing directive value
  586.             $item->setContent($content);
  587.             return $item;
  588.         }
  589.     // end func setDirective
  590.  
  591.     /**
  592.     * Is this item root, in a config container object
  593.     * @return bool    true if item is root
  594.     */
  595.     function isRoot()
  596.     {
  597.         if (is_null($this->parent)) {
  598.             return true;
  599.         }
  600.         return false;
  601.     // end func isRoot
  602.  
  603.     /**
  604.     * Call the toString methods in the container plugin
  605.     * @param    string  $configType  Type of configuration used to generate the string
  606.     * @param    array   $options     Specify special options used by the parser
  607.     * @return   mixed   true on success or PEAR_ERROR
  608.     */
  609.     function toString($configType$options = array())
  610.     {
  611.         $configType strtolower($configType);
  612.         if (!isset($GLOBALS['CONFIG_TYPES'][$configType])) {
  613.             return PEAR::raiseError("Configuration type '$configType' is not registered in Config_Container::toString."nullPEAR_ERROR_RETURN);
  614.         }
  615.         $includeFile $GLOBALS['CONFIG_TYPES'][$configType][0];
  616.         $className   $GLOBALS['CONFIG_TYPES'][$configType][1];
  617.         include_once($includeFile);
  618.         $renderer = new $className($options);
  619.         return $renderer->toString($this);
  620.     // end func toString
  621.  
  622.     /**
  623.     * Returns a key/value pair array of the container and its children.
  624.     *
  625.     * Format : section[directive][index] = value
  626.     * If the container has attributes, it will use '@' and '#'
  627.     * index is here because multiple directives can have the same name.
  628.     *
  629.     * @param    bool    $useAttr        Whether to return the attributes too
  630.     * @return array 
  631.     */
  632.     function toArray($useAttr = true)
  633.     {
  634.         $array[$this->name= array();
  635.         switch ($this->type{
  636.             case 'directive':
  637.                 if ($useAttr && count($this->attributes> 0{
  638.                     $array[$this->name]['#'$this->content;
  639.                     $array[$this->name]['@'$this->attributes;
  640.                 else {
  641.                     $array[$this->name$this->content;
  642.                 }
  643.                 break;
  644.             case 'section':
  645.                 if ($useAttr && count($this->attributes> 0{
  646.                     $array[$this->name]['@'$this->attributes;
  647.                 }
  648.                 if ($count count($this->children)) {
  649.                     for ($i = 0; $i $count$i++{
  650.                         $newArr $this->children[$i]->toArray($useAttr);
  651.                         if (!is_null($newArr)) {
  652.                             foreach ($newArr as $key => $value{
  653.                                 if (isset($array[$this->name][$key])) {
  654.                                     // duplicate name/type
  655.                                     if (!is_array($array[$this->name][$key]||
  656.                                         !isset($array[$this->name][$key][0])) {
  657.                                         $old $array[$this->name][$key];
  658.                                         unset($array[$this->name][$key]);
  659.                                         $array[$this->name][$key][0$old;
  660.                                     }
  661.                                     $array[$this->name][$key][$value;
  662.                                 else {
  663.                                     $array[$this->name][$key$value;
  664.                                 }
  665.                             }
  666.                         }
  667.                     }
  668.                 }
  669.                 break;
  670.             default:
  671.                 return null;
  672.         }
  673.         return $array;
  674.     // end func toArray
  675.     
  676.     /**
  677.     * Writes the configuration to a file
  678.     * 
  679.     * @param  mixed  $datasrc        Info on datasource such as path to the configuraton file or dsn...
  680.     * @param  string $configType     Type of configuration
  681.     * @param  array  $options        Options for writer
  682.     * @access public
  683.     * @return mixed     true on success or PEAR_ERROR
  684.     */
  685.     function writeDatasrc($datasrc$configType$options = array())
  686.     {
  687.         $configType strtolower($configType);
  688.         if (!isset($GLOBALS['CONFIG_TYPES'][$configType])) {
  689.             return PEAR::raiseError("Configuration type '$configType' is not registered in Config_Container::writeDatasrc."nullPEAR_ERROR_RETURN);
  690.         }
  691.         $includeFile $GLOBALS['CONFIG_TYPES'][$configType][0];
  692.         $className $GLOBALS['CONFIG_TYPES'][$configType][1];
  693.         include_once($includeFile);
  694.  
  695.         if (in_array('writeDatasrc'get_class_methods($className))) {
  696.             $writer = new $className($options);
  697.             return $writer->writeDatasrc($datasrc$this);
  698.         }
  699.  
  700.         // Default behaviour
  701.         $fp @fopen($datasrc'w');
  702.         if ($fp{
  703.             $string $this->toString($configType$options);
  704.             $len strlen($string);
  705.             @flock($fpLOCK_EX);
  706.             @fwrite($fp$string$len);
  707.             @flock($fpLOCK_UN);
  708.             @fclose($fp);
  709.             return true;
  710.         else {
  711.             return PEAR::raiseError('Cannot open datasource for writing.'1PEAR_ERROR_RETURN);
  712.         }
  713.     // end func writeDatasrc
  714. // end class Config_Container
  715. ?>

Documentation generated on Mon, 11 Mar 2019 10:17:12 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.