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

asherwunk/phabstractic now supports the Structure abstract data type, this is like a C structure, but with some added functionality.

Can PHP Be Like C?

Obviously, the answer is no, there’s no way PHP could handle all the lower-level functionality that C and C++ allow you to address.  But there is a data structure found in C, and other programming languages, that can be somewhat emulated in PHP… if not actually done better.

This is a structure.  A structure is a bit like the TaggedUnion, only that it doesn’t necessarily change data format.  Instead, it requires particular data types grouped together.  Think of it like the fore-bearer of classes.  It’s a bunch of data stuck together without methods.  In C this is handy because all the data happens one right after another, and it can be accessed as its own data type.  In PHP, with OOP, and other things this isn’t as big of a deal.

However, we can do some interesting things with a Structure data type.  First, we can use the Restrictions Predicate to constrain some elements (fields) of the Structure to particular data types (including classes and interfaces).  This is as simple as building in some functionality to have fields of the Structure represented as Tagged Unions.  For more information on the Tagged Union abstract data type, check out this post.

Two Different Constructors

There is one bit of trouble with the constructor of the Structure, though.

The first version of the constructor (version 2) was written somewhat backward in that you specified field names in the values of an array.  These were processed for TaggedUnions and such, and then ‘flipped’ so that the values became keys, and then merged with the TaggedUnions (which can’t flip into a key).  Upon review, this wasn’t the best implementation, but it seemed straightforward at the time.

The new version of the constructor (version 3) is written so that an array is passed with the keys as the field names and the values as their initial state, or in the case of TaggedUnions, a TaggedUnion object or Restrictions predicate.  The code to process this and the resulting input format is much superior to what I was doing before.  I’m not sure what I was thinking before, I guess I thought it important that the fields be represented by an indexed array.

Here’s the constructor, you can see both versions.  You can access the old version of doing things by specifying ‘version2’ in the options array passed to the constructor:

<?php

namespace Phabstractic\Data\Types
{

    // ...

    $includes = array(// we are a configurable object
                      '/Features/ConfigurationTrait.php',
                      // we provide filters to tagged unions
                      '/Data/Types/Resource/FilterInterface.php',
                      // for our typed fields we use tagged unions
                      '/Data/Types/TaggedUnion.php',
                      // we return None on invalid key
                      '/Data/Types/None.php',
                      // we offer up the type of value in a field
                      '/Data/Types/Type.php',
                      // we throw these exceptions
                      '/Data/Types/Exception/InvalidArgumentException.php',
                      '/Data/Types/Exception/RangeException.php',
                      // for change value case
                      '/Resource/ArrayUtilities.php',);
    
    // ...
    
    use Phabstractic\Features;
    use Phabstractic\Data\Types\Resource as TypesResource;
    use Phabstractic\Data\Types\Type;
    use Phabstractic\Data\Types\Exception as TypesException;
    use Phabstractic\Resource as PhabstracticResource;
    
    class Structure implements \ArrayAccess
    {

        // ...

        private $fields = array();
        
        public function __construct(
            array $allowed = array(),
            array $options = array()
        ) {
            
            if (!isset($options['insensitive'])) {
                $options['insensitive'] = true;
            }
            
            if (!isset($options['version2'])) {
                $options['version2'] = false;
            }
            
            $this->configure($options);
            
            if ( !$allowed ) {
                /* We haven't been given any information to
                   construct the structure */
                if ( $this->conf->strict ) {
                    throw new TypesException\InvalidArgumentException(
                                'Phabstractic\\Data\\Types\\Structure->__construct: ' .
                                'No Elements Given');
                }
                
                return;
            }
            
            if ($this->conf->version2 == false) {
                /* In the new version (version 3) we construct the fields from
                 * an array of indices (the field names) and values (the initial
                 * values of the fields, filters are turned into tagged unions)
                 */
                foreach ($allowed as $key => $val) {
                    if ($val instanceof TypesResource\FilterInterface) {
                        $this->fields[$key] = new TaggedUnion(
                            $val,
                            array('strict' => $this->conf->strict)
                        );
                        
                    } else {
                        $this->fields[$key] = $val;
                    }
                }
                
                if ($this->conf->insensitive) {
                    $this->fields = array_change_key_case($this->fields);
                }
                
            } else {
                // We are using the old version to construct the fields
                
                /* Since we are not using a map, and it wouldn't make sense
                 * to do so since having the value (tagged union) be the key
                 * would accomplish nothing, we generate an array with the keys
                 * being the specified tags.  These are merged in after the flip
                 * 
                 * EX: if value is array (restrictions, tagged unions, etc )
                 */
                $mergeArray = array();
                $arrayCount = count($allowed);
                
                if ($arrayCount) {
                    // go through every field
                    for ($i = 0; $i < $arrayCount; $i++) {
                        if (is_array($allowed[$i]) && count($allowed[$i]) == 2) {
                            // this field is a field name with a restrictions object
                            if ($allowed[$i][0] instanceof TypesResource\FilterInterface) {
                                $mergeArray[$allowed[$i][1]] =
                                    new TaggedUnion(
                                        $allowed[$i][0],
                                        array('strict' => $this->conf->strict)
                                    );
                            } else if ($allowed[$i][0] instanceof TaggedUnion) {
                                $mergeArray[$allowed[$i][1]] = $allowed[$i][0];
                            } else {
                                $mergeArray[$allowed[$i][1]] = $allowed[$i][0];
                            }
                            
                            // in preparation for the flip
                            unset($allowed[$i]);
                        }
                    }
                }
                
                if ($this->conf->insensitive) {
                    // make soon-to-be keys case insensitive
                    $allowed =
                        PhabstracticResource\ArrayUtilities::arrayChangeValueCase($allowed);
                }
                
                // If keys have been made insensitive, the last key value will be used
                $this->fields = array_flip($allowed);
                
                // Remove the values from fields (used to be keys)
                foreach ($this->fields as $k => $v) {
                    $this->fields[$k] = null;
                }
                
                // Merge in the alternative values (these couldn't be flipped)
                $this->fields = array_merge($this->fields, $mergeArray);
            }
        }
        
    }
    
}

Thus the following version 2 input array:

array(
    'field1',
    'field2',
    array(Restrictions, 'field3'),
    array(TaggedUnion, 'field4')
);

Is equivalent to the following version 3 input array:

array(
    'field1' => null,
    'field2' => null,
    'field3' => Restrictions,
    'field4' => TaggedUnion,
);

I think that’s a lot easier to understand, don’t you?

Can We Normalize?

Another thing version 2 of the Structure abstract data type would do was ‘denormalize’ the input to the field name of the structure, regardless if the structure was defined as being case-insensitive (see below).  In version 3 I eliminated this, and field names must now be referenced with case sensitivity (even after specifying case-insensitive in the constructor, see below).  Here you can see the denormalize method, and how it’s only called in version 2:

private function denormalize($field)
{
    foreach ($this->fields as $key => $anon) {
        if (strtoupper($field) == strtoupper($key)) {
            return $key;
        }
        
    }
    
}

// ...

public function getElement($element)
{
    if ($this->conf->version2) {
        $element = $this->denormalize($element);
    }
    
    if (!$this->elementExists($element)) {
        return new None();
    } else {
        if ($this->fields[$element] instanceof TaggedUnion) {
            return $this->fields[$element]->get();
        }
        
        return $this->fields[$element];
    }
    
}

When Does Case-Insensitive Not Mean Case-Insensitive?

I thought it was important in version 3 to make the field names of a structure be case-sensitive.  I thought this would eliminate errors in the long run, and allow for more flexibility.  As you can see above, version 2 doesn’t really follow this thought and denormalizes all incoming field names.  So what’s the point of the case-insensitive option?

The case-insensitive option in the constructor (‘insensitive’) is for the constructor only.  That means when the field names are defined in the structure they are defined in all lower case.  From then on, they must be referred to in a case-sensitive fashion.  So, you must always use lower case when identifying them outside of the class with version 3.

What Can This Do?

Well, the Structure abstract data type does something kind of neat: it implements arrayAccess methods:

public function offsetSet($key, $value)
{
    try {
        $this->setElement($key, $value);
    } catch (TypesException\InvalidArgumentException $e) {
        if (strpos($e->getMessage(), 'TaggedUnion') === false) {
            if ($this->conf->strict) {
                throw new TypesException\RangeException(
                    'Phabstractic\\Data\\Types\\Structure->offsetSet: ' .
                    'OffsetSet key ' . $key . 'out of range.');
            }
        } else {
            throw $e;
        }
    }
    
} 

public function offsetGet($key)
{
    try {
        return $this->getElement($key);
    } catch (TypesException\InvalidArgumentException $e) {
        throw new TypesException\RangeException(
                        'Phabstractic\\Data\\Types\\Structure->offsetGet: ' .
                        'OffsetSet key ' . $key . ' out of range.');
    }
    
} 

public function offsetUnset($key)
{ 
    return false;
} 

public function offsetExists($key)
{
    return array_key_exists($key, $this->fields);
}

This means that in code we can actually set and interact with TaggedUnion members as if they are just ‘plain’ array elements.  The following is valid for both a non-restricted and restricted data field in a structure:

$structure['field3'] = 0;

Conclusion

Structures are data structures supported by many different languages.  They are not as prevalent in modern interpreted languages, which generally favor more object-oriented programming approaches.  The usefulness I think of this particular abstract data type is the ability to be accessed as an array, enabling TaggedUnions to act like normal variables.

Thanks!

(View on github)

This is part of the Phabstractic Library.

photo credit: Forest in the Desert 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