Skip to content

Commit

Permalink
[WIP] Typed data
Browse files Browse the repository at this point in the history
  • Loading branch information
gapple committed Jan 11, 2022
1 parent 348fc57 commit 78b38fe
Show file tree
Hide file tree
Showing 13 changed files with 510 additions and 37 deletions.
24 changes: 24 additions & 0 deletions src/InnerList.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace gapple\StructuredFields;

class InnerList implements TupleInterface, \IteratorAggregate
{
use TupleTrait;

public function __construct($value, ?object $parameters = null)
{
$this->value = $value;

if (is_null($parameters)) {
$this->parameters = new \stdClass();
} else {
$this->parameters = $parameters;
}
}

public function getIterator()
{
return new \ArrayIterator($this->value);
}
}
19 changes: 19 additions & 0 deletions src/Item.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace gapple\StructuredFields;

class Item implements TupleInterface
{
use TupleTrait;

public function __construct($value, ?object $parameters = null)
{
$this->value = $value;

if (is_null($parameters)) {
$this->parameters = new \stdClass();
} else {
$this->parameters = $parameters;
}
}
}
66 changes: 66 additions & 0 deletions src/OuterList.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

namespace gapple\StructuredFields;

class OuterList implements \IteratorAggregate, \ArrayAccess
{
/**
* The array of values.
*
* @var array
*/
public $value;

public function __construct($value = [])
{
array_walk($value, [$this, 'validateItemType']);

$this->value = $value;
}

private static function validateItemType($value): void
{
if (is_object($value)) {
if (!($value instanceof TupleInterface)) {
throw new \InvalidArgumentException();
}
} elseif (is_array($value)) {
if (count($value) != 2) {
throw new \InvalidArgumentException();
}
} else {
throw new \InvalidArgumentException();
}
}

public function getIterator()
{
return new \ArrayIterator($this->value);
}

public function offsetExists($offset)
{
return isset($this->value[$offset]);
}

public function offsetGet($offset)
{
return $this->value[$offset] ?? null;
}

public function offsetSet($offset, $value)
{
static::validateItemType($value);

if (is_null($offset)) {
$this->value[] = $value;
} else {
$this->value[$offset] = $value;
}
}

public function offsetUnset($offset)
{
unset($this->value[$offset]);
}
}
28 changes: 14 additions & 14 deletions src/Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public static function parseDictionary(string $string): \stdClass
$value->{$key} = self::parseItemOrInnerList($string);
} else {
// Bare boolean true value.
$value->{$key} = [true, self::parseParameters($string)];
$value->{$key} = new Item(true, self::parseParameters($string));
}

// OWS (optional whitespace) before comma.
Expand All @@ -44,9 +44,9 @@ public static function parseDictionary(string $string): \stdClass
return $value;
}

public static function parseList(string $string): array
public static function parseList(string $string): OuterList
{
$value = [];
$value = new OuterList();

$string = ltrim($string, ' ');

Expand Down Expand Up @@ -76,7 +76,7 @@ public static function parseList(string $string): array
return $value;
}

private static function parseItemOrInnerList(string &$string): array
private static function parseItemOrInnerList(string &$string): TupleInterface
{
if ($string[0] === '(') {
return self::parseInnerList($string);
Expand All @@ -85,7 +85,7 @@ private static function parseItemOrInnerList(string &$string): array
}
}

private static function parseInnerList(string &$string): array
private static function parseInnerList(string &$string): InnerList
{
$value = [];

Expand All @@ -96,10 +96,10 @@ private static function parseInnerList(string &$string): array

if ($string[0] === ')') {
$string = substr($string, 1);
return [
return new InnerList(
$value,
self::parseParameters($string),
];
self::parseParameters($string)
);
}

$value[] = self::doParseItem($string);
Expand All @@ -115,10 +115,10 @@ private static function parseInnerList(string &$string): array
/**
* @param string $string
*
* @return array
* @return \gapple\StructuredFields\Item
* A [value, parameters] tuple.
*/
public static function parseItem(string $string): array
public static function parseItem(string $string): Item
{
$string = ltrim($string, ' ');

Expand All @@ -137,15 +137,15 @@ public static function parseItem(string $string): array
*
* @param string $string
*
* @return array
* @return \gapple\StructuredFields\Item
* A [value, parameters] tuple.
*/
private static function doParseItem(string &$string): array
private static function doParseItem(string &$string): Item
{
return [
return new Item(
self::parseBareItem($string),
self::parseParameters($string)
];
);
}

/**
Expand Down
32 changes: 30 additions & 2 deletions src/Serializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,33 @@

class Serializer
{
/**
* Serialize and item with optional parameters.
*
* @param $value
* A bare value, or an Item object.
* @param object|null $parameters
* An optional object containing parameter values if a bare value is provided.
*
* @return string
* The serialized value.
*/
public static function serializeItem($value, ?object $parameters = null): string
{
$output = self::serializeBareItem($value);
if ($value instanceof Item) {
if (!is_null($parameters)) {
throw new \InvalidArgumentException(
'Parameters argument is not allowed when serializing an Item object'
);
}

$bareValue = $value->value;
$parameters = $value->parameters;
} else {
$bareValue = $value;
}

$output = self::serializeBareItem($bareValue);

if (!empty($parameters)) {
$output .= self::serializeParameters($parameters);
Expand All @@ -15,8 +39,12 @@ public static function serializeItem($value, ?object $parameters = null): string
return $output;
}

public static function serializeList(array $value): string
public static function serializeList($value): string
{
if ($value instanceof OuterList) {
$value = iterator_to_array($value->getIterator());
}

$returnValue = array_map(function ($item) {
if (is_array($item[0])) {
return self::serializeInnerList($item[0], $item[1]);
Expand Down
7 changes: 7 additions & 0 deletions src/TupleInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace gapple\StructuredFields;

interface TupleInterface extends \ArrayAccess
{
}
53 changes: 53 additions & 0 deletions src/TupleTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

namespace gapple\StructuredFields;

trait TupleTrait
{
/**
* The tuple's value.
*
* @var mixed
*/
public $value;

/**
* The tuple's parameters
*
* @var object
*/
public $parameters;

public function offsetExists($offset): bool
{
return $offset == 0 || $offset == 1;
}

public function offsetGet($offset)
{
if ($offset == 0) {
return $this->value;
} elseif ($offset == 1) {
return $this->parameters;
}
return null;
}

public function offsetSet($offset, $value)
{
if ($offset == 0) {
$this->value = $value;
} elseif ($offset == 1) {
$this->parameters = $value;
}
}

public function offsetUnset($offset)
{
if ($offset == 0) {
$this->value = null;
} elseif ($offset == 1) {
$this->parameters = new \stdClass();
}
}
}
51 changes: 51 additions & 0 deletions tests/InnerListTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

namespace gapple\Tests\StructuredFields;

use gapple\StructuredFields\InnerList;
use PHPUnit\Framework\TestCase;

class InnerListTest extends TestCase
{
public function testDefaultParameters()
{
$item = new InnerList(true);

$this->assertInstanceOf(\stdClass::class, $item->parameters);
$this->assertEmpty(get_object_vars($item->parameters));
}

public function testArrayAccess()
{
$item = new InnerList(
[
'Test Value One',
'Test Value Two',
],
(object) ['paramKey' => 'param value']
);

$this->assertEquals('Test Value One', $item->value[0]);
$this->assertEquals('Test Value One', $item[0][0]);
$this->assertEquals('param value', $item->parameters->paramKey);
$this->assertEquals('param value', $item[1]->paramKey);
}

public function testIteration()
{
$list = new InnerList(
[
'Test Value One',
'Test Value Two',
],
(object) ['paramKey' => 'param value']
);

$this->assertIsIterable($list);

$this->assertEquals(
['Test Value One', 'Test Value Two'],
iterator_to_array($list)
);
}
}
Loading

0 comments on commit 78b38fe

Please sign in to comment.