The Indexed Objects List Model (ILM) for OOP PHP Applications

jkon 2 Tallied Votes 826 Views Share

Although objects collections have a place in the PHP world many years now , their use is now flourishing because more and more PHP programmers turn to OOP. This tutorial is a sequel of https://www.daniweb.com/programming/web-development/tutorials/500118/a-simple-data-layer-for-oop-php-applications (we will refer to it as “previous tutorial”) and https://www.daniweb.com/programming/web-development/tutorials/500118/a-simple-data-layer-for-oop-php-applications , in order to understand what we are doing I would strongly recommend to read those (and even better to make your own tests).

Keeping objects of the same class in an array , in PHP isn't something new. We will do a brief explanation of what we will do using arrays , that most are already familiar with. Let's suppose that you have an array of objects Category and you want to get the object with property “id” that has value “12” (if any), how would you do it ?.

The obvious way would be to keep the id as a key to that array , but what if there is another unique property e.g. fullUrl and you want to get the object from the array that has fullUrl “news/internet” would you reindex the array to get that fullUrl? One solution could be to iterate through all objects of the array until (and if) you find the one that has property fullUrl “news/internet”. But if this would end up slow and using a lot of resources. One other way is to create an array where as keys you will have the fullUrl of the object and as values the position of it in the array of “Category” objects. Creating the same strategy using a multidimensional array you could do the same even for properties that are not unique in this array of objects e.g. parentId , in that way you could get all the the objects that has value in their property “parentId” lets say “14”. If those objects were PPO (see previous tutorial) that have data mainly from DB (through Object Instantiators) you would also need to update these arrays every time you modify the DB through Data Workers.

Indexed Objects List can be used even when there are no indexes , e.g. “Product” objects list of a page normally have no reason to have indexes (we retrieve those through pagination), but also there is a big chance that a “Product” object has (as a property) a variationsList ( List of “Variation” objects ) that is indexed. Also some times it has a meaning to keep the list with Application Scope Caching , e.g. there is no reason to retrieve from DB the list of “Category” objects in each request. Our application is modifying the categories table (probably through an admin) and there we can reset the categoriesList . How it will be used each times depends on the need. For example if the “Category” object had a property “txt” that was the html content (header) of the category we wouldn't have any reason to fill all the objects of the list with that.

Any model has limitations of how it can be used. For example if we had 5.000.000 folders in a filesystem there isn't a single answer on what to use, it all depends on what you want to do with them. There are some more complicated implementations of the Indexed List Model (even in the DB side) that would cover some needs , or even other Models that could cover others. We will not see more such specialized demands , we will move to the _List class.

The List Object

/**
 * A _List object is a collection of objects of the same
 * type that can be indexed. Implementing the IteratorAggregate interface
 * (and as such the Traversable) it can return an iterator for objects in
 * the list through getIterator or through foreach
 */
class _List extends _Object implements IteratorAggregate
{
    /**
     * The class name of the objects
     * @var String
     */
    private $objectsClassName;

    /**
     * Private static marks if an index is going to be unique (for object)
     * or for many (for sublist)
     * @var int
     */
    private static $indexForObject = 1;
    private static $indexForSublist = 0;

    /**
     * Array with key the name of the index and value its type (as defined
     * from the private static marks)
     * @var array
     */
    private $indexes = array();

    /**
     * Array containing the objects of the list
     * @var array of _Object
     */
    private $list = array();

    /**
     * Array that associates an index with the key of the object in the list
     * array. Can be multidimentional for indexForSublist type.
     * @var array
     */
    private $map = array();

    /**
     * @param String $objectsClassName The class name of the _Object
     * as it returned from its class (that is child of _Object) static
     * className() method call.
     */
    public function __construct($objectsClassName)
    {
        $this->objectsClassName = $objectsClassName;
    }

    /**
     * @return String . The class name of the objects in the list
     */
    public function getObjectsClassName()
    {
        return $this->objectsClassName;
    }

    /**
     * It returns the number of objects in the list
     * @return int
     */
    public function size()
    {
        return count($this->list);
    }

    /**
     * Sets an index for single objects from a property name
     * of the object. The value of this property in each object
     * of the list should be unique.
     * @param string $index . The name of the property that will
     * be used as index for single objects.
     * @return bool. True if the index has been added or false
     * if there is allready an index with that property name
     */
    public function setIndexForObjects($index)
    {
        if(isset($this->indexes[$index]))
        {
            return false;
        }
        else
        {
            $this->indexes[$index] = self::$indexForObject;
            $this->map[$index] = array();
            if(count($this->list) > 0)
            {
                $this->appendIndexToList($index, self::$indexForObject);
            }
            return true;
        }
    }


    /**
     * Sets an index (property name) for retrieving a sub list
     * from the list based to a given value
     * @param string $index .The name of the property that will
     * be used as index for the sublist
     * @return bool. True if the index has been added or false
     * if there is allready an index with that property name
     */
    public function setIndexForSublists($index)
    {
        if(isset($this->indexes[$index]))
        {
            return false;
        }
        else
        {
            $this->indexes[$index] = self::$indexForSublist;
            $this->map[$index] = array(array());
            if(count($this->list) > 0)
            {
                $this->appendIndexToList($index, self::$indexForSublist);
            }
            return true;
        }
    }

    /* (non-PHPdoc)
     * @see IteratorAggregate::getIterator()
     */
    public function getIterator()
    {
        return new ArrayIterator($this->list);
    }

    /**
     * Adds an object to the list
     * @param _Object $object
     * @throws Exception . If the object to be added isn't instace
     * of the class name defined in the constructor
     */
    public function add(_Object $object)
    {
        $this->checkObjectClass($object);
        $this->list[] = $object;
        end($this->list);
        $this->mapObjectIndexes(key($this->list), $object);
    }

    /**
     * Enter description here ...
     * @param int $key . The key of the object to be set
     * @param _Object $object
     * @throws Exception . If the object to be added isn't instace
     * of the class name defined in the constructor. Or if
     * the $i key to set is not an integer
     */
    public function set($key,_Object $object)
    {
        if(!is_integer($key))
        {
            throw new Exception("_List set method can only accept
            integer for key ".$key." given", 780);
        }
        if(isset($this->list[$key]))
        {
            $this->remove($key);
        }
        $this->list[$key] = $object;
        $this->mapObjectIndexes($key, $object);
    }

    /**
     * It returns an object of the list depending to its key
     * Notice that returns a new object instance of the defined class name
     * that its properties are null if there isn't such key in
     * the list
     * @param integer $key .
     */
    public function get($key)
    {
        if(isset($this->list[$key]))
        {
            return $this->list[$key];
        }
        else
        {
            $type = $this->objectsClassName;
            return new $type();
        }
    }

    /**
     * Check if the list contains a indexed object based
     * to the index property name and the value of it defined
     * by setIndexForObjects method call
     * @param string $index
     * @param string $value
     * @return boolean
     */
    public function containsObject($index,$value)
    {
        return isset($this->map[$index][$value])
        && !is_array($this->map[$index][$value]);
    }

    /**
     * Gets an object from the list based to the value of a
     * predefiend index that has been set by setIndexForObjects method call
     * Notice that returns a new object instance of the defined class name
     * that its properties are null if there isn't such an object in the list
     * @param string $index The index predefined by setIndexForObjects
     * @param string $value The value of the property to get the object
     */
    public function getObject($index,$value)
    {
        if(isset($this->map[$index][$value]))
        {
            return $this->list[$this->map[$index][$value]];
        }
        else
        {
            $type = $this->objectsClassName;
            return new $type();
        }
    }

    /**
     * Check if the list contains a indexed sublist based
     * to the index property name and the value of it defined by
     * setIndexForSublists method call
     * @param string $index
     * @param string $value
     * @return boolean
     */
    public function containsSublist($index,$value)
    {
        return isset($this->map[$index][$value]) && is_array($this->map[$index][$value]);
    }

    /**
     * Gets a sub list from the list based to the value of a
     * predefined index by setIndexForSublists method casll
     * It will return an empty _List (with size 0) if there
     * is no match.
     * @param string $index The index predefined by setIndexForSublists method casll
     * @param string $value The value of the property to generate the sub list
     * @param bool $preserveIndexes . If set to true the new List that will be returned
     * will have the same indexes as the current one. Default false
     * @return _List
     */
    public function getSublist($index,$value,$preserveIndexes = false)
    {
        $list = new _List($this->objectsClassName);
        if(isset($this->map[$index][$value]))
        {
            if($preserveIndexes)
            {
                foreach($this->indexes as $newIndex => $indexType)
                {
                    if($indexType == self::$indexForObject)
                    {
                        $list->setIndexForObjects($newIndex);
                    }
                    else
                    {
                        $list->setIndexForSublists($newIndex);
                    }
                }
            }

            foreach($this->map[$index][$value] as $key)
            {
                $list->add($this->get($key));
            }
        }
        return $list;
    }


    /**
     * Removes an object from the list array based on the
     * key it has in it.
     * @param int $key . The key of the object in the list array
     * @return bool $removed. True if the object removed , false if there
     * isn't such key in the objects list array.
     */
    public function remove($key)
    {
        if(!isset($this->list[$key]))
        {
            return false;
        }

        // The properties values of the object to remove in array
        $arr = $this->list[$key]->toArray();
        foreach ($this->indexes as $index => $indexType)
        {
            if($indexType == self::$indexForObject)
            {
                unset($this->map[$index][$arr[$index]]);
            }
            // it is index for sublist
            else
            {
                // We will not use array_search due to performance issues
                // maybe in a new PHP version this will be updated
                foreach($this->map[$index][$arr[$index]] as $ind => $value)
                {
                    if($value == $key)
                    {
                        unset($this->map[$index][$arr[$index]][$ind]);
                        break;
                    }
                }
            }
        }
        return true;
    }

    /**
     * Removes all objects from the list that fetch a specific index and a value
     * no matter what type of index is it (for single object or for sub list)
     * @param $index
     * @param  $value
     * @return int . The number of objects removed;
     */
    public function removeByIndex($index,$value)
    {
        $removed = 0;
        if( isset($this->indexes[$index])
        && isset($this->map[$index][$value]) )
        {
            if($this->indexes[$index] == self::$indexForObject)
            {
                unset($this->list[$this->map[$index][$value]]);
                $removed = 1;
            }
            // if it is index for sublists
            else
            {
                foreach($this->map[$index][$value] as $ind => $key)
                {
                    unset($this->list[$key]);
                    $removed++;
                }
            }

            unset($this->map[$index][$value]);
        }
        return $removed;
    }


    /**
     * Check if an object (for add or for set) is instance of the class
     * given by class name in the contructor
     * @param _Object $object
     * @throws Exception
     */
    private function checkObjectClass(_Object $object)
    {
        if(!$object instanceof $this->objectsClassName)
        {
            throw new Exception("This _List accepts only ".$this->objectsClassName." objects ", 781)    ;
        }
    }

    /**
     * Uses the object properties to fill the needed map array for the defined indexes
     * @param int $key .The key of the list array that the object have been added
     * @param _Object $object . The object that have been added
     */
    private function mapObjectIndexes($key,_Object $object)
    {
        $arr = $object->toArray();
        foreach ($this->indexes as $index => $indexType)
        {
            // If the object has property with the name of the index
            if(isset($arr[$index]))
            {
                // If the index is for one object
                if($indexType==self::$indexForObject)
                {
                    $this->map[$index][$arr[$index]] = $key;
                }
                // If the index is for sublist
                else
                {
                    $this->map[$index][$arr[$index]][] = $key;
                }
            }
        }
    }

    /**
     * Adds an index to the list when the list allready contains objects
     * @param String $index
     * @param int $indexType
     */
    private function appendIndexToList($index,$indexType)
    {
        foreach($this->list as $key => $object)
        {
            $arr = $object->toArray();
            if(isset($arr[$index]))
            {
                if($indexType==self::$indexForObject)
                {
                    $this->map[$index][$arr[$index]] = $key;
                }
                else
                {
                    $this->map[$index][$arr[$index]][] = $key;
                }
            }
        }
    }

    /* (non-PHPdoc)
     * @see _Object::toArray()
     */
    public function toArray($includeChilds = false)
    {
        $arr = array();
        if(count($this->list) > 0)
        {
            foreach($this->list as $o)
            {
                $arr[] = $o->toArray($includeChilds);
            }
        }
        return $arr;
    }

}

Notice that this is the minified version of the class keeping it as simple as it can be to be fully operational. There are more methods , like reindexing , intersected sublists , ordered sublists and more , if you want we could get back to that with more.

**The Object Instantiator object (+ mapList method) **

In the previous example we examined the Object Instantiator Object , here we will add only one more method to it the mapList

/**
 * Parent class for Object Instantiators
 */
abstract class _Object_Instantiator extends _Data_Worker
{

    /**
     * Maps an statement result array from a Data Worker
     * to a _List of objects
     * @param array $r The array to be mapped (array of arrays)
     * @param _List $list
     * @param string $mapper defult empty array. If passed will map the table field to the proper property
     * e.g. "CAT_ID" => "id" will set the id property of the object from the CAT_ID field value
     */
    protected function mapList($r,_List $list,$mapper = null)
    {
        if(count($r)>0)
        {
            foreach($r as $row)
            {
                $className = $list->getObjectsClassName();
                $o = new $className();
                $this->map($row, $o,$mapper);
                $list->add($o);
            }
        }
    }

    /**
     * Assigns values to properties of an object from an associative array.
     * @param array $array . The array to be mapped. If $mapper is not set then native behavior will
     * be used(e.g. array key value $r["categoryId"] will mapped to object's property $categoryId)
     * if the object has a method setCategoryId($categoryId).
     * @param _Object $object The object that values will map to.
     * @param string $mapper defult empty array. If passed will map the table field to the proper property
     * e.g. "CAT_ID" => "id" will set the id property of the object from the CAT_ID field value
     */
    public function map($array,_Object $object,$mapper = array())
    {
        foreach( $array as $key => $value )
        {
            if( isset( $mapper[$key]) )
            {
                $methodName = "set".ucfirst( $mapper[$key] );
            }
            else if(strpos($key,"_") !== false)
            {
                $methodName = "set";
                $keyParts = explode("_", $key);
                foreach($keyParts as $keyPart)
                {
                    $methodName .= ucfirst(strtolower($keyPart));
                }
            }
            else
            {
                $methodName = "set".ucfirst($key);
            }

            if($object->methodExists($methodName))
            {
                $object->$methodName($value);
            }
        }
    }
}

**Example of usage **

We will use almost the same example as in the previous tutorial (I added there also a client side code generator for PPO objects , Data Workers and Object Instantiator , in fact there is also a getList method , remember always to check your code when it is the result of code generators before paste it in your application) . I will rewrite the classes but the differences are really small and we will comment them. Notice that this is a heavily indexed list example (out of the seven properties of the PPO object three of them are indexes) but it will help deepens the understanding in what we are doing.

Some data for our example:

--
-- Table structure for table `categories`
--

CREATE TABLE IF NOT EXISTS `categories` (
  `id` int(10) NOT NULL AUTO_INCREMENT,
  `parentId` int(10) NOT NULL,
  `title` varchar(255) COLLATE utf8_bin NOT NULL,
  `url` varchar(255) COLLATE utf8_bin NOT NULL,
  `priority` int(5) NOT NULL DEFAULT '0',
  `status` tinyint(1) NOT NULL COMMENT '0 Inactive / 1 Active',
  PRIMARY KEY (`id`),
  UNIQUE KEY `parentIdurlInd` (`parentId`,`url`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_bin AUTO_INCREMENT=25 ;

--
-- Dumping data for table `categories`
--

INSERT INTO `categories` (`id`, `parentId`, `title`, `url`, `priority`, `status`) VALUES
(1, 0, 'News', 'news', 10, 1),
(2, 1, 'Internet', 'internet', 30, 1),
(3, 1, 'Newspapers', 'newspapers', 20, 1),
(4, 1, 'Television', 'television', 40, 1),
(5, 1, 'Current Events', 'current-events', 10, 1),
(6, 0, 'Computers', 'computers', 20, 1),
(8, 6, 'Hardware', 'hardware', 30, 1),
(9, 6, 'Networking', 'networking', 10, 1),
(10, 6, 'Security & Encryption', 'security-encryption', 30, 1),
(11, 6, 'Robotics', 'robotics', 20, 1),
(12, 6, 'Internet', 'internet', 50, 1),
(13, 6, 'Mobile Computing', 'mobile-computing', 60, 1);

The Category PPO object:
Notice the fullUrl property that doesn't exist in the data table

class Model_Object_Category extends _Object
{
    private $id;
    private $parentId;
    private $title;
    private $url;
    private $priority;
    private $status;
    /**
     * The full url of the category. The url segments of the
     * top parent category to this concatenated by "/"
     * @var string
     */
    private $fullUrl;

    public function setId($id)
    {
        $this->id = $id;
    }

    public function setParentId($parentId)
    {
        $this->parentId = $parentId;
    }

    public function setTitle($title)
    {
        $this->title = $title;
    }

    public function setUrl($url)
    {
        $this->url = $url;
    }

    public function setPriority($priority)
    {
        $this->priority = $priority;
    }

    public function setStatus($status)
    {
        $this->status = $status;
    }

    public function setFullUrl($fullUrl)
    {
        $this->fullUrl = $fullUrl;
    }

    /** int(10) NOT NULL AUTO_INCREMENT */
    public function getId()
    {
        return $this->id;
    }

    /** int(10) NOT NULL */
    public function getParentId()
    {
        return $this->parentId;
    }

    /** varchar(255) COLLATE utf8_bin NOT NULL */
    public function getTitle()
    {
        return $this->title;
    }

    /** varchar(255) COLLATE utf8_bin NOT NULL */
    public function getUrl()
    {
        return $this->url;
    }

    /** int(5) NOT NULL DEFAULT '0' */
    public function getPriority()
    {
        return $this->priority;
    }

    /** tinyint(1) NOT NULL COMMENT '0 Inactive / 1 Active' */
    public function getStatus()
    {
        return $this->status;
    }

    /**
     * The full url of the category. The url segments of the
     * top parent category to this concatenated by "/"
     * @return string
     */
    public function getFullUrl()
    {
        return $this->fullUrl;
    }
}

The Category Data Worker:
Notice the ORDER BY parentId ASC , priority ASC in the selectAllSql

class Model_Data_Category extends _Data_Worker
{
    private $insertSql = "INSERT INTO categories (parentId,title,url,priority,status) VALUES (?,?,?,?,?)";
    private $selectAllSql = "SELECT * FROM categories ORDER BY parentId ASC , priority ASC";
    private $selectSql = "SELECT * FROM categories WHERE id = ?";
    private $deleteSql = "DELETE FROM categories WHERE id = ?";
    private $updateParentidSql = "UPDATE categories SET parentId = ? WHERE id = ?";
    private $updateTitleSql = "UPDATE categories SET title = ? WHERE id = ?";
    private $updateUrlSql = "UPDATE categories SET url = ? WHERE id = ?";
    private $updatePrioritySql = "UPDATE categories SET priority = ? WHERE id = ?";
    private $updateStatusSql = "UPDATE categories SET status = ? WHERE id = ?";

    public function insert($parentid,$title,$url,$priority,$status)
    {
        $this->db()->request($this->insertSql,array($parentid,$title,$url,$priority,$status));
        return $this->db()->lastInsertId();
    }

    public function selectAll()
    {
        return $this->db()->request($this->selectAllSql);
    }

    public function select($id)
    {
        return $this->db()->request($this->selectSql,$id);
    }

    public function delete($id)
    {
        $this->db()->request($this->deleteSql,$id);
    }

    public function updateParentid($parentid,$id)
    {
        $this->db()->request($this->updateParentidSql,array($parentid,$id));
    }

    public function updateTitle($title,$id)
    {
        $this->db()->request($this->updateTitleSql,array($title,$id));
    }

    public function updateUrl($url,$id)
    {
        $this->db()->request($this->updateUrlSql,array($url,$id));
    }

    public function updatePriority($priority,$id)
    {
        $this->db()->request($this->updatePrioritySql,array($priority,$id));
    }

    public function updateStatus($status,$id)
    {
        $this->db()->request($this->updateStatusSql,array($status,$id));
    }

}

The Category Object Instantiator:
Notice that how we add the index “fullUrl” for unique objects.

class Model_Instantiator_Category extends _Object_Instantiator
{

    /**
     * Returns a List of all categories (of course you could 
     * have different methods accepting variables as getListByStatus($status)
     * that would get only those categories that have the status defined. 
     * @return _List
     */
    public function getList()
    {
        $list = new _List(Model_Object_Category::className());
        $list->setIndexForObjects("id");
        $list->setIndexForSublists("parentId");
        $data = new Model_Data_Category($this->dbFactory(),$this->dbMaps());
        $this->mapList($data->selectAll(), $list);
        if($list->size() > 0)
        {
            /* @var $category Model_Object_Category */
            /* @var $parentCategory Model_Object_Category */
            foreach($list as $category)
            {
                // This could be written in one line but we will write
                // analytically to make it more clear
                if($category->getParentid() == 0)
                {
                    $category->setFullUrl($category->getUrl());
                }
                else
                {
                    $parentCategory = $list->getObject("id", $category->getParentid());
                    $category->setFullUrl($parentCategory->getFullUrl() . "/" . $category->getUrl());
                }
            }
        }
        $list->setIndexForObjects("fullUrl");
        return $list;
    }

    /**
     * Returns a category object using data from db id this id
     * exists or a category object with null properties values
     * if doesn't
     * @param int $id
     * @param array $mapper default empty array
     * @return Model_Object_Category
     */
    public function getById($id,$mapper = array())
    {
        $o = new Model_Object_Category();
        $data = new Model_Data_Category($this->dbFactory(),$this->dbMaps());
        $r = $data->select($id);
        if(count($r) > 0)
        {
            $this->map($r[0], $o,$mapper);
        }
        return $o;
    }
}

Use of it:

error_reporting(E_ALL);
ini_set("display_errors", "1");

// This array could be decoded by a json file above public_html
$dbPropertiesArray =
    array(
        ""=>
            array("database" => "test",
                "username" => "testuser",
                "password" => "testpassword",
                "charset" => "UTF8")
    );

$dbFactory = new _Data_DbFactory($dbPropertiesArray);
echoTime();
$inst = new Model_Instantiator_Category($dbFactory);
$categoriesList = $inst->getList();
// now we have the categories list 
/* @var $category Model_Object_Category */

Get size of the list:

$size = $categoriesList->size();
var_export($size);

iterate through all categories list

foreach($categoriesList as $category)
{
    // now we can iterate through all categories
    var_export($category);
}

Get by id:

if($categoriesList->containsObject("id", 1))
{
    $category = $categoriesList->getObject("id", 1);
    // here we have the category that has id 1
    var_export($category);
}

Get all categories by parentId:

if($categoriesList->containsSublist("parentId", 0))
{
    foreach($categoriesList->getSublist("parentId", 0) as $category)
    {
        // now we can iterate through all categories that have parent id 1
        var_export($category);
    }
}

Get category that has full url "news/internet":

if($categoriesList->containsObject("fullUrl", "news/internet"))
{
    $category = $categoriesList->getObject("fullUrl", "news/internet");
    // This is the category that has fullUrl "news/internet"
    var_export($category);
}

create Breadcrumb for a category e.g. the one that has fullUrl "computers/internet":

if($categoriesList->containsObject("fullUrl", "computers/internet"))
{
    $category = $categoriesList->getObject("fullUrl", "computers/internet");
    $viewBreadcrumb = new View_Generator_Breadcrumb($categoriesList, "https://example.com", "EXAMPLE INC");
    $breadCrumbHtml = $viewBreadcrumb->generate($category);
    var_export($breadCrumbHtml);
}


class View_Generator_Breadcrumb
{
    /**
     * @var _List of Model_Object_Category
     */
    private $categoriesList;

    private $rootUrl;

    private $rootTitle;

    /**
     * Enter description here ...
     * @param _List $categoriesList
     * @param String $rootUrl The root url
     * @param String $rootTitle The title for the root url link
     */
    public function __construct(_List $categoriesList,$rootUrl,$rootTitle)
    {
        $this->categoriesList = $categoriesList;
        $this->rootUrl = $rootUrl;
        $this->rootTitle = $rootTitle;
    }

    /**
     * Enter description here ...
     * @param Model_Object_Category $category
     * @param String $rootUrl The root url
     * @param String $rootTitle The title for the root url link
     */
    public function generate(Model_Object_Category $category)
    {
        $re  = $this->toBreadcrumb($this->rootUrl, $this->rootTitle);
        $re .= $this->createBreacrumb($category);
        return $re;
    }

    private function createBreacrumb(Model_Object_Category $category)
    {
        $breadcrumb = $this->toBreadcrumb($this->rootUrl . "/"
        . $category->getFullUrl() , $category->getTitle() );
        if($category->getParentid() == 0)
        {
            return $breadcrumb;
        }
        else
        {
            return $this->createBreacrumb($this->categoriesList
            ->getObject("id", $category->getParentid())) . $breadcrumb;
        }
    }

    private function toBreadcrumb($url,$title)
    {
        $re = "";
        $re .= "<h2 itemscope itemtype=\"http://data-vocabulary.org/Breadcrumb\">\n";
        $re .= "\t<a href=\"".$url."\" itemprop=\"url\">\n";
        $re .= "\t\t<span itemprop=\"title\">".$title."</span>\n";
        $re .= "\t</a>\n";
        $re .= "</h2>\n";

        return $re;
    }
}

Here there could be many examples. The usage of ILM Indexed List Model , is almost limitless. There was a road to create such an approach containing the Db object , Db factory , PPO Objects , Data Workers , Object Instantiators and Indexed List . This approach have gradually formed in the core of it over many years prior to PHP in other languages but within OOP PHP it had the opportunity to strength the strong points of it. It is used in production for more than five years now in our company , and we think to publish our latest framework _Underscore public. Before that I thought it would be great to share it in a way that can be used even without a framework or implemented in almost any known. Probably I should close this tutorial series with Application Scope Caching although there are already solutions for that. If there are any questions , or anyone need more details about something , or just sharing opinions I would be happy to read , response and help if I can.

diafol commented: Thanks again for this jkon. This series is certainly shaping up to be a must-go-to resource. Huge amount of work. Bookmarked. +15
jkon 638 Posting Whiz in Training Featured Poster

There are two really minor things in this tutorial that needs more details. Remember I wrote those minifying other classes to keep only what is necessity for ILM and a bit more to make it work without problems in any implementation of that.
In the example of usage: Get all categories by parentId: would be better if it were:

if($categoriesList->containsSublist("parentId", 0))
{
    $tempList = $categoriesList->getSublist("parentId", 0);
    foreach($tempList as $category)
    {
        // now we can iterate through all categories that have parent id 0
        var_export($category);
    }
}

It is clearer to iterate through a tempList than a list that came out of a sublist inside the foreach.

Also there are some rare conditions (e.g. WebSockets) where line 141 and 142 of the _List class should be reversed . If you are going to use a lot of remove method you don't want to have PHP decide where to put the last element.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.