Editors Note, February 14th 2022: This project is now ABANDONED and no longer supported or updated.

asherwunk/phabstractic now has restricted list data type support.

Restricted Lists

A list is a type of abstract data type that in essence, keeps pieces of data in a certain order, like a stack or a queue.  I’ve written about Stacks, and Queues in regards to asherwunk/phabstractic.  I’ve also written about the Restrictions predicate that is available in the library.

Well, now it’s time to put those two things together and introduce the Restricted List!

Remember the ListInterface?  Our new list will implement the following as inheriting from AbstractList (github):

namespace Phabstractic\Data\Types\Resource
{
    interface ListInterface extends \Countable {
        
        public function top(); 
        
        public function &topReference();
        
        public function push();
        
        public function pushReference(&$a); 
        
        public function pop();
        
        public function &popReference(); 
        
        public function index($i);
        
        public function &indexReference($i); 
        
        public function count();
        
        public function isEmpty();
        
        public function clear();

        public function getList();
        
    }
    
}

We add and remove elements onto the list using ->push()  and ->pop(), and we index into the list using ->index() and ->indexReference().  In our implementation we’re going to have more methods than this, in fact, we inherit from AbstractList (github):

namespace Phabstractic\Data\Types\Resource
{    
    abstract class AbstractList implements ListInterface
    {
        protected $list = array();
        
        public function __construct($data = null) 
        {
            if (is_array($data)) { 
                $this->list = $data; 
            } elseif ($data instanceof AbstractList) { 
                $this->list = $data->getList(); 
            } elseif ( $data ) { 
                $this->list = array($data);
            } else { 
                $this->list = array();
            }
            
        } 
        
        abstract public function top();
        
        abstract public function &topReference();
        
        abstract public function push();
        
        abstract public function pushReference( &$a );
        
        abstract public function pop();
        
        abstract public function &popReference();
        
        abstract public function index($i);
        
        abstract public function &indexReference($i); 
        
        public function count()
        {
            return count($this->list);
        }
        
        public function isEmpty()
        { 
            return empty($this->list); 
        } 
    
        public function clear()
        { 
            $this->list = array();
            return $this;
        }
        
        public function remove($value)
        {
            if ($key = array_search($value, $this->list)) {
                unset($this->list[$key]);
            }
            
            // resets indices
            $this->list = array_merge($this->list, array());
            return $this->list;
        }
        
        public function getList()
        {
            return $this->list;
        }
        
        abstract public function exchange();
         
        abstract public function duplicate();
        
        abstract function roll($i);
    }     
}

For this particular list implementation, it doesn’t really matter if we’re a stack or a queue as if we want a restricted version of those two we’ll make implementations of those.  For now, we must implement the Abstract Restricted List.  This in essence serves as the middle man between implementation and restriction.  The methods implemented in this abstract class are meant to be called as parts of the final implementation’s methods.  In order to keep our list only holding values of data types we approve of, we simply filter what goes into them.  Here you can see the ->push() methods set up to call a function that passes the value by the Restrictions predicate set up in the constructor (github):

namespace Phabstractic\Data\Types\Resource
{    
    use Phabstractic\Data\Types;
    use Phabstractic\Data\Types\Exception as TypesException;
    use Phabstractic\Features;
    
    abstract class AbstractRestrictedList extends AbstractList
    {
        use Features\ConfigurationTrait;
        
        protected $restrictions = null;
        
        public function getRestrictions()
        {
            return $this->restrictions;
        }
        
        public function __construct(
            $data = null,
            FilterInterface $restrictions = null,
            $options = array()) 
        {
            $this->configure($options);
            
            // If there are no restrictions given, build basic free form restrictions
            // Default doesn't allow Type::TYPED_OBJECT    
            if (!$restrictions) {
                $restrictions = Types\Restrictions::getDefaultRestrictions();
            }
            
            $this->restrictions = $restrictions;
            if ( is_array( $data ) ) {
                // Plain Array
                
                /* Check input values for any illegal types
                   If false, throw error because this is a constructor and can't
                   really return anything. */
                if (!$this->restrictions::checkElements(
                        $data,
                        $this->restrictions,
                        $this->conf->strict)) {
                    throw new TypesException\InvalidArgumentException(
                        'Phabstractic\\Data\\Types\\Resource\\' .
                        'AbstractRestrictedList->__construct: Illegal Value');
                }
            } else if ($data instanceof AbstractList) {
                // Phabstractic\Data\Types\Resource\AbstractList
                    
                /* Check input values for any illegal types
                   If false, throw error because this is a constructor and can't
                   really return anything. */
                if (!$this->restrictions::checkElements(
                        $data->getList(),
                        $this->restrictions,
                        $this->conf->strict)) {
                    throw new TypesException\InvalidArgumentException(
                        'Phabstractic\\Data\\Types\\Resource\\' .
                        'AbstractRestrictedList->__construct: Illegal Value');
                }
            } else if ($data) {
                // A scalar value
                
                /* Check input values for any illegal types
                   If false, throw error because this is a constructor and can't
                   really return nothing */
                if (!$this->restrictions::checkElements(
                        array($data),
                        $this->restrictions,
                        $this->conf->strict)) {
                    throw new TypesException\InvalidArgumentException(
                        'Phabstractic\\Data\\Types\\Resource\\' .
                        'AbstractRestrictedList->__construct: Illegal Value');
                }
            }
            
            parent::__construct($data);
        }
        
        protected function check()
        {
            if (!$this->restrictions::checkElements(
                    func_get_args(),
                    $this->restrictions,
                    $this->conf->strict)) {
                if ($this->conf->strict) {
                    throw new TypesException\InvalidArgumentException(
                        'Phabstractic\\Data\\Types\\Resource\\' .
                        'AbstractRestrictedList->check: Value not in ' .
                        'restrictions' );
                } else {
                    return false;
                }
                
            }
            
            return true;
        }
        
        public function push()
        {
            $args = func_get_args();
            return call_user_func_array(array($this, 'check'), $args);
        }
        
        public function pushReference(&$a)
        {
            return $this->check($a);
        }
        
    }
    
}

The idea here is that an implementation will call the parent::push(), acting on that return value before executing its own code.  These push methods here encapsulate the behavior we are seeking: they pass a value by the type restrictions object that accepts or rejects it based on what it is (a scalar, a number, an object, a certain class, etc. see the restrictions predicate for more information).  Then, depending on the return value of the parent method, it will act on that data to either reject or add it to the list.  RestrictedList.php has been implemented to give us an example (in this case happening to implement a basic array) (github):

namespace Phabstractic\Data\Types
{    
    use Phabstractic\Data\Types\Resource as TypesResource;
    use Phabstractic\Data\Types\Exception as TypesException;
    use Phabstractic\Features;
    use Phabstractic\Features\Resource as FeaturesResource;
    
    class RestrictedList extends TypesResource\AbstractRestrictedList implements
        \ArrayAccess,
        FeaturesResource\ConfigurationInterface
    {
        use Features\ConfigurationTrait;
        
        function __construct(
            $data = null,
            TypesResource\FilterInterface $restrictions = null,
            $options = array()
        ) {
            $this->configure($options);
            
            parent::__construct($data, $restrictions, $options);
        }
        
        // ...
    
        public function push()
        {
            $arguments = func_get_args();
            $exec = 'if ( parent::push( ';
            for ($a = 0; $a < count( $arguments ); $a++) {
                if ($a) {
                    $exec .= ", ";
                }
                
                $exec .= "\$arguments[$a] ";
            }
            
            $exec .= ' ) ) { return array_push( $this->list, ';
            for ($a = 0; $a < count( $arguments ); $a++) {
                if ($a) {
                    $exec .= ", ";
                }
                
                $exec .= "\$arguments[$a] ";
            }
            
            $exec .= ' ); } else { return null; }';  
            return eval($exec);
        }
        
        public function pushReference( &$a ) {
            $args = func_get_args();
            if (parent::pushReference($a)) {
                $this->list[] = &$a;
                return count($this->list);
            }
            
            return null;
        }
        
        // ...
       
    }
    
}

Note that as in the previous list types we are sure to specify the ArrayAccess methods.  But note their difference in the Restricted List.  They must also check against data type restrictions, and we haven’t forgotten that:

namespace Phabstractic\Data\Types
{
    // ...

    class RestrictedList extends TypesResource\AbstractRestrictedList implements
        \ArrayAccess,
        FeaturesResource\ConfigurationInterface
    {
        use Features\ConfigurationTrait;
        
        // ...
        
        // The \ArrayAccess member functions
        
        public function offsetSet($key, $value)
        {
            if (is_numeric($key)) {
                if ($this->offsetExists($key)) {
                    if (!$this->check($value)) {
                        if ($this->conf->strict) {
                            throw new TypesException\InvalidArgumentException(
                                'Phabstractic\\Data\\Types\\RestrictedList->offsetSet: ' .
                                'Value not in restrictions' );
                        } else {
                            return null;
                        }
                    }
                    // encapsulate indexing logic in indexing function
                    $r = &$this->indexReference($key);
                    $r = $value;
                    return count($this->list);
                }
                
            }
            
            // If the key is empty: stack[], push value
            if (!$key) {
                return $this->push($value);
            }
            
            // At this point, we should have returned, or something is wrong
            // Such as treating the stack as an associative variable
            
            if ($this->conf->strict) {
                throw new TypesException\RangeException(
                    'Phabstractic\\Data\\Types\\RestrictedList->offsetSet: offsetSet key ' .
                    $key . ' out of range.');
            }
            
        }
        
        public function offsetGet($key) {
            if (!is_numeric($key)) {
                if ($this->conf->strict) {
                    throw new Exception\InvalidArgumentException(
                        'Phabstractic\\Data\\Types\\RestrictedList->offsetGet: ' .
                        'Invalid argument key');
                } else {
                    return new None();
                }
                
            }
            
            // Can return Types\Null()
            return $this->index($key);
        } 
        
        public function offsetUnset($key) {
            if (!is_numeric($key)) {
                return false;
            } else {
                unset($this->list[$key]);
                // reset keys
                $this->list = array_merge($this->list, array());
                return true;
            }
            
            return false;
        } 
        
        public function offsetExists( $key ) {
            if (!is_numeric($key)) {
                return false;
            } else {
                return isset($this->list[$key]);
            }
        }
        
        // ...       
    }
    
}

Usage

An example of using this functionality is, say I wanted a list of automobiles, but only automobiles.  They are forming a queue and are going to be allowed through a gate.  It happens that my Car class is in the namespace MyNamespace\Automobile\Car.  I would first instantiate my restrictions to match that data type, and then instantiate a list:

<?php

use Phabstractic\Data\Types;
use Phabstractic\Data\Types\Type;

$restrictions = new Types\Restrictions(array(Type::TYPED_OBJECT),
                                       array('\\MyNamespace\\Automobile\\Car'),
                                       array('autoload' => true));

$listOfCars = new Types\RestrictedList(array(), $restrictions, array('strict' => true));

Then when I am ready to put a car on the list I might do something like this:

$firstCar = new Automobile\Car();
$secondCar = new Automobile\Car();

$listOfCars->push($firstCar, $secondCar);

Note, that if we try to add a value to the list that is not proper since we’ve set it strict, it will throw an error:

// This will throw an error:

$listOfCars->push('notacar');

Conclusion

Lists are very important pieces of software engineering and implementation.  We have studied lists such as stacks and queues before, now we have simply implemented them with type checking.  PHP is not a typed language, any variable can be anything, but with a restrictions predicate, we are able to ensure variables in PHP are specific types.  If we combine these two things we get a restricted list.

This is part of the Phabstractic Library.

photo credit: Promenade, Torquay via photopin (license)

Asher Wolfstein

Metaverse Resident

About the Author

A metaverse resident, you can find me on Second Life (kadar.talbot) and other online platforms. I write about my digital life, my musings, and my projects as a programmer, webmaster, artist, and game designer. (exist (be wunk) (use rational imagination) (import artist coder maker furry) (conditional (if (eq you asshole) (me (block you))))

View Articles