Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions example/01-json-encode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php
use Gt\DataObject\DataObject;

require __DIR__ . "/../vendor/autoload.php";

$obj = (new DataObject())
->with("name", "Cody")
->with("colour", "orange")
->with("food", [
"biscuits",
"mushrooms",
"corn on the cob",
]);

echo json_encode($obj), PHP_EOL;
25 changes: 25 additions & 0 deletions example/02-json-decode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php
use Gt\DataObject\DataObjectBuilder;

require __DIR__ . "/../vendor/autoload.php";

$jsonString = <<<JSON
{
"name": "Cody",
"colour": "orange",
"food": [
"biscuits",
"mushrooms",
"corn on the cob"
]
}
JSON;

$builder = new DataObjectBuilder();
$obj = $builder->fromObject(json_decode($jsonString));

echo "Hello, ",
$obj->getString("name"),
"! Your favourite food is ",
$obj->getArray("food")[0],
PHP_EOL;
22 changes: 22 additions & 0 deletions example/03-type-casting.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

use Gt\DataObject\DataObject;

require __DIR__ . "/../vendor/autoload.php";

// Set an object with all string values, similar to a web requests:
$obj = new DataObject();
$obj = $obj->with("one", "1");
$obj = $obj->with("two", "two");
$obj = $obj->with("pi", "3.14159");

// Automatically cast to int:

$int1 = $obj->getInt("one");
$int2 = $obj->getInt("two");
$int3 = $obj->getInt("pi");

echo "One: $int1, two: $int2, three: $int3", PHP_EOL;
// Outputs: One: 1, two: 0, three: 3
// Note how the decimal data is lost in the cast to int,
// but how the original data is not lost.
20 changes: 20 additions & 0 deletions example/04-type-safety-arrays.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

use Gt\DataObject\DataObject;

require __DIR__ . "/../vendor/autoload.php";

$obj = new DataObject();
$obj = $obj->with("arrayOfData", [
1,
2,
3.14159,
]);

echo "The third element in the array is: ",
$obj->getArray("arrayOfData", "int")[2], // note the type check of "int"
PHP_EOL;

/* Output:
The third element in the array is: 3
*/
74 changes: 55 additions & 19 deletions src/DataObject.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public function getArray(string $name, ?string $type = null):?array {
}

if($array && $type) {
$this->checkArrayType($array, $type);
$array = $this->checkArrayType($array, $type);
}

return $array;
Expand Down Expand Up @@ -113,28 +113,64 @@ public function asObject():object {
return (object)$array;
}

/** @param mixed[] $array */
private function checkArrayType(array $array, string $type):void {
$errorMessage = "";
/**
* @param array<mixed> $array
* @return array<mixed>
*/
private function checkArrayType(array $array, string $type): array {
$this->validateTypeExists($type);

foreach($array as $i => $value) {
$actualType = is_scalar($value) ? gettype($value) : get_class($value);
foreach ($array as $i => $value) {
$array[$i] = $this->processValue($value, $type, $i);
}

if(class_exists($type) || interface_exists($type)) {
if(!is_a($value, $type)) {
$errorMessage = "Array index $i must be of type $type, $actualType given";
}
}
else {
$checkFunction = "is_$type";
if(!call_user_func($checkFunction, $value)) {
$errorMessage = "Array index $i must be of type $type, $actualType given";
}
}
return $array;
}

private function validateTypeExists(string $type): void {
if(!class_exists($type)
&& !interface_exists($type)
&& !function_exists("is_$type")) {
throw new TypeError("Invalid type: $type does not exist.");
}
}

if($errorMessage) {
throw new TypeError($errorMessage);
private function processValue(
mixed $value,
string $type,
int $index,
): mixed {
if (class_exists($type) || interface_exists($type)) {
$this->assertInstanceOfType($value, $type, $index);
} elseif (function_exists("is_$type")) {
return $this->castValue($value, $type);
}

return $value;
}

private function assertInstanceOfType(
mixed $value,
string $type,
int $index,
): void {
if (!is_a($value, $type)) {
$actualType = is_scalar($value)
? gettype($value)
: get_class($value);
throw new TypeError("Array index $index"
. " must be of type $type, $actualType given");
}
}

private function castValue(mixed $value, string $type): mixed {
return match ($type) {
"int" => (int)$value,
"bool" => (bool)$value,
"string" => (string)$value,
"float", "double" => (float)$value,
"array" => (array)$value,
default => null,
};
}
}
41 changes: 31 additions & 10 deletions test/phpunit/DataObjectTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@

use DateTime;
use DateTimeInterface;
use Error;
use Gt\DataObject\DataObject;
use PHPUnit\Framework\TestCase;
use stdClass;
use Throwable;
use TypeError;

class DataObjectTest extends TestCase {
Expand Down Expand Up @@ -105,6 +107,21 @@ public function testGetStringNull() {
self::assertNull($sut->getString("nothing"));
}

public function testGetStringFromDateTime() {
$sut = (new DataObject())
->with("dt", new DateTime());

$exception = null;

try {
$data = $sut->getString("dt");
}
catch(Throwable $exception) {}

self::assertInstanceOf(Error::class, $exception);
self::assertSame("Object of class DateTime could not be converted to string", $exception->getMessage());
}

public function testGetIntFromString() {
$sut = (new DataObject())
->with("one", "1")
Expand Down Expand Up @@ -374,15 +391,20 @@ public function testGetArrayFixedTypeInt_typeError() {
49997,
50000,
49999,
"PLOP!",
"50003",
50001,
];
$sut = (new DataObject())
->with("timestamps", $timestampArray);

self::expectException(TypeError::class);
self::expectExceptionMessage("Array index 3 must be of type int, string given");
$sut->getArray("timestamps", "int");
$array = $sut->getArray("timestamps", "int");
foreach($array as $i => $value) {
if($i === 3) {
self::assertSame(50003, $value);
}

self::assertIsInt($value);
}
}

public function testGetArrayFixedTypeFloat() {
Expand Down Expand Up @@ -433,7 +455,7 @@ public function testGetArrayFixedTypeString() {
}
}

public function testGetArrayFixedTypeString_typeErrorWithObject() {
public function testGetArrayFixedTypeString_typeErrorWithDateTime() {
$wordsArray = [
"one",
"two",
Expand All @@ -443,7 +465,7 @@ public function testGetArrayFixedTypeString_typeErrorWithObject() {
];
$sut = (new DataObject())
->with("words", $wordsArray);
self::expectExceptionMessage("Array index 3 must be of type string, DateTime given");
self::expectExceptionMessage("Object of class DateTime could not be converted to string");
$sut->getArray("words", "string");
}

Expand All @@ -456,7 +478,7 @@ public function testGetArrayFixedTypeDateTime() {
->with("dates", $dateArray);
$array = $sut->getArray("dates", DateTimeInterface::class);
foreach($array as $i => $value) {
self::assertInstanceOf(DateTimeInterface::class, $value);
self::assertInstanceOf(DateTime::class, $value);
}
}

Expand All @@ -470,9 +492,8 @@ public function testGetArrayFixedTypesMismatch() {
];
$sut = (new DataObject())
->with("timestamps", $timestampArray);
self::expectException(TypeError::class);
self::expectExceptionMessage("Array index 2 must be of type int, double given");
$sut->getArray("timestamps", "int");
$array = $sut->getArray("timestamps", "int");
self::assertSame(49999, $array[2]);
}

public function testGetArray_nullableType():void {
Expand Down