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

The set data type available in asherwunk/phabstractic now has type restriction added to it, making it a Restricted Set.

Restricted Set Data Type

In order for this data type to make more sense, you should be familiar with the Restrictions Predicate data type, as well as the Set data type.  To re-cap on what a set is, I quote Wikipedia:

In computer science, a set is an abstract data type that can store certain values, without any particular order, and no repeated values. It is a computer implementation of the mathematical concept of a finite set. Unlike most other collection types, rather than retrieving a specific element from a set, one typically tests a value for membership in a set.

The idea for a restricted set is that it is basically a set with a restrictions object that it checks elements against.  Restricted Set still implements the Set interface (View On Github):

namespace Phabstractic\Data\Types\Resource
{
    interface SetInterface
    {
        public function getArray();
        
        public function &getArrayReference();
        
        public function getPlainArray();
        
        public function &retrieveReference($identifier);
        
        public function add($value);
        
        public function addReference(&$value);
        
        public function remove($value);
        
        public function removeByIdentifier($identity);
        
        public function in($value);
        
        public function isEmpty();
        
        public function size();
        
        public function iterate();
        
        public function enumerate();
        
        public function pop();
        
        public function clear();
        
        public function hash();
        
        public static function equal(SetInterface $S1, SetInterface $S2);
        
        public static function fold($F, SetInterface $S);
        
        public static function filter($F, SetInterface $S);
        
        public static function map($F, SetInterface $S);
        
        public static function walk(SetInterface &$S, $F, $D);
        
        public static function build(array $values = array());
        
        public static function union();
        
        public static function intersection();
        
        public static function difference();
        
        public static function subset(SetInterface $S, SetInterface $T);
    }
    
}

I list the interface here for reference, so we don’t forget what a Set does.

Filter_Class

The biggest change is the ‘filter class’ now present in the Set.  You can see what I mean by examining the constructor:

public function __construct(
    array $values = array(),
    TypesResource\AbstractFilter $restrictions = null,
    $options = array()
) {
    if (!isset($options['filter_class'])) {
        $options['filter_class'] = 'Phabstractic\\Data\\Types\\Restrictions';
    }
    
    $this->configure($options);
    
    $filterClass = $this->conf->filter_class;
    
    // If there are no restrictions given, build basic free form restrictions
    // Default doesn't allow Type::TYPED_OBJECT    
    if (!$restrictions) {
        $restrictions = $filterClass::getDefaultRestrictions();
    }
    
    $this->restrictions = $restrictions;
    
    /* Check input values for any illegal types
       If false, throw error because this is a constructor and can't
       really return nothing */
    if (!$filterClass::checkElements($values, $this->restrictions))
        throw new TypesException\InvalidArgumentException(
            'Phabstractic\\Data\\Types\\RestrictedSet->__construct: ' .
            'Illegal Value');
    
    // Construct set
    parent::__construct($values, $options);
}

The idea here is that if you specify a class name in the ‘filter_class’ $option[] you can use that restrictions predicate instead.  It just has to implement FilterInterface, if you remember, to recap briefly (View On Github):

namespace Phabstractic\Data\Types\Resource
{
    interface FilterInterface
    {
        public function isAllowed($type, $strict = false);
        
        public static function checkElements(
            array $values,
            FilterInterface $filter,
            $strict = false);
        
        public static function getDefaultRestrictions();
        
    }
}

You’ll notice when I call the static function checkElements()  I use the filter_class variable.  This means whatever class I suggest, that’s whose checkElements()  will be called.  If we don’t specify any restrictions to place on the set, we instantiate a default set of restrictions from Restrictions::getDefaultRestrictions() which, if you remember, go like this (View On Github):

public static function getDefaultRestrictions()
{
    return new Restrictions( array( Type::BASIC_BOOL,
                                    Type::BASIC_INT,
                                    Type::BASIC_STRING,
                                    Type::BASIC_ARRAY,
                                    Type::BASIC_OBJECT,
                                    Type::BASIC_RESOURCE,
                                    Type::BASIC_NULL,
                                    Type::BASIC_CLOSURE,
                                    Type::BASIC_FUNCTION,
                                    Type::BASIC_FLOAT,
                                    Type::BASIC_CALLABLE,)
                            );
}

Adding Values

So in order for a restricted set to stay restricted, we have to make sure the only values that can be added to the set fit within the restrictions/filter.  We already did so in the constructor by running the values through checkElements()  before calling parent::__construct() to construct the set normally.  In order to do the rest we can just inherit from Set and alter only the functions that add values:

public function add($value)
{
    $filterClass = $this->conf->filter_class;
    
    if (($valid = $filterClass::checkElements(
            array($value),
            $this->restrictions,
            true)) == false) {
        if ($this->conf->strict) {
            throw new TypesException\InvalidArgumentException(
                'Phabstractic\\Data\\Types\\RestrictedSet->add: ' .
                'Restricted Value');
        } else {
            return false;
        }
    }
    
    // Passes restrictions, but safety check
    if ($valid) {
        return parent::add($value);
    }
    
}

public function addReference(&$value)
{
    $filterClass = $this->conf->filter_class;
    
    if (($valid = $filterClass::checkElements(
            array($value),
            $this->restrictions,
            true)) == false) {
        if ($this->conf->strict) {
            throw new TypesException\InvalidArgumentException(
                'Phabstractic\\Data\\Types\\RestrictedSet->add: ' .
                'Restricted Value');
        } else {
            return false;
        }
    }
    
    // Passes restrictions, but safety check
    if ($valid) {
        return parent::addReference($value);
    }
    
}

static public function build(
    array $values = array(),
    TypesResource\AbstractFilter $restrictions = null,
    array $options = array()
) {
    return new RestrictedSet($values, $restrictions, $options);
}

You’ll notice we still refer to $filterClass  from the $options[]  (or in this case $this->conf ).  The rest of the functionality remains the same, thus our inheritance.

The coolest part of the Restricted Set is the mapInternal() method.  You can see it here:

/**
 * Calls an internal member function of a set member with arguments
 * 
 * Like map, only it calls a particular method on the given object,
 * this is possible because the set can be guaranteed to hold only
 * certain objects.  !! Do not attempt on a mixed set. !!
 * 
 * @param string $method The method name to call
 * @param array $args The arguments to pass to the method
 * @param Phabstractic\Data\Types\Set $S The set to act on
 * 
 */
static public function mapInternal($method, $args, RestrictedSet $S)
{
    foreach ($S->iterate() as $object) {
        if (method_exists($object, $method)) {
            call_user_func_array(array($object, $method), $args);
        }
        
    }
    
}

Now, this is really cool.  What this does is calls the method $method on all the objects in the Set.  This can be done because we’re guaranteed that only objects fitting certain parameters are in the Set.  However, because it may still be mixed, we make sure that the method exists.  This is very useful for things such as attaching, detaching and notifying listeners and observers that might exist in a set.

Conclusion

By combining the restrictions predicate and the set we are able to configure a Set that only holds certain objects and types.  This means I can make a set of integers, or a set of nodes, or a set of whatever without fear that the set will hold anything else.  This inherits from Set and implements Restrictions in a property.  All the rest of the functionality besides adding in new additional values is taken care of by the parent.

(View On Github)

This is part of the Phabstractic Library.

photo credit: The Sky News team you don’t see on TV (K3_4570) 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