# Standard Data Structures

## Introduction

**Standard Data Structures** is sets of data manipulation objects and data entities structure which provide functionalities to ease working with data sets. these libraries are used as part of core functionalities of Poirot Framework.\
\
The component are distributed under the "**Poirot/Std**" namespace and has a [**git repository**](https://gitlab.com/poirot), and, for the php components, a light **autoloader** and [**composer**](https://getcomposer.org/) support.\
\
\&#xNAN;*All Poirot components following the* [*PSR-4 convention*](http://www.php-fig.org/psr/psr-4/) *for namespace related to directory structure and so, can be loaded using any modern framework* [*autoloader*](https://redcatphp.com/autoload-psr4)*. 100% Test Coverage and fully testable by PHPUnit Test.*

**In Case You need any support or question use the links below:**

**Slack**:\
getpoirot.slack.com\
<https://join.slack.com/t/getpoirot/shared_invite/zt-dvfhkoes-h45XoKlyxamfhaHm6G4uSA>

## `iData` Interface

The `iData` interface provide simple interface around objects which considered as providing data properties with all objects implementing iData interface it's possible to exchange data between any data entity object.&#x20;

Data Objects are:\
\- Traversable\
\- Countable\
\- Implementing `iObject` [property overloading](https://www.php.net/manual/en/language.oop5.overloading.php#language.oop5.overloading.members) interface.&#x20;

```php

interface iData
    extends iObject, \Countable
{
    /**
     * Set Struct Data From Array
     *
     * @param iterable $data
     *
     * @return $this
     */
    function import(iterable $data);

    /**
     * If a variable is declared
     *
     * @param mixed $key
     *
     * @return bool
     */
    function has(...$key);

    /**
     * Unset a given variable
     *
     * @param mixed $key
     *
     * @return $this
     */
    function del(...$key);

    /**
     * Clear all values
     *
     * @return $this
     */
    function empty();

    /**
     * Is Empty?
     *
     * @return bool
     */
    function isEmpty();
}
```

## DataStruct

Implementing [iData](#idata-interface) interface and is a simplest data entity object.

### Constructing

The `DataStructure` can accept sets of data as a constructor argument. These data is an [iterable](https://www.php.net/manual/en/language.types.iterable.php#language.types.iterable) object  with key as data property name which hold the value of the property.

```php
$dataClass = new DataStruct([
   'name' => 'PHP',
   'designed_by' => 'Rasmus Lerdorf',
 ]);
```

### Constructing by variable reference

When there is an array variable, this can be referenced as a construct to the data object, make any change to the data object properties will reflect the reference variable.

```php
$data = [ 'name' => null ];
$dataClass = DataStruct::ofReference($data);
$dataClass->name = 'Power';

print_r($data);
// ['name' => 'Power']
```

### It's Traversable

```php
$dataClass = new DataStruct([
    'name' => 'PHP',
    'designed_by' => 'Rasmus Lerdorf',
]);

foreach ($dataClass as $key => $val)
    echo $key . ': '. $val . PHP_EOL;

// name: PHP
// designed_by: Rasmus Lerdorf
```

### It's Countable

```php
count($dataClass);
$dataClass->count();
```

### Data Manipulation, property overloading

Set Properties:

```php
$dataClass = new DataStruct;

$dataClass->name = 'PHP';
$dataClass->designed_by = 'Rasmus Lerdorf';
```

Get Properties value:

```php
echo $dataClass->name;        // PHP
echo $dataClass->designed_by; // Rasmus Lerdorf
```

Remove property:

```php
unset($dataClass->name);
```

Check availability of property:

Note: if property exists and has `null` as value it also considered as available property and this method will return `true`.

```php
isset($dataClass->name);
```

### Properties are accessible by memory reference

```php
$dataClass = new DataStruct([
  'data' => 'value'
]);

$dataRef = &$dataClass->data;
$dataRef.= '-changed';

echo $dataClass->data; 
// value-changed
```

Data property can be reserved by it's memory reference:\
the property with not assigned value will not considered as property until take a value.&#x20;

```php
$dataClass = new DataStruct;
$dataReference = &$dataClass->data;

if (!isset($dataClass->data) && 0 === count($dataClass)) {
   $dataReference = value;
}

echo $dataClass->data; 
// value
```

### `import` - Set data to object

The `import` method will replace all existing property values by new values provided to method.

```php
$dataClass = new DataStruct();
$dataClass->import([
   'name' => 'PHP',
   'designed_by' => 'Rasmus Lerdorf',
 ]);
```

### `del` - Remove property

Any given properties to method will be removed from data object.

```php
$dataClass->del('name');
$dataClass->del('none_exists_prop', 'designed_by');
```

### `has` - Check property exists

Check whether any properties given as argument exists or not? If any of given properties exists will return `true` otherwise `false` will be returned.

Note: if property exists and has `null` as value it also considered as available property and this method will return `true`.

```php
$dataClass->has('name'); // true
$dataClass->has('none_exists_prop', 'designed_by'); // true
```

### `empty` - Remove all properties

```php
$dataClass->empty();
```

### `isEmpty` - Check has any properties

The `isEmpty` method will check if object has any property and return `boolean` value.

```php
$dataClass->isEmpty();
```

## DataTypedSet

The `DataTypedSet` is extending [DataStruct](#datastruct) and have all functionalities from that and in addition have some unique functionalities in general can be used to define custom property accessors which known as Getter/Setter methods. A getter is a method that gets the value of a specific property. On the other hand, a setter is a method that sets the value of a specific property.\
\
You don't need to use getter/setter for everything and they should used wisely only when it's needed.\
They might needed to be used when you have for example a property were accept only a specific PHP type. The `date_time_created` property can be one of those which only accept a `\DateTime` object.

```php
$dataClass = new BlogPost;
$dataClass->created_date = '2020-01-14';
// Throw PropertyError Exception

$dataClass = new BlogPost;
$dataClass->created_date = new \DateTime('2020-01-14');
```

### Read-only properties

Read-only properties are created by defining only a getter method for a property. A `PropertyIsGetterOnlyError` exception is thrown in attempt to set a read-only property.

Methods with `protected` visibility which are prefixed with `get`, `has` and `is` all determined as the property getter. like: `getPropertyName()`, `hasAnyOrder()`, `isAdministration()`.\
\
\&#xNAN;*Note: adding docblock for property is not mandatory but it's recommended.*

```php
/**
 * @property-read $property
 */
class ReadOnlyProperty
    extends DataTypedSet
{
    protected function getProperty()
    {
        return 'value';
    }
}

$a = new ReadOnlyProperty;
echo $a->property;     // value
$a->property = null;   // throws PropertyIsGetterOnlyError
```

### Protect construct level properties by Read-only

Read-only properties are often used to provide read access to a property that was provided during *construct*, which should stay unchanged during the life time of an instance.

In this example below, we can have access to the connection used by Model class afterward and options is used by class itself for some internal usage inside the class and it's not a property accessor.

```php
/**
 * @property-read Connection $connection
 */
class Model
    extends DataTypedSet
{
    /** @var Connection */
    private $connection;
    
    protected $options;

    function __construct(Connection $connection, array $options = [])
    {
        $this->connection = $connection;
        $this->options = $options;
    }
    
    // Getter method
    
    protected function getConnection()
    {
        return $this->connection;
    }
}

$model = new Model(new Connection);

$model->connection = null; // throws PropertyIsGetterOnlyError
unset($model->connection); // throws PropertyIsGetterOnlyError
```

### Write-only properties

Read-only properties are created by defining only a setter method for a property. A `PropertyIsSetterOnlyError` exception is thrown in attempt to get a write-only property.

Methods with `protected` visibility which are prefixed with `set`, `give` all determined as the property getter. like: `setPropertyName($name)`, `giveConnection($connection)`.\
\
\&#xNAN;*Note: adding docblock for property is not mandatory but it's recommended.*

```php
/**
 * @property-write $property
 */
class WriteOnlyProperty
    extends DataTypedSet
{
    private $property;
    
    protected function setProperty($value)
    {
        return $this->property = $value;
    }
}

$a = new WriteOnlyProperty;
$a->property = 'value';
echo $a->property;           // throws PropertyIsSetterOnlyError
```

### Property Type declaration

Type declarations allow property to require a certain type at setter/getter level but mostly it would define on setters. \
On set property value the `PropertyError` will thrown if value type is mismatch by method arguments definition. When type declaration not happen on method argument and check happen inside the method block for given type and it's not match the `PropertyTypeError` expected to thrown as well.&#x20;

{% hint style="info" %}
When using Type Declaration the method argument should be nullable. it's needed when we gonna to unset or remove property by unseting property the underlying value will set to `null`.
{% endhint %}

```php
/**
 * @property-write $created_date
 */
class BlogPost
    extends DataTypedSet
{
    protected $date_created;
    protected $tags;
    
    protected function setCreatedDate(?\DateTime $datetime)
    {
        return $this->date_created = $datetime;
    }
    
    protected function setTags($tags)
    {
       if (!is_array($tags) || empty($tags))
           throw new Struct\PropertyTypeError(
              'tags should be none empty array.'
           );
    }
    
    // ...
}
```

### `import` - Set data to object

The `import` method will replace all existing property values by new values provided to method.

{% hint style="info" %}
As DataTypedSet could be sensitive to data given to properties couple of exceptions can happen when importing data.
{% endhint %}

Here is an example how the exceptions can be ignored while importing data: &#x20;

```php
$DataTypedSet = new ErrorProneDataTypedSet;

// Ignore All Errors Related To Getter Only Property
$DataTypedSet->import($someData, [PropertyIsGetterOnlyError::class]);

// Ignore All Errors Related To Getter Only Property and Immutable Property
$DataTypedSet->import($someData, [
   PropertyIsGetterOnlyError::class, 
   PropertyIsImmutableError::class
]);

// Ignore All Property Error, including getter only, immutable, etc.
$DataTypedSet->import($someData, [PropertyError::class]);

// Ignore All Exceptions regarding if it's property error or anything
$DataTypedSet->import($someData, [\Exception::class]);
```

### more methods ...

The `DataTypedSet` is extending [DataStruct](#datastruct) and they have same functionalities except methods mentioned above hereto know more about available methods check the documentation from [DataStruct](#datastruct) .

## DataHashMap

The `DataTypedSet` is extending [DataStruct](#datastruct) and have all similar functionalities from that. what specific is about hash map data object is allow to set [none scalar](https://www.php.net/manual/en/function.is-scalar.php) keys for data.&#x20;

```php
$dataClass = new DataHashMap;
$dataClass->set([1, 2, 3], 6);

echo $dataClass->get([1, 2, 3]); // 6
```

As none scalar types can be assigned as a key holding values there is no possibility to use property accessors, so two methods are added to `DataHashMap` object:

### `set` - Set property

```php
$dataClass = new DataHashMap;

$dataClass->set('name', 'PHP');

$closure = function () {};
$dataClass->set($closure, 'value');
```

### `get` - Get property

```php
$dataClass->get('name');
$dataClass->get($closure);
$dataClass->get([1, 2, 3]);
```

### `del` - Remove property

```php
$dataClass->del('name');
$dataClass->del($closure);
$dataClass->del([1, 2, 3]);
```

### `has` - Check property exists

```php
$dataClass->has('name');
$dataClass->has($closure);
$dataClass->has([1, 2, 3]);
```

### more methods ...

The `DataTypedSet` is extending [DataStruct](#datastruct) and have all functionalities from that to know more about available methods check the documentation from [DataStruct](#datastruct) .

## CollectionObject

The `CollectionObject` is using to store any kind of data type in a collection set, it's possible to define set of meta data to it when data is stored, which can used to query the whole data based on theses associated meta data. for every same data a unique datum id will be generated which is used as a reference to fetch data again.

### It's Countable

```php
// Add random data amount
for ($i=1; $i<=rand(1, 10); $i++) {
   $collectionObject->add(uniqid());
}

$collectionObject->count();
count($collectionObject);
```

### ObjectID for data type

Every object when stored to the collection will get a unique ID and can be used to access to the object.\
here is how the uniqueID can be generated in forehand for an object.

```php
$objectToStore = [1, 2, 3, 4, 5];
$objectId = DatumId::of($objectToStore);
echo $objectId->__toString();
// 00d34736902d23bd0da1e4de9e79d94a


$objectToStore = pack("nvc*", 0x1234, 0x5678, 65, 66);
$objectId = DatumId::of($objectToStore);
echo $objectId->__toString();
// 3f5145ad7bb6fe230a3badefc32f90e7
```

### `add` - Adding data

Add data to the collection, data can be any value type. with the second argument it's possible to define set of meta data to the stored data which can fetched later or used as a term when querying the collection.

*Note: when same Data is exists in collection the meta data will be replaced with the given meta data in `add` method.*

```php
$collectionObject = new CollectionObject;

$objectId = $collectionObject->add([1, 2, 3, 4, 5]);
$objectId = $collectionObject->add('abcdef');


// Associated meta data to stored object
//
$objectId = $collectionObject->add(new User(1), [
  'username' => 'mail@email.com'
]);

$objectId = $collectionObject->add(
   pack("nvc*", 0x1234, 0x5678, 65, 66),
   [
     'pack_format' => 'nvc*',
   ]
);
```

### `get` - Get stored object by objectId

Get stored object data in collection by its unique object id, will return `null` if not found.

```php
$object = $collectionObject->get(
  new DatumId('00d34736902d23bd0da1e4de9e79d94a')
);
```

**Get additional meta data associated with the object:**\
with a second argument the meta data and object itself is available to the callback function.\
if object with given id not exists in collection null will returned without calling the given callback function.

```php
$objectId = $collectionObject->add(new User(1), [
  'username' => 'mail@email.com'
]);

[$object, $objData] = $collectionObject->get($objectId,
   function($obj, $meta) {
      return [$obj, $meta];
   }
);

print_r($objectData);
// [ 'username' => 'mail@email.com' ]
```

### `find` - Find an object by meta data

Search for stored objects that match given accurate meta data. The result returned is the [IteratorWrapper](https://poirot-framework.gitbook.io/poirot/standard-core-libraries#iteratorwrapper) instance include all the object matched with the criteria data.

Fetch all objects inside collection:

```php
foreach ($collectionObject->find([]) as $hashIdAsString => $object) {
    // Do anything with all stored objects
}
```

Find based on the criteria matching the metadata associated with each object:

```php
$data = ['name' => 'PHP', 'designed_by' => 'Rasmus Lerdorf' ];
$collectionObject->add((object) $data, $data);

$object = $collectionObject->find(['name' => 'PHP'])->current();
/*
object(stdClass)
  public 'name' => string 'PHP'
  public 'designed_by' => string 'Rasmus Lerdorf'
*/
```

### `del` - Remove object from collection

Remove an object by the ObjectID. if no object with same ObjectID exists on collection it will do nothing.

```php
$collectionObject->del(
   DatumId::of([1, 2, 3, 4, 5])
);
```

### `setMetaData` - Set object meta data

Set meta data associated with the existence object. It will `thrown` an Exception if object not found.

```php
$objectId = $collectionObject->add(new User(1));
$collectionObject->setMetaData($objectId, ['meta' => 'data']);

$objData = $collectionObject->get($objectId, function($_, $meta) {
    return $meta;
});

print_r($objData);
// ['meta' => 'data']
```

## CollectionPriorityObject

The `CollectionPriorityObject` is extending `CollectionObject` to allow add objects with priority order. To read about all available methods see the [CollectionObject](#collectionobject).&#x20;

### `add` - Adding data

The third argument of add method indicate the priority order of the object.

```php
$collectionObject = new CollectionPriorityObject;
$collectionObject->add('C', [], 2);
$collectionObject->add('A', [], 4);
$collectionObject->add('D', [], 1);
$collectionObject->add('B', [], 3);

foreach($collectionObject->find([]) as $storedObj) {
    echo $storedObj;
}

// ABCD
```

priority order also can be defined as the meta data:

```php
$collectionObject->add('C', ['__order' => '2']);
$collectionObject->add('A', ['__order' => 4]);
$collectionObject->add('D', ['__order' => 1]);
$collectionObject->add('B', ['__order' => 3]);
```

### more methods ...

More methods is similar with `CollectionObject`, To read about all available methods see the [CollectionObject documentation](#collectionobject).

## Value Object Abstraction

In practical terms you can think of [value objects](https://martinfowler.com/eaaCatalog/valueObject.html) as your own primitive types. And they should behave as ones. It means that value objects should always be [immutable](https://en.wikipedia.org/wiki/Immutable_object)! This is crucial otherwise it’s hard to avoid issues with values changing unexpectedly (values should be replaced, not modified!).

{% hint style="info" %}
It’s important to remember about immutability (none of these methods can modify internal state) and to not to put any actual business logic inside value objects – they should be as self-contained as possible.
{% endhint %}

### Declaration

To define a value object of any type it should extends the `aValueObject` abstract class, and you should follow these simple rules when working with ValueObject in Poirot:

#### Constructor:

* Value Object will have `__construct` method.
* Constructor method should have all possible Value Object needed properties as arguments in constructor method.
* Each constructor arguments should have an accessor method with same name of argument.\
  for example, if you have `$amount` variable it should have one protected accessor method named `withAmount($value)` which can change the state of object for the property.
* The parent `__construct` method call on constructing object to initialize the init state.

```php
class Money
  extends aValueObject
{
    private $amount;
    protected $currency;

    function __construct(int $amount, string $currency = 'EUR')
    {
        parent::__construct();

        $this->withAmount($amount);
        $this->withCurrency($currency);
    }

    protected function withAmount($amount)
    {
        $this->amount = $amount;
        return $this;
    }

    protected function withCurrency($currency)
    {
        $this->currency = $currency;
        return $this;
    }
    
    // Rest of implementation ...
}
```

#### Validation:

When creating a new value object we need to make sure that it has a valid state on constructing. when values are not correct Exception should thrown on creating object. To read more see [Validation](#validation) section.

#### Getting current state properties:

To know what state Value Object is and to get property values that we need to be expose to outside the getter methods can be defined:

```php
class Money
  extends aValueObject
{
    private $amount;
    protected $currency;

    function __construct(int $amount, string $currency = 'EUR')
    {
        parent::__construct();

        $this->withAmount($amount);
        $this->withCurrency($currency);
    }

    protected function withAmount($amount)
    {
        $this->amount = $amount;
        return $this;
    }

    protected function withCurrency($currency)
    {
        $this->currency = $currency;
        return $this;
    }
    
    // Getter Methods:
    
    protected function getAmount()
    {
        return $this->amount;
    }
    
    protected function getCurrency()
    {
        return $this->currency;
    }
}

$moneySpent = new Money(100, 'USD');
echo $moneySpent->getAmount() . ' ' . $moneySpent->getCurrency();
// 100 USD
```

#### Change object state and immutability:

Changing the object state and immutability will handled by abstract class the only thing is to define an accessor protected method that is prefixed by `with`. Check the example below and how it's been used:

```php
$eur = new Money(100, 'EUR');
$usd = $eur->withCurrency('USD'); // change the state and return new object

echo $eur->getCurrency(); // EUR
echo $usd->getCurrency(); // USD
```

to make it understandable to most IDEs some notations can be added to class:

```php
/**
 * @method Money  withAmount($amount)
 * @method Money  withCurrency($currency)
 *
 * @method int    getAmount()
 * @method string getCurrency()
 */
class Money
  extends aValueObject
{
    private $amount;
    protected $currency;

    function __construct(int $amount, string $currency = 'EUR')
    {
        parent::__construct();

        $this->withAmount($amount);
        $this->withCurrency($currency);
    }

    protected function withAmount($amount)
    {
        $this->amount = $amount;
        
        // This is not mandatory to have but can help with auto-completion 
        // in code editors
        return $this;
    }
    
    // Rest of implementation here
    // ...
```

### It's Traversable

```php
$money = new Money(100, 'EUR');

$asArray = iterator_to_array($money);
/*
['amount' => 100, 'currency' => 'EUR'];
*/
```

### It's Serializable

```php
$money = new Money(100, 'EUR');
$serialized = serialize($money);

$moneyUnserialized = unserialize($serialized);
echo $moneyUnserialized->getAmount(); 
// 100
```

### Check equality

Two value objects are equal not when they refer to the same object, but when the value that they hold is equal, e.g. two different one hundred dollar bills have the same value when we use them to buy something (class Money would be a value object).\
\
To check equality of two value object the abstract object will check the whole values one by one and the class implementation to see if the given object is extend the current value object, which mean something similar to this method:

```php
function isEqualTo(iValueObject $object): bool
{
   return get_class($this) === get_class($object) && $this == $object;
}
```

The `isEqualTo` method can be override by object extending abstract value object, for the Money object it could be re written: &#x20;

```php
class Money
  extends aValueObject
{
    private $amount;
    protected $currency;

    function __construct(int $amount, string $currency = 'EUR')
    {
        parent::__construct();

        $this->withAmount($amount);
        $this->withCurrency($currency);
    }
    
    function isEqualTo(iValueObject $object): bool
    {
        if (! $object instanceof Money) 
           return false;
           
        return $this->getAmount() === $object->getAmount()
            && $this->getCurrency() === $object->getCurrency();
    }
    
    // Rest of implementation here
    // ...
}


$eur = new Money(100);
$usd = $eur->withCurrency('USD');

var_dump($eur->isEqualTo($usd)); // false
```

### Validation

Let’s take currency from our Money object, Currency value should always consist of three uppercase letters, so why not to convert it to its own Value Object:

{% hint style="info" %}
Validation should happen on **construct** level and when value object state got changed by **property accessor** as well.
{% endhint %}

Here we only make sure that currency code has a correct format, but as an improvement it could also validated against [ISO 4217](http://www2.1010data.com/documentationcenter/discover/FunctionReference/DataTypesAndFormats/currencyUnitCodes.html) currency list (or its subset).

```php
class Currency
  extends aValueObject 
{
    private $currency;
 
    function __construct(string $currency)
    {
        // The exception will thrown if currency is invalid.
        $this->withCurrency($currency);
    }
 
    function withCurrency(string $currency)
    {
       $currency = strtoupper($currency);
        if (strlen($currency) !== 3 || !ctype_alpha($currency))
            throw new InvalidArgumentException(
              'Currency has to consist of three letters'
            );
            
        $this->currency = $currency;
        return $this;
    }
    
    // Rest of the definition should be here
    // ...
}
```

### Additional helper methods

In addition to standard getter methods for internal state retrieval, VOs can have helper methods with various functionality. This includes creating new values (either entirely new or based on the current one, e.g. result of adding some amount to Money object), providing different representation for its internal state (e.g. IPAddress VO can present IP address as string or binary data), checking if value object meets some condition (e.g. DateRange VO can have a method to find out if it overlaps another DateRange object), etc.

```php
class Money
  extends aValueObject
{
    // Money definition from previous example should be here
 
    function add(Money $toAdd): Money
    {
        if ($this->getCurrency() !== $toAdd->getCurrency())
            throw new InvalidArgumentException(
                'You can only add money with the same currency'
            );
        
        if ($toAdd->getAmount() === 0)
            return $this;
        
        return new Money(
           $this->getAmount() + $toAdd->getAmount(), 
           $this->getCurrency()
        );
    }
}
```

## PriorityQueue

A `PriorityQueue` is very similar to a Queue. Values are pushed into the queue with an assigned priority, and the value with the highest priority will always be at the front of the queue.\
\
The PHP Default [SplPriorityQueue](https://www.php.net/manual/en/class.splpriorityqueue.php) acts as a heap; on iteration, each item is removed from the queue. This class will allow to iterate multiple time over internal queue which is implemented default php `SplPriorityQueue`.

{% hint style="info" %}
Iterating over a Queue and successive pop operations will not remove items from the queue.
{% endhint %}

### Simple Usage

The `PriorityQueue` only has a method `insert` and provide Traversable object to travers over the items in queue without deleting them.

```php
$queue = new PriorityQueue(PriorityQueue::Ascending);
$queue->insert('C', 3);
$queue->insert('D', 4);
$queue->insert('B', 2);
$queue->insert('A', 1);

foreach ($queue as $value) {
    echo $value;
}

// ABCD
```

### SplQueue

There is some workaround and fixes for default PHP `SplPriorityQueue` which is available as these classes. these two classes are extend `SplPriorityQueue` and used internally by `PriorityQueue`.

#### `SplDescendingQueue`

#### `SplAscendingQueue`
