# Standard Data Types

## Introduction

**Standard Data Types Libraries** is wrapper around existence PHP data types which adding more convenient methods and useful helpers to them. 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>

## Arrays

Additionally to the [rich set of PHP array functions](https://secure.php.net/manual/en/book.array.php), there is a collection of convenience helper methods array helper provides extra methods allowing you to deal with arrays more efficiently.

{% hint style="info" %}
**Method name convention:**\
Any method from `StdArray` which prefixed with get like `getKeys`, `getLast`, ... has no effect on the current object and will return new instance of object include the expected result.
{% endhint %}

### Instantiate and Construct&#x20;

`StdArray` can be instantiated around existing array or from an initial empty array:

```php
$arrOne   = new StdArray();
$arrTwo   = new StdArray([1, 2, 3, 4]);
$arrThree = new StdArray(new \ArrayIterator([1, 2, 3, 4]));

// Through factory static method:
$arr = StdArray::of([1, 2, 3, 4]);
```

`StdArray` can built around different data sources such as `Traversable`, `Object`, `json string`, reference memory address to array variable.

All accepted different data types can be constructed through construct method and none-strict mode by passing second argument to constructor.&#x20;

```php
$initialValue = (object) ['field' => 'value', 'otherField' => 'other-value']

// can be constructed on none-strict mode
$stdArr = new StdArray($initialValue, false);
```

### Converting To Array

#### Traversable:

```php
$initialValue = function() {
  yield field => 'value';
  yield otherField => 'other-value';
}();

$stdArr = StdArray::ofTraversable($initialValue);
```

#### stdClass object:

```php
$initialValue = new \stdClass();
$initialValue->field = 'value';
$initialValue->otherField = 'other-value';

$stdArr = StdArray::ofObject($initialValue);
```

#### class object:

```php
$initialValue = new class {
  public $field = 'value';
  public $otherField = 'other-value';
};

$stdArr = StdArray::ofObject($initialValue);
```

#### json string:

```php
$initialValue = '{
  "field": "value",
  "otherField": "other-value"
}';

$stdArr = StdArray::ofJson($initialValue);
```

#### memory address reference:

```php
$initialValue = [1, 2, 3, 4];
$stdArray = StdArray::ofReference($initialValue);
$stdArray->append(5);

print_r($initialValue); // [1, 2, 3, 4, 5]
```

### It's Iterable&#x20;

`StdArray` is iterable object and items are accessible through `\Iterator`interface.

```php
$stdArray = new StdArray(['a', 'b', 'c', 'd']);
while ($stdArray->valid()) {
    echo $stdArray->key() . '=' . $stdArray->current() . PHP_EOL;
    $stdArray->next();
}
```

### It's Array Accessible

The object items are accessible just like a regular array, the only difference is when key is undefined instead of trigger `E_NOTICE` error of Array key does not exist, it will just return `null` as a value.

```php
$stdArray = new StdArray(['a', 'b', 'c', 'd']);

// when key dosen't exists
var_dump( $stdArray[100] );        # null
var_dump( isset($stdArray[100]) ); # false

echo $stdArray[1];                 # b

$stdArray['field'] = 'value';
echo $stdArray['field'];           # value
```

### It's Countable

```php
$initialValue = [1, 2, 3, 4];
$stdArray = new StdArray($initialValue);
count($stdArray); // 4
```

### Array keys accessible by reference

```php
$stdArray = new StdArray([1, 2, 3, 4]);
$stdArray['new-item'] = 'value';

$t = &$stdArray['new-item'];
$t = 'new-value';
echo $stdArray['new-item']; // new-value

// Internal array:
$stdArray = new StdArray([1, 2, 3, 4]);

$internalArray = &$stdArray->asInternalArray;
array_pop($internalArray);

// [1, 2, 3]
var_dump(
  $stdArray->asInternalArray
); 
```

### `lookup` - Search array elements by query path

The `lookup` method search array to matching against given query path `expression` and will return `\Generator` traversable of matching elements or exact value of an element  based on the given query path `expression`type.\
\
Any expression which does not return nodes will return `null`.\
\
These path expressions look very much like the path expressions you use with traditional computer file systems:

<div align="left"><img src="https://2021537479-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LkFIsrp32T2osZZ-hee%2F-M4AALm2_KBJ_NUqWok7%2F-M4DVl9FiuHdc5l9TdtT%2Fimg_path_folders.jpg?alt=media&#x26;token=65157915-03c8-4f2c-aafb-4aa06e9f6041" alt=""></div>

#### Fetch one by nested hierarchy lookup:

Selects from the root by hierarchy descendant.

```php
$arr = [
    'books' => [
        [
            'title' => 'Professional PHP Design Patterns',
            'author' => [
                'firstname' => 'Aaron',
                'lastname' => 'Saray',
            ],
        ],
    ],
];

$result = StdArray::of($arr)->lookup('/books/0/title');
// Professional PHP Design Patterns

$result = StdArray::of($arr)->lookup('/books/0/not_exist_field');
// null
```

First `slash` given to expression can be removed, so this query will do the same:&#x20;

```php
$result = StdArray::of($arr)->lookup('books/0/title');
```

#### Fetch all by nested hierarchy lookup:

By adding `*` beside the element in query, simply expected path to match with more than one element or make the result to `\Generator` iterable object.

```php
$result = StdArray::of($arr)->lookup('/books/0/*title');
foreach ($result as $k => $v) {
   print $k .': ' . $v;
}

// title: Professional PHP Design Patterns
```

#### Regex lookup:

Regex matching expression can be defined between `~` characters:

```php
$arr = [
    'books' => [
        [
            'title' => 'Professional PHP Design Patterns',
            'author' => [
                'firstname' => 'Aaron',
                'lastname' => 'Saray',
            ],
        ],
    ],
];

## First element key which match with given regex
#
$result = StdArray::of($arr)->lookup('/books/0/author/~.+name$~');
// Aaron

## All elements which matches with given regex
#
$result = StdArray::of($arr)->lookup('/books/0/author/*~.+name$~');
// or
$result = StdArray::of($arr)->lookup('/books/0/author/*~firstname|lastname~');
foreach ($result as $k => $v) {
   print $k .': ' . $v . PHP_EOL;
}
// firstname: Aaron
// lastname: Saray
```

#### Match lookup in specified depth only:

The depth are counting from 1 for the next level of hierarchy elements inside root. check this out:

```php
$arr = [
    'Python' => [
        // Depth 1
        'name' => 'Python',
        "designed_by" => "Guido van Rossum",
        "details" => [
            // Depth 2
            'name' => 'python language',
            'typing_discipline' => [
                'tags' => ['Duck', 'dynamic', 'gradual'],
            ],
        ],
    ],
    'PHP' => [
        // Depth 1
        'name' => 'PHP',
        "designed_by" => "Rasmus Lerdorf",
        "details" => [
            // Depth 2
            'name' => 'php language',
            "typing_discipline" => [
                'tags' => ['Dynamic', 'weak'],
            ],
        ],
    ],
];

## First element in depth level ONE matching with given expression
#
$result = StdArray::of($arr)->lookup('/>/name');
// Python

## All elements in depth level TWO matching with given expression
#
$result = StdArray::of($arr)->lookup('/>2/*name');
foreach ($result as $k => $v) {
   print $k .': ' . $v . PHP_EOL;
}
// name: python language
// name: php language
```

#### Recursive lookup - Match lookup expression within any depth:

The result of `//` expression is always an `\Generator` traversable.

```php
$arr = [
    'Python' => [
        'name' => 'Python',
        'details' => [
            'name' => 'python language',
            'typing_discipline' => [
                'tags' => ['Duck', 'dynamic', 'gradual'],
            ],
        ],
    ],
    'PHP' => [
        'name' => 'PHP',
        'details' => [
            'name' => 'php language',
            'typing_discipline' => [
                'tags' => ['Dynamic', 'weak'],
            ],
        ],
    ],
];

$result = StdArray::of($arr)->lookup('//name');
foreach ($result as $k => $v) {
    print $k .': ' . $v . PHP_EOL;
}

// name: Python
// name: python language
// name: PHP
// name: php language
```

#### Considerations about lookup resulting in same keys:

As the result of query lookup is a Generator some times we have duplicated key in the result so using some functions like iterator\_to\_array will not give us the expected result.\
\
**check this sample:**\
as you see here we have two similar key in the iterator each came from the nested element on main array.\
when use query take this into consideration.

```php
## Fetch first tag in every item
#
$result = StdArray::of($arr)->lookup('//tags/>/*0')
foreach ($result as $k => $v) {
  print $k .': ' . $v . PHP_EOL;
}
// 0: dynamic 0: dynamic
```

#### Reindex lookup result:

By re-index expression token the result will get re-indexed on numeric values started from zero, it could be a solution the the same key on the traversable result which also mentioned.\
\
here are the examples:\
to re-index `:` should be comes after `/`

```php
$result = StdArray::of($arr)->lookup('//tags/>/*0/:');
iterator_to_array($result);
// ['dynamic', 'dynamic']
```

#### Samples on Combination of variant query expressions:

```php
$arr = [
    'Python' => [
        'name' => 'Python',
        'details' => [
            'name' => 'python language',
            'typing_discipline' => [
                'tags' => ['dynamic', 'duck', 'gradual'],
            ],
        ],
    ],
    'PHP' => [
        'name' => 'PHP',
        'details' => [
            'name' => 'php language',
            'typing_discipline' => [
                'tags' => ['dynamic', 'weak'],
            ],
        ],
    ],
];

## Fetch any tags inside PHP element
#
$result = StdArray::of($arr)->lookup('~PHP~//tags');
foreach ($result as $k => $v) {
    print $k .': ' . implode(', ', $v) . PHP_EOL;
}
// tags: Dynamic, weak


## Fetch All available tags
#
$result = StdArray::of($arr)->lookup('//tags/:');
foreach ($result as $v) {
    print implode(', ', $v) . PHP_EOL;
}
// dynamic, duck, gradual
// dynamic, weak


## Fetch Re-Indexed result of First(0) and Third(2) tag from each element
#
$result = StdArray::of($arr)->lookup('//tags/>/*~0|2~/:');
iterator_to_array($result);
// ['dynamic', 'gradual', 'dynamic']

$result = StdArray::of($arr)->lookup('//tags/:/>/*~0|2~/:')
// ['dynamic', 'gradual', 'dynamic']
```

### `lookup` - Access by reference

The `lookup` method can access elements by their memory reference address.

```php
$stdArray = new StdArray([
    [
        'user' => 'user@domain.com',
        'name' => 'name a',
        'data' => ['id' => '1234'],
    ],
    [
        'user' => 'user2@domain.com',
        'name' => 'name b',
        'data' => ['id' => '5678'],
    ],
]);

$firstUserByReference = &$stdArray->lookup('/0/user');
$firstUserByReference = 'changed_user@domain.com';

$names = &$stdArray->lookup('/>/*name');
foreach ($names as &$name)
    $name = strtoupper($name);

$ids = &$stdArray->lookup('//id/:');
foreach ($ids as $i => &$id)
    $id = (int) $id;

/*
[
  [
    'user' => 'changed_user@domain.com',
    'name' => 'NAME A',
    'data' => ['id' => 1234],
  ],
  [
    'user' => 'user2@domain.com',
    'name' => 'NAME B',
    'data' => ['id' => 5678],
  ],
];
*/
```

### `filter` - Filter Array Items

This method will affect current elements inside array object, check out the following code. We'll use the `filter` helper to capitalise each string element with `strtoupper` and remove all none string and empty elements.

```php
$initialValue = ['a', 'b', 'c', '', 0];
$stdArray     = new StdArray($initialValue);

$stdArray->filter(function($value) {
    if (!is_string($value) || $value === '')
        /** @var IteratorWrapper $this */
        $this->skipCurrentIteration();

    if (is_string($value))
        $value = strtoupper($value);

    return $value;
});

// ['A', 'B', 'C']
print_r(
  $stdArray->asInternalArray
);
```

Array keys can change as well with `filter` method:

```php
$initialValue = ['a', 'b', 'c'];
$stdArray     = new StdArray($initialValue);

$stdArray->filter(function($value, &$key) {
    // 65 is ascii code for 'A' char
    $key = chr(65 + $key);
    
    // actual value always should be returned by filter
    return $value;
});

// ['A' => 'a', 'B' => 'b', 'C' => 'c']
print_r(
   $stdArray->asInternalArray
);
```

#### Further reading

As `filter` method use `IteratorWrapper` internally to provide the functionalities for filter elements to know better about all available features see `IteratorWrapper` [documentation](https://poirot-framework.gitbook.io/poirot/components/standard-core-libraries#iteratorwrapper) for more.

### `append` - Append to end of array

The `append` method add a given value to the end of array, if given array key as a second argument exists will replaced by new value and will moved to the end of array elements. &#x20;

```php
$stdArray = StdArray::of([1986 => 'a', 1983 => 'b']);

$stdArray->append('c');
// [1986 => a, 1983 => b, 1987 => c]

$stdArray->append(1, 'one');
// [1986 => a, 1983 => b, 1987 => c, 'one' => 1]

$stdArray->append('d');
// [1986 => a, 1983 => b, 1987 => c, 'one' => 1, 1988 => 'd']

$stdArray->append('e', 1986);
// [1983 => b, 1987 => c, 'one' => 1, 1988 => 'd', 1986 => 'e']
```

### `prepend` - Prepend to beginning of array

The `prepend` method add and element to the beginning of array and re-index array keys. if given array key as a second argument exists will replaced by new value and will moved to the beginning of array elements.&#x20;

```php
$stdArray = StdArray::of([1986 => 'b', 1983 => 'c']);

$stdArray->prepend('a');
// [0 => 'a', 1 => 'b', 2 => 'c']

$stdArray->prepend(1, 'one');
// ['one' => 1, 0 => 'a', 1 => 'b', 2 => 'c']

$stdArray->prepend('x');
// [0 => 'x', 'one' => 1, 1 => 'a', 2 => 'b', 3 => 'c']

$stdArray->prepend(1, 'one');
// ['one' => 1, 0 => 'x', 1 => 'a', 2 => 'b', 3 => 'c']
```

### `insert` - Insert an element to array

The `insert` method will append an element to the array if not any specific key given as second argument or set value of the element if key exists. \
\
When key is not given the value will appended to array:

```php
$stdArray = StdArray::of([])->insert('a');
// [0 => 'a']

$stdArray = $stdArray->insert('b');
// [0 => 'a', 1 => 'b']
```

When we specify key the default behaviour is to replace value with new one:

```php
$stdArray = StdArray::of([0 => 'a', 1 => 'zz']);
$stdArray->insert(1, 'b');
// [0 => 'a', 1 => 'b']
```

By comparison callable as third arguments it's possible to define the insert behaviour when given key is exists in array:&#x20;

```php
$comprator = function ($givenValue, $currValue, $key) {
    if ($givenValue === $currValue)
        return StdArray::InsertDoSkip;
    elseif ($key == 'words')
        return StdArray::InsertDoPush;

    // otherwise when no insert instruction returned 
    // the default behaviour is to replace value
    // which is considered as StdArray::InsertDoReplace
};

$stdArray = StdArray::of(['words' => 'a', 'time' => 1585909377]);
$stdArray
  ->insert('b', 'words', $comprator)
  ->insert(time(), 'time', $comprator);
  
 // ['words' => ['a', 'b'], 'time' => 1585909389]
```

### `merge` - merges the given array recursively

The `merge` method gives flexibility on how to merges the given array into the current array by introducing merge strategies.\
\
By default when element has an integer key it will be appended to array.

```php
$stdArray = StdArray::of([ 0 => 'foo'])
   ->merge([ 0 => 'bar']);
   
// [0 => foo, 1 => bar]
```

The way that merge behaves can be changed with providing merge strategy as a second argument:\
**Merge Strategy** is a callable or integer value of compare result which instruct the merge how to behave when same element with key exists, the returned number is `-1, 0, 1` which actually has equality constant defined in the class in order as `MergeDoReplace`, `MergeDoMerging`, `MergeDoPush`.\
\
The code below changes the merge behaviour in this way when there is an existing integer key try to merge their value together and for the rest default strategy is called.&#x20;

```php
$a = [ 0 => 'foo'];
$b = [ 0 => 'bar'];

$stdArray = StdArray::of($a)
    ->merge($b, function ($value, $currValue, $key) {
        if (is_int($key))
            return StdArray::MergeDoMerging;

        return StdArray\DefaultMergeStrategy::call($value, $currValue, $key);
    });
    
// [0 => ['foo', 'bar']]
```

If the array not consist of other different mix of structures which is not important how it's gonna merge, the codes above can be simplified to this:

```php
$stdArray = StdArray::of($a)
    ->merge($b, StdArray::MergeDoMerging);
```

#### Other examples to see how merge works in default:

```php
$stdArray = StdArray::of([ 0 => ['foo']])
    ->merge([ 0 => 'bar']);
// [['foo'], 'bar']

$stdArray = StdArray::of([ 0 => 'foo'])
    ->merge([ 0 => ['bar']]);
// ['foo', ['bar']]

$stdArray = StdArray::of(['key' => 'foo'])
    ->merge(['key' => 'bar']);
// ['key' => 'bar']

$stdArray = StdArray::of(['key' => ['foo']])
    ->merge(['key' => 'bar']);
// ['key' => ['foo', 'bar']]

$stdArray = StdArray::of(['key' => 'foo'])
    ->merge(['key' => ['bar']]);
// ['key' => ['bar', 'foo']]


$a = [ 'key' => [['foo', 'a' => null], 'a' => 1], 'foo'];
$b = [ 'key' => [[['bar'], 'a' => 1, 'b' => 2], 'b' => 2], 'bar'];

$stdArray = StdArray::of($a)
    ->merge($b);
    
/*
[
    'key' => [
        [
            'foo',
            'a' => 1,
            ['bar'],
            'b' => 2,
        ],
        'a' => 1,
        'b' => 2,
    ],
    'foo',
    'bar',
]
*/
```

#### Another merge strategy example:&#x20;

If any field has a same value it's not gonna merge otherwise the values getting merged for the rest.\
In this sample `'value'` field has same value in both array and will not get merged.

```php
$a = ['field' => [
  'type' => 'invalid-type',
  'message' => 'Error message 1.',
  'value' => 'invalid_value',
]];

$b = ['field' => [
  'type' => 'not-in-range',
  'message' => 'Error message 2.',
  'value' => 'invalid_value',
]];

$messages = StdArray::of($a)
   ->merge($b, function ($value, $currVal) {
        if ($value === $currVal)
           return StdArray::MergeDoReplace;
        return StdArray::MergeDoMerging;
})->asInternalArray;

/*
[
    'field' => [
        'type' => [
            'invalid-type',
            'not-in-range',
        ],
        'message' => [
            'Error message 1.',
            'Error message 2.',
        ],
        'value' => 'invalid_value',
    ],
]
*/
```

### `except` - Remove array element(s) by key(s)

The `except` method will remove all given key(s) element from array if key exists.

```php
$initialValue = [1 => 'b', 3 => 'd', 'a' => 0, 'b' => 1, 'd' => 3];
$stdArray = StdArray::of($initialValue)
    ->except(...[1, 3]);

// ['a' => 0, 'b' => 1, 'd' => 3]
```

### `keep` - Keep only given key(s) in array

The `keep` method only keeps the given keys in array if they exists and rest of array will be cleared from unwanted keys.

```php
$initialValue = [1 => 'b', 3 => 'd', 'a' => 0, 'b' => 1, 'd' => 3];
$stdArray = StdArray::of($initialValue)
    ->keep(...[1, 3]);

// [1 => 'b', 3 => 'd'];
```

### `keepIntersection` - Keep values that are present in all the arguments.

Computes the intersection of arrays, compares data by a callback function.

```php
$initialValue = ["a" => "green", "b" => "brown", "c" => "blue", "red"];
$stdArray = StdArray::of($initialValue)
    ->keepIntersection(["a" => "GREEN", "B" => "brown", "yellow", "red"], 
      function($a, $b) {
        return \strcasecmp($a, $b);
      });

// ['a' => 'green', 'b' => 'brown', 0 => 'red']
```

### `getFirst` - Get first array element

The `getFirst` method return first array element of array which the result is another `StdArray` object with the single element from beginning of current array. if current array is empty the result will be an empty array.

```php
$initialValue = [1 => 'b', 3 => 'd', 'a' => 0, 'b' => 1, 'd' => 3];
$firstElement = StdArray::of($initialValue)
    ->getFirst();

echo $firstElement->key() . ' = ' . $firstElement->value();
// 1 = b
```

### `getFirstAndRemove` - Pop an element off the beginning of array

The `getFirstAndRemove` shift element off from beginning of array by removing the element from array and return new `StdArray` object containing the element.&#x20;

{% hint style="info" %}
This method will reset the array pointer of the input array to the beginning.
{% endhint %}

```php
$stdArray = StdArray::of([1 => 'b', 3 => 'd', 'a' => 0, 'b' => 1, 'd' => 3]);
$firstElement = $stdArray
    ->getFirstAndRemove();

echo $firstElement->key() . ' = ' . $firstElement->value();
// 1 = b

$stdArray->reset();
echo $stdArray->key() . ' = ' . $stdArray->value();
// 3 = d
```

### `getLast` - Get last array element

The `getLast` method return last array element of array which the result is another `StdArray` object with the single element from end of current array. if current array is empty the result will be an empty array.

```php
$initialValue = [1 => 'b', 3 => 'd', 'a' => 0, 'b' => 1, 'd' => 3];
$lastElement = StdArray::of($initialValue)
    ->getLast();

echo $lastElement->key() . ' = ' . $lastElement->value();
// d = 3
```

### `getLastAndRemove` - Pop an element off the end of array

The `getLastAndRemove` pop element off from end of array by removing the element from array and return new `StdArray` object containing the element.&#x20;

{% hint style="info" %}
This method will reset the array pointer of the input array to the beginning.
{% endhint %}

```php
$stdArray = StdArray::of([1 => 'b', 3 => 'd', 'a' => 0, 'b' => 1, 'd' => 3]);
$lastElement = $stdArray
    ->getLastAndRemove();

echo $lastElement->key() . ' = ' . $lastElement->value();
// d = 3

echo $stdArray->key() . ' = ' . $stdArray->value();
// 1 = b
```

### `getKeys` - List Array keys

The `getKeys` method returns an instance of `StdArray` with values contains all the keys from current array.

```php
$stdArray = StdArray::of([1 => 'b', 3 => 'd', 'a' => 0, 'b' => 1, 'd' => 3]);
$arrayKeys = $stdArray->getKeys();
// [1, 3, 'a', 'b', 'd']
```

### `getValues` - List Array values

The `getValues` method returns an instance of `StdArray` contains all the values from current array indexed from zero.

```php
$stdArray = StdArray::of([1 => 'b', 3 => 'd', 'a' => 0, 'b' => 1, 'd' => 3]);
$arrayValues = $stdArray->getValues();
// ['b', 'd', 0, 1, 3]
```

### `getColumn` - representing a single column from array

The `getColumn` method retrieves all of the values for a given key(column), it possible to specify how the resulting values from column to be keyed by defining another key as second argument.

```php
$rows = [
  ['id' => '3', 'title' => 'Foo', 'date' => '2013-03-25'],
  ['id' => '5', 'title' => 'Bar', 'date' => '2012-05-20'],
];

$stdArray = StdArray::of($rows)->getColumn('title');
// ['Foo', 'Bar']

$stdArray = StdArray::of($rows)->getColumn('title', 'id');
// [3 => 'Foo', 5 => 'Bar']

$stdArray = StdArray::of($rows)->getColumn(null, 'id');
// [
//   3 => ['id' => '3', 'title' => 'Foo', 'date' => '2013-03-25'],
//   5 => ['id' => '5', 'title' => 'Bar', 'date' => '2012-05-20'],
// ]
```

### `getFlatten` - Get flatten face of multi-dimensional array

The `getFlatten` method flattens a multi-dimensional array into a single level array that uses given notation to indicate depth and return new `StdArray` object containing the element.&#x20;

```php
$nestedArray = [
    'key1' => 'value1',
    'key2' => [
        'subkey' => 'subkeyval'
    ],
    'key3' => 'value3',
    'key4' => [
        'subkey4' => [
            'subsubkey4' => 'subsubkeyval4',
            'subsubkey5' => 'subsubkeyval5',
        ],
        'subkey5' => 'subkeyval5'
    ]
];

$flattenArr = StdArray::of($nestedArray)
    ->getFlatten('.');
    
/*
array (
  'key1' => 'value1',
  'key2.subkey' => 'subkeyval',
  'key3' => 'value3',
  'key4.subkey4.subsubkey4' => 'subsubkeyval4',
  'key4.subkey4.subsubkey5' => 'subsubkeyval5',
  'key4.subkey5' => 'subkeyval5',
)
*/
```

```php
$flattenArr = StdArray::of($nestedArray)
    ->getFlatten('[%s]');

/*
array (
  'key1' => 'value1',
  'key2[subkey]' => 'subkeyval',
  'key3' => 'value3',
  'key4[subkey4][subsubkey4]' => 'subsubkeyval4',
  'key4[subkey4][subsubkey5]' => 'subsubkeyval5',
  'key4[subkey5]' => 'subkeyval5',
)
*/
```

### `reindex` - ReIndex array keys&#x20;

The `reindex` method will index array values from zero based on the current position of the item, if the key is an integer then take none-integer keys sort keys and concat them to the end of array as final result. &#x20;

```php
$stdArray = StdArray::of([1 => 'b', 3 => 'd', 'a' => 0, 'd' => 3, 'b' => 1]);
$arrayKeys = $stdArray->reindex();
// [0 => b, 1 => d, 'a' => 0, 'b' => 1, 'd' => 3]
```

### `clean` - Clean elements with empty values

The `clean` method Clean all elements with [falsy](https://www.php.net/manual/en/language.types.boolean.php#language.types.boolean.casting) values from the current array.

```php
$stdArray = StdArray::of([null, false, true, -9, 
  'foo' => false, 'foo', 'lall' => [], null => '^']);
  
$stdArray->clean();
// [2 => 1, 3 => -9, 4 => foo, null => ^]
```

### `isEqual` - Check whether origin array is equal to given value

The `isEqual` method check equality of current array with the given value can be any type which can constructed by `StdArray`

```php
$stdArray = new StdArray([1, 2, 3, 4])
    ->isEqualTo([1, 2, 3, 4]);
    
// true
```

### `isEmpty` - Check if array is empty

The `isEmpty` method returns `true` if the collection is empty; otherwise, `false` is returned.

```php
StdArray::of([])->isEmpty();
// true
```

### `isAssoc` - Check if array is Associative array

```php
$initialValue = ['field' => 'value', 'otherField' => 'other-value'];
$stdArray = new StdArray($initialValue);

$stdArray->isAssoc();
// true
```

## Enums

An enumeration type, "enum" for short, is a data type to categorise named values. Enums can be used instead of hard coded strings to represent, for example, the status of a blog post in a structured and typed way.\
\
For example, a Post object supposed to have three different values for status of the post, it could be one of three strings: `draft`, `published` or `archived`.

```php
class Post
{
    function setStatus(PostStatus $status): void
    {
        $this->status = $status(); // Invoke StdEnum to get value
    }
}
```

With `StdEnum` the enumeration type can be defined as follow:

```php
class PostStatus extends StdEnum
{
    const Draft = 'draft';
    const Published = 'published';
    const Archived = 'archived';
}
```

And Named values can be accessed by calling as static methods:

```php
$post->setStatus(PostStatus::Draft());
```

### Declaration

To define new enumeration type it's needed to just extend the `StdEnum` and define needed named values as public `constant` inside the class.

The `StdEnum` will automatically recognize the possible values from defined constants and will proxy all static method calls to related constant name to build new instance with value equal to const.

To get IDE support on autocompletion on the class the Docblock notation can be defined but it's not necessary but highly recommended.&#x20;

```php
/**
 * @method static EnumFixture Draft();
 * @method static EnumFixture Published();
 * @method static EnumFixture Archived();
 */
class PostStatus extends StdEnum
{
    const Draft = 'draft';
    const Published = 'published';
    const Archived = 'archived';
    
    // 'draft' as a default value
    function __construct($initial_value = self::Draft)
    {
        parent::__construct($initial_value);
    }
}
```

### Constructing object

Instantiating object is possible by `new` syntax and by calling statically the value name like `::NamedConst`.

```php
$postStatus = PostStatus::Archived();
// which is equal to
$postStatus = new PostStatus(PostStatus::Archived);
```

When the named value is not valid to class the `\UnexpectedValueException` will thrown.

```php
new PostStatus('invalid');
// Exception: \UnexpectedValueException

PostStatus::UNDEFINED();
// Exception: \UnexpectedValueException
```

### Get Value of enum object

The `StdEnum` is [invokable](https://www.php.net/manual/en/language.oop5.magic.php#object.invoke) and by calling the object like functions will return the value which is holding.

```php
$postStatus = PostStatus::Draft();
if ($postStatus() === PostStatus::Draft) {
   echo 'Post is on Draft.';
}

// Post is on Draft.
```

An example on How it's used in classes which are needed these values can be [found here](#enums).&#x20;

### Equality check

The `isEqualTo` method will tests whether enum instances are equal (returns `true` if enum values are equal, `false` otherwise).

```php
$archiveStatus = PostStatus::Archived();

// true
$archivedStatus->isEqualTo(new PostStatus(PostStatus::Archived));
$archivedStatus->isEqualTo(PostStatus::Archived());

// false
$archivedStatus->isEqualTo(PostStatus::Draft());
```

#### Check on classes which extends from base enum class&#x20;

The equality check on different object can be `true` only for the classes which extended from base enum class with same value. otherwise result check for different objects with the same value is always `false`.

```php
class ExtendedPostStatus extends PostStatus
{
    const Banned = 'banned';
}


$archiveStatus = ExtendedPostStatus::Archived();
$archivedStatus->isEqualTo(PostStatus::Archived());
// true
```

### List all possible enums

Return an array of enum name to the Enum object initialized with the internal value of related constant.

```php
$enums = PostStatus::listEnums();

/*
[
   'Draft' => (PostStatus) 'draft',
   // ..
]
*/
```

## Strings

The `StdString` is easy to use and easy to bundle library include convenience methods to handle strings with different character set encoding support. This library will also auto-detect server environment and will use the available installed php-extensions and pick them as a fallback to do the task.

{% hint style="info" %}
The `StdString` is immutable object which means any modification method to the current object will apply changes on the new instance of the object and the current one will stay same without any changes.
{% endhint %}

It's a wrapper around existence several extensions or libraries like `mbstring`, `iconv`, `intl`, ... in addition any custom user implementation can be registered as a fallback to the library through the available interfaces.

If Symfony Polyfills for `mbstring` is available (required in composer) or classes are loaded or autoloadable by PHP it might be selected as a fallback by `StdString` if needed.(<https://github.com/symfony/polyfill>)

### Register custom wrapper

Any custom implementation string wrapper can be registered to `StdString` by implementing `iStringWrapper` interface and then simply can be registered.

Registered wrapper will take the higher priority when `StdString` taking a wrapper from registry basically depends on the implemented methods it would take the job.

```php
StdString::registerWrapper(CustomTestStringWrapper::class);

// Do the string manuplation job ....

StdString::unregisterWrapper(CustomTestStringWrapper::class);
```

### Usage sample

```php
echo StdString::of('iñtërnâtiônàlizætiøn')->upperFirstChar();
// Iñtërnâtiônàlizætiøn

echo (string) StdString::of('mĄkA')->lower();
// mąka

echo StdString::of('/foo/bar/baz')
    ->stripPrefix('/')
    ->upperFirstChar();
// Foo/bar/baz

$str = StdString::of('¬fòô bàř¬');
if ($str->isStartedWith('¬fòô ')) {
    echo 'String has started with the expected prefix.';
}
```

### Instantiate and Construct

When string object constructed the object should know about the encoding charset of string, the default charset is `UTF-8`, depend on the string representing content any other encoding can be chosen.

The `Encodings` class contains all available charsets which can be used to get more consistency when using different charset on code base.

```php
StdString\Encodings::Charset['UTF-32'];
StdString\Encodings::CharsetSingleByte['ISO-8859-1'];
```

Values given as initial value to the class will casting to string type if it's not string. On Any object which can't cast to string `\UnexpectedValueException` will thrown.

```php
$stdString = new StdString('Hello!');
$stdString = new StdString($toStrigableObject);

$stdString = new StdString(3.14, StdString::CharsetDefault);
// '3.14'

$stdString = new StdString(-1234, StdString::CharsetDefault);
// '-1234'

$stdString = new StdString(true, StdString::CharsetDefault);
// 'true'
```

### It's serializable

The serialized string is a 3 byte sequence in the beginning of the string which indicate the encoding charset of the string and the rest is the origin string byte sequences means the whole original string.\
\
For instance the serialized string `中文空白` with encoding `UTF-16` represented like this:\
`\x00\x25 中文空白` the string part is also should be considered as byte sequences but here just to make it more understandable the string is used.

{% hint style="info" %}
Serialized string is safe to store in DB and other storages and can be searchable and can be converted and used by`StdString` object  with same encoding again.
{% endhint %}

```php
$stdStr    = StdString::of('中文空白', StdString\Encodings::Charset['UTF-32']);
$newStdStr = StdString::ofSerializedByteOrder(
    $stdStr->asSerializedByteOrder()
);

echo $newStdStr->encoding;
// UTF-32
```

### It's array like accessible

Characters within strings may be only accessed by specifying the zero-based offset of the desired character after the string using square array brackets, as in `$str[0]`.

{% hint style="info" %}
Instead of default PHP behaviour of accessing elements which is consider the string as series of 8bit data for each element, here each element represent a character in string. in multibyte encodings one character may consist more than 8bit.
{% endhint %}

```php
$str = '中文空白';
$stdString = StdString::of($str);

echo $stdString[1];
// 文

var_dump(isset($str[6]));       // true
var_dump(isset($stdString[4])); // false
```

### It's countable

```php
$stdString = StdString::of('中文空白');
$stdString->length();
// or
count($stdString);
// 4
```

### Get string encoding

To know which charset encoding object is instantiated with there are two readonly property which hold the same value.

```php
$stdStr = new StdString('', StdString\Encodings::CharsetSingleByte['ASCII']);
$stdStr->charset;
$stdStr->encoding;
// ASCII
```

### `lower`

Convert all alphabetic characters within string to lowercase.

```php
echo StdString::of('foO Bar BAZ')->lower();
// foo bar baz

echo StdString::of('mĄkA')->lower();
// mąka
```

### `lowerFirstChar`&#x20;

Makes string's first char lowercase, if that character is alphabetic.&#x20;

```php
echo StdString::of('Foo bar')->lowerFirstChar();
// foo bar

echo StdString::of('Öäü')->lowerFirstChar();
// 'öäü'
```

### `upper`

Convert all alphabetic characters within string to uppercase.

```php
echo StdString::of('foO Bar BAZ')->upper();
// FOO BAR BAZ

echo StdString::of('Umlaute äöü')->upper();
// UMLAUTE ÄÖÜ
```

### `upperFirstChar`

Makes string's first char uppercase, if that character is alphabetic.&#x20;

```php
echo StdString::of('foo bar')->upperFirstChar();
// Foo bar

echo StdString::of('öäü')->upperFirstChar();
// Öäü
```

### `camelCase`

Note that whitespaces will be removed from the string. only `_` character will be considered as a separator char and will be used in creating camelCase result of string other like numbers will be considered as part of words.

```php
echo StdString::of('camelCase')->camelCase();
// camelCase

echo StdString::of('camel_case')->camelCase();
// camelCase

echo StdString::of('  camel  case')->camelCase();
// camelCase

echo StdString::of('Camel2case')->camelCase();
// camel2case

echo StdString::of('Camel2_case')->camelCase();
// camel2Case

echo StdString::of('ΣamelCase')->camelCase();
// σamelCase
```

The delimiter can given to the method and that will be remain if string at the beginning or end has it.

```php
echo StdString::of('__Camel_case__')->camelCase('_');
// __cascalCase__
```

### `pascalCase`

```php
echo StdString::of('pascalCase')->pascalCase();
// PascalCase

echo StdString::of('pascal__case')->pascalCase();
// PascalCase

echo StdString::of('  pascal  case')->pascalCase();
// PascalCase

echo StdString::of('pascal2case')->pascalCase();
// Pascal2case

echo StdString::of('pascal2_case')->pascalCase();
// Pascal2Case

echo StdString::of('σascalCase')->pascalCase();
// ΣascalCase
```

The delimiter can given to the method and that will be remain if string at the beginning or end has it.

```php
echo StdString::of('__σascal_case__')->pascalCase('_');
// __ΣascalCase__
```

### `snakeCase`

```php
echo StdString::of('SnakeCase')->snakeCase();
// snake_case

echo StdString::of('Snake#Case')->snakeCase();
// snake#_case

echo StdString::of('snake  case')->snakeCase();
// snake_case

echo StdString::of('snake2Case')->snakeCase();
// snake2_case

echo StdString::of('ΣτανιλCase')->snakeCase();
// στανιλ_case
```

The delimiter can given to the method and that will be remain if string at the beginning or end has it.

```php
echo StdString::of('__snakeCase__')->snakeCase('_');
// __snake_case__
```

### `subString` - Return part of a string

Returns the portion of string specified by the start and length parameters. to see about all possible values to the parameters check [here](https://www.php.net/manual/en/function.substr.php).

```php
$stdString = StdString::of('abcde');
echo $stdString->subString(1, 2);  // bc
echo $stdString->subString(-2);    // de
echo $stdString->subString(-3, 2); // cd

$stdString = StdString::of('中文空白');
echo $stdString->subString(1, 2);  // 文空
echo $stdString->subString(-2);    // 空白
```

### `stripPrefix` - remove string from beginning

Remove given string if only it's found at the beginning of the original string.

```php
$stdString = StdString::of('/foo/bar/baz');
echo $stdString->stripPrefix('/');      // foo/bar/baz
echo $stdString->stripPrefix('/foo/');  // bar/baz

$stdString = StdString::of('آهایی دنیا سلام');
echo $stdString->stripPrefix('آهایی');  
// دنیا سلام
```

Second argument to the method will strip any whitespace from beginning of array before strip given string when it has `true` value. which is default value.

```php
echo StdString::of('  /foo/bar/')->stripPrefix();
// '/foo/bar/'
```

### `stripPostfix` - remove string from end

Remove given string if only it's found at the end of the original string.

```php
$stdString = StdString::of('foo/bar/baz/');
echo $stdString->stripPostfix('/');      // foo/bar/baz
echo $stdString->stripPostfix('/baz/');  // foo/bar

$stdString = StdString::of('آهایی دنیا سلام');
echo $stdString->stripPostfix('سلام');  
// آهایی دنیا
```

Second argument to the method will strip any whitespace from end of array before strip given string when it has `true` value. which is default value.

```php
echo StdString::of('/foo/bar/  ')->stripPostfix();
// '/foo/bar/'
```

### `hasStartedWith` - Get prefixed string

Get the given string as a result if the origin string is started with that otherwise return empty string.\
Method will accept [variadic argument](https://www.php.net/manual/en/functions.arguments.php#functions.variable-arg-list) of string values and will return the one which match first with the origin string prefix.

```php
$stdStr = StdString::of('foobarbaz');
$prefix = $str->hasStartedWith('bo', 'fo');

echo $prefix->__toString(); // fo 
```

### `isStartedWith` - Check prefix of string&#x20;

Method will accept [variadic argument](https://www.php.net/manual/en/functions.arguments.php#functions.variable-arg-list) of string values and will check if the string has one of the given values in the beginning.

```php
$stdStr = StdString::of('foobarbaz');
$str->hasStartedWith('fo');
// true
```

### `hasContains` - Has string contains a substring

Determine existence of a substring in origin string, if it has the substring will be returned as the result otherwise empty string will be returned.

```php
$stdString = new StdString('щука 漢字');
echo $stdString->hasContains('щука');
// щука
```

### `isContains` - Check string contains a substring

Check if string contains given substring and return boolean value `true` if has substring otherwise return `false`.

```php
$stdString = new StdString('щука 漢字');
$stdString->isContains('щука');
// true
```

### `concatSafeImplode` - Safe join strings

Join string elements from array with a delimiter glue, joining will be safe to not include extra glue character if concat string has already contain the delimiter glue.

```php
$stdInitial = StdString::of('/a/');
$stdString  = $stdInitial->concatSafeImplode(['b/', 'c', 'd/'], '/');

echo $stdString;
// /a/b/c/d/
```

### `normalizeExtraNewLines` - Remove extra line breaks

Remove extra new lines and line breaks from string.

```php
$stdString = new StdString("aaжи\r\n\r\n\r\n\r\nвπά\n\n\n\n\n우리をあöä\r\n");
echo $stdString->normalizeExtraNewLines();
// aaжи\r\n\r\nвπά\n\n우리をあöä\r\n
```

### `normalizeWhitespaces` - Strip and remove whitespaces

```php
$stdString = new StdString("\tΟ \r\n   συγγραφέας  ");
echo $stdString->normalizeWhitespaces();
// Ο συγγραφέας
```
