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

asherwunk/phabstractic now has type restrictions for elements implemented through a filter class in Restrictions.php (github)

Filtering

A filter is a predicate data structure that compares values against some internal logic to see if they ‘pass through’ the filter or not.  This is the idea behind the FilterInterface (github) and the AbstractFilter (github) class.  Because the static method ::checkElements() can be used out of the box it was defined in the AbstractFilter class as follows:

public static function checkElements(
    array $values,
    TypesResource\FilterInterface $filter,
    $strict = false)
{
    foreach ($values as $value) {
        if ($filter->isAllowed($value, $strict) === false) {
            if ($strict) {
                throw new TypesException\InvalidArgumentException(
                    'Phabstractic\\Data\\Types\\Resource\\' .
                    'AbstractFilter->checkElements: Illegal Value');
            }
            
            return false;
        }
        
    }
    
    return true;
}

This function can of course be overridden by the inheriting class, as it is in the case of Restrictions.php (github).

Type Restrictions

An example of a filter is to delineate what kinds of values can be accepted (into say, a set).  You can allow multiple kinds of values, or just one.  You can also specify if objects are of/inherit from a particular class or interface (it checks them up the inheritance tree).  This uses the Type predicate data object to figure out what type a given value is.  It also overrides the Phabstractic\Data\Types\Restrictions::checkElements() static function as well as provides a shortcut of its own Phabstractic\Data\Types\Restrictions::checkRestrictedValues() [for backwards compatibility]. You can use the class simply as follows (check out the unit test for more examples):

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

$r = new Types\Restrictions(array(Type::BASIC_BOOL));
$v = true;
$i = 5;

$r->isAllowed(Type::BASIC_BOOL); // true
$r->isAllowed(Type::BASIC_INT); // false

$r->checkElements(array($v)); // true
$r->checkElements(array($i)); // false

If you are interested in restricting types to a particular class (including sub-classes) or interface you can specify the following using the Phabstractic\Data\Types\Type::TYPED_OBJECT  constant:

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

$restrictions = new Types\Restrictions(array(Type::TYPED_OBJECT),
                                       array('Phabstractic\\Data\\Types\\Set'));

$set = new Types\Set(array(1,2,3,4));

$restrictions->isAllowed(array(Type::TYPED_OBJECT, 'Phabstractic\\Data\\Types\\Set')); // true
$restrictions->isAllowed(array(Type::TYPED_OBJECT, 'SomeOtherClass'); // false;
$restrictions->checkElements(array($set)); // true

Be sure to only pass type information to Phabstractic\Data\Types\Restrictions::isAllowed() , not actual variables themselves.  You can accomplish that by formatting your calls using Type Enumeration:

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

$restrictions = new Types\Restrictions(array(Type::BASIC_BOOL,
                                             Type::BASIC_INT,));

$b = true;
$s = 'test';

$restrictions->isAllowed(Type\getValueType($b)); // true
$restrictions->isAllowed(Type\getValueType($s)); // false

If you want to check if an actual variable holding a value is allowed through a restrictions filter, use the static Phabstractic\Data\Types\Restrictions::checkElements()  method instead:

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

$restrictions = new Types\Restrictions(array(Type::BASIC_INT,
                                             Type::BASIC_BOOL,));

$b = true;
$i = 5;
$s = 'test';

Types\Restrictions::checkElements(array($b, $i), $restrictions); // true
Types\Restrictions::checkElements(array($i, $s), $restrictions); // false

The Restrictions Function

Phabstractic\Data\Types\Restrictions::isAllowed() is the ultimate function for checking the type of a variable against the allowed classes and types of the restriction.  You can see it below:

public function isAllowed($type, $strict = false)
{
    $typeClass = $this->conf->type_class;
    $testClass = '';

    if (!($type instanceof Type)) {
        
        if (is_int($type)) {
            try {
                $type = new $typeClass($type);
                
            } catch (\UnexpectedValueException $e) {
                if ($strict) {
                    throw new TypesException\UnexpectedValueException(
                        'Phabstractic\\Data\\Types\\Restrictions->isAllowed: ' .
                        'Incorrect integer given for type' );
                }
                
                return false;
            }
            
        } else if (is_string($type)) {
            $type = Type\stringToType($type);
            if ($type == null) {
                if ($strict) {
                    throw new TypesException\UnexpectedValueException(
                        'Phabstractic\\Data\\Types\\Restrictions->isAllowed: ' .
                        'Incorrect string given for type' );
                }
                
                return false;
            }
            
        } else if (is_array($type)) {
            if (count($type) == 2) {
                if (
                    (($type[0] instanceof Type) &&
                        ($type[0]->get() == $typeClass::TYPED_OBJECT)) ||
                    (is_int($type[0]) && $type[0] == $typeClass::TYPED_OBJECT)) {
                    $obj = $type[1];
                    
                    if (is_object($obj)) {
                        $testClass = get_class($obj);
                    } elseif (!is_string($obj)) {
                        if ($strict) {
                            throw new TypesException\UnexpectedValueException(
                                'Phabstractic\\Data\\Types\\Restrictions->isAllowed: ' .
                                'Passed array has improper value (not string).');
                        } else {
                            return false;
                        }
                    } else {
                        $testClass = $obj;
                    }
                } else {
                    if ($strict) {
                        throw new TypesException\UnexpectedValueException(
                            'Phabstractic\\Data\\Types\\Restrictions->isAllowed: ' .
                            'TYPED_OBJECT Not present in array');
                    }
                    
                    return false;
                }
                   
            } else {
                throw new TypesException\UnexpectedValueException(
                    'Phabstractic\\Data\\Types\\Restrictions->isAllowed: ' .
                    'Passed array has more than two elements.');
            }
            
        } else {
            return false;
        }
        
    }
    
    if ($testClass === '') {
        return $this->allowed->in($type->get());
    } else {
        if (class_exists($testClass, $this->conf->autoload)) {
            if (!($this->classes->in($testClass))) {
                foreach ($this->classes->iterate() as $class) {
                    // usable only since PHP 5.0.3
                    if (is_subclass_of($testClass, $class)) {
                        // derives from a required class/interface
                        return true;
                    }
                }
            } else {
                return true;
            }
        } else {
            if ($strict) {
                throw new TypesException\UnexpectedValueException(
                    'Phabstractic\\Data\\Types\\Restrictions->isAllowed: ' .
                    'Given class doesn\'t exist.');
            }
        }
    }
    
    // Default
    return false;
}

Dynamic Restrictions

Of course, you can change the predicate at run-time.  This is useful if a new class is loaded into the code and you want to include it in your restrictions:

public function setAllowedTypes(array $allowed) {
    foreach ($allowed as $type) {
        if (!$this->isProperType($type)) {
            throw new TypesException\RangeException(
                'Phabstractic\\Data\\Types\\Restrictions->setAllowedTypes: ' .
                'Given Enumerator Value Doesn\'t Exist - ' .
                $type );
        }
    }
    
    $this->allowed = new Set(
        $allowed,
        array('strict' => $this->conf->strict_sets,
              'unique' => true,)
    );
    
}

public function addAllowedType($type) {
    if (!$this->isProperType($type)) {
        throw new TypesException\RangeException(
            'Phabstractic\\Data\\Types\\Restrictions->addAllowedType: ' .
            'Given Enumerator Value Doesn\'t Exist - ' .
            $type );
    }
    
    
    
    $this->allowed->add($type);
}

public function removeAllowedType($type) {
    if (!$this->isProperType($type)) {
        throw new TypesException\RangeException(
            'Phabstractic\\Data\\Types\\Restrictions->addAllowedType: ' .
            'Given Enumerator Value Doesn\'t Exist - ' .
            $type );
    }
    
    $this->allowed->remove($type);
}

public function setAllowedClasses(array $classes) {
    /* Check that each class defined in the classes input actually
       exists, does NOT autoload by default */
    foreach ($classes as $class) {
        if (!$this->classExists($class)) {
            throw new TypesException\RuntimeException(
                'Phabstractic\\Data\\Types\\Restrictions->setAllowedClasses: ' .
                'Undefined Class Classification: ' . $class);
        }
    }
    
    $this->classes = new Set(
        $classes,
        array('strict' =>$this->conf->strict_sets,
              'unique' => true,)
    );
    
    $this->setBasicOrTyped();
}

public function addAllowedClass($class) {
    /* Check that each class defined in the classes input actually
       exists, does NOT autoload by default */
    if (!$this->classExists($class)) {
        throw new TypesException\RuntimeException(
            'Phabstractic\\Data\\Types\\Restrictions->addAllowedClass: ' .
            'Undefined Class Classification: ' . $class);
    }
    
    $this->classes->add($class);
    
    $this->setBasicOrTyped();
}

public function removeAllowedClass($class) {
    $this->classes->remove($class);
    
    $this->setBasicOrTyped();
}

“Dependency Injection”

Because I want to encourage and make room for flexibility you can also inject your own set classes using the ‘allowed’ and ‘classes’ options in the constructor.  You can also specify a particular Type class to be instantiated during the check for Phabstractic\Data\Types\Restrictions::isAllowed(), although it will reference the Type:: constants.  ‘allowed’ and ‘classes’ must implement SetInterface.php (github).

NOTE: If these options aren’t provided they default to Phabstractic\Data\Types\Set instances, instantiated in the constructor function.  This is important for unit testing purposes.  ‘type_class’ must also be a valid classname that implements Phabstractic\Data\Types\Type::get();

You can see how these are checked and incorporated in the __constructor:

public function __construct(
    array $allowed,
    array $classes = array(),
    $options = array() )
{
    
    $this->configure($options);
    
    // autoload must be boolean
    if (!isset($this->conf->autoload) || !is_bool($this->conf->autoload)) {
        $this->conf->autoload = false;
    }
    
    if (!isset($this->conf->type_class)) {
        $this->conf->type_class = 'Phabstractic\\Data\\Types\\Type';
    }
    
    if (!isset($this->conf->strict_sets)) {
        $this->conf->strict_sets = true;
    }

    /* $allowed = array_unique($allowed);
    $classes = array_unique($classes) */;
    
    $type_class = $this->conf->type_class;
    
    /* Check to see if the allowed types are in the
       'allowed types' Type::constants */
    $consts = $type_class::getConstants();
    foreach ($allowed as $type) {
        if (!in_array($type, $consts)) {
            throw new TypesException\RangeException(
                'Phabstractic\\Data\\Types\\Restrictions->__construct: ' .
                'Cannot Construct New Predicate With Enumerator Value ' .
                $type );
        }
    }
    
    /* Make allowed types into a set, strictly, guaranteeing no
       duplicate types */
    if (isset($this->conf->allowed) && $this->conf->allowed) {
        if ($this->conf->allowed instanceof TypesResource\SetInterface) {
            $this->allowed = &$this->conf->allowed;
        } else {
            throw new TypesEXception\RuntimeException(
                'Phabstractic\\Data\\Types\\Restrictions->__construct: ' .
                'Cannot Construct New Predicate With Non Set Interface');
        }
    } else {
        $this->allowed = new Set(
            $allowed,
            array('strict' => $this->conf->strict_sets,
                  'unique' => true));
    }
    
    /* Check that each class defined in the classes input actually
       exists, does NOT autoload by default */
    foreach ($classes as $class) {
        if (!$this->classExists($class)) {
            throw new TypesException\RuntimeException(
                'Phabstractic\\Data\\Types\\Restrictions->__construct: ' .
                'Undefined Class Classification: ' . $class);
        }
    }
    
    /* Make allowed classes into a set, strictly, gauranteeing no
       duplicate classes */
    if ($this->conf->classes) {
        if ($this->conf->classes instanceof TypesResource\SetInterface) {
            $this->classes = &$this->conf->classes;
        } else {
            throw new TypesException\RuntimeException(
                'Phabstractic\\Data\\Types\\Restrictions->__construct: ' .
                'Cannot Construct New Predicate With Non Set Interface');
        }
        
    } else {
        $this->classes = new Set(
            $classes,
            array('strict' =>$this->conf->strict_sets,
                  'unique' => true));
    }
    
    $this->setBasicOrTyped();
}

Conclusion

Filters are important and very useful objects, allowing values or other logic to be filtered in and out of a particular set of data.  Restrictions.php offers a way to use type checking in PHP but in a very flexible and dynamic way.  This can be useful for defining lists or sets that only accept certain values, which is useful for code cohesiveness and can reduce error checking.

View on GitHub

This is part of the Phabstractic Library.

photo credit: Sigma 18-35, 24mm @ f/4, medium focus distance 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