Skip to content

Commit

Permalink
Separate Array and Object types
Browse files Browse the repository at this point in the history
  • Loading branch information
DrechslerDerek committed Jul 24, 2024
1 parent 9520b24 commit 20d873c
Show file tree
Hide file tree
Showing 13 changed files with 782 additions and 353 deletions.
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ composer require focus/data

## Usage

The most basic usage is `KeyedData`, which wraps arrays and objects:
The most basic usage is `KeyedDataObject`, which wraps objects and `KeyedDataArray`, which warps arrays:

```php
use Focus\Data\KeyedData;
use Focus\Data\KeyedData\KeyedDataFactory;

$value = [
'user' => [
Expand All @@ -35,7 +35,8 @@ $value = [
],
];

$data = KeyedData::from($value);
// Will create either KeyedDataObject or KeyedDataArray, depending on the value
$data = KeyedDataFactory::from($value);
```

Once you have an instance of data, you can access the values by using dot paths:
Expand Down Expand Up @@ -90,7 +91,9 @@ the value of `getParsedBody()` will be used by default. To disable this behavior
```php
$data = JsonData::fromRequest($request, useParsedBody: false);
```

When using `getParsedBody()` remember that most `ServerRequestInterface` objects will decode the request body
with `associative: true`, producing an array. If you wish to have `JsonData->value` be an object, instead of an array,
configure your `ServerRequestInterface` to decode request bodies with `associative: false` (default for `json_decode()`)
## FAQ

These are some of the most common questions about usage and design of this package.
Expand Down
591 changes: 307 additions & 284 deletions composer.lock

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions examples/basic-use.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

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

use Focus\Data\KeyedData;
use Focus\Data\KeyedData\KeyedDataFactory;

$value = [
'user' => [
Expand All @@ -19,7 +19,9 @@
],
];

$data = KeyedData::from($value);
// If the value is an object, then a KeyedDataObject will be returned.
// If the value is an array or ArrayObject, then a KeyedDataArray will be returned.
$data = KeyedDataFactory::from($value);

// get() returns values using dot paths
$name = $data->get(path: 'user.name');
Expand Down
6 changes: 3 additions & 3 deletions examples/data-proxy.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
use Focus\Data\Behavior\DataProxyBehavior;
use Focus\Data\Data;
use Focus\Data\DataProxy;
use Focus\Data\KeyedData;
use Focus\Data\KeyedData\KeyedDataFactory;

// phpcs:disable
final class MyData extends DataProxy
final readonly class MyData extends DataProxy
{
use DataProxyBehavior;

Expand Down Expand Up @@ -46,7 +46,7 @@ private function swapUsernameForEmail(string $path): string

// While this looks like normal OOP abstraction, the key difference here is that
// the underlying Data object can be any implementation.
$data = new MyData(KeyedData::from($value));
$data = new MyData(KeyedDataFactory::from($value));

assert(assertion: $data instanceof Data);

Expand Down
4 changes: 2 additions & 2 deletions src/Behavior/DataProxyBehavior.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
namespace Focus\Data\Behavior;

use Focus\Data\Data;
use Focus\Data\KeyedData;
use Focus\Data\KeyedData\KeyedDataObject;

/**
* Provides simple source() method for a DataProxy extension
Expand All @@ -15,7 +15,7 @@
trait DataProxyBehavior
{
public function __construct(
private readonly Data $data = new KeyedData(),
private readonly Data $data = new KeyedDataObject(),
) {
}

Expand Down
17 changes: 10 additions & 7 deletions src/JsonData.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Focus\Data;

use Focus\Data\Behavior\DataProxyBehavior;
use Focus\Data\KeyedData\KeyedDataFactory;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
Expand All @@ -17,26 +18,28 @@
{
use DataProxyBehavior;

public static function fromString(string $json): self
public static function fromString(string $json, bool $associative = false): self
{
$data = KeyedData::tryFrom(json_decode($json, flags: JSON_THROW_ON_ERROR));
$data = json_decode(json: $json, associative: $associative, flags: JSON_THROW_ON_ERROR);

$data = KeyedDataFactory::from($data);

return new self($data);
}

public static function fromRequest(RequestInterface $request, bool $useParsedBody = true): self
public static function fromRequest(RequestInterface $request, bool $useParsedBody = true, bool $associative = true): self
{
if ($request instanceof ServerRequestInterface && $useParsedBody) {
$data = KeyedData::tryFrom($request->getParsedBody());
$data = KeyedDataFactory::from($request->getParsedBody());

return new self($data);
}

return self::fromString((string) $request->getBody());
return self::fromString((string) $request->getBody(), $associative);
}

public static function fromResponse(ResponseInterface $response): self
public static function fromResponse(ResponseInterface $response, bool $associative = false): self
{
return self::fromString((string) $response->getBody());
return self::fromString((string) $response->getBody(), $associative);
}
}
35 changes: 8 additions & 27 deletions src/KeyedData.php → src/KeyedData/KeyedDataArray.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,54 +2,43 @@

declare(strict_types=1);

namespace Focus\Data;
namespace Focus\Data\KeyedData;

use ArrayAccess;
use InvalidArgumentException;
use Focus\Data\Data;
use JmesPath\Env as JmesPath;
use RuntimeException;
use stdClass;

use function array_key_exists;
use function explode;
use function gettype;
use function is_array;
use function is_object;
use function preg_match;
use function property_exists;
use function trim;
use function vsprintf;

final readonly class KeyedData implements Data
final readonly class KeyedDataArray implements Data
{
public static function from(array|object $value): self
public static function from(array|ArrayAccess $value): self
{
return new self($value);
}

public static function tryFrom(mixed $value): self
public static function tryFrom(mixed $value): KeyedDataArray
{
if ($value === null) {
return new self();
}

if (is_array($value) || is_object($value)) {
return self::from($value);
}

throw new InvalidArgumentException(
message: vsprintf(format: 'Cannot create KeyedData from %s', values: [
gettype($value),
]),
);
return KeyedDataFactory::from($value);
}

public function __construct(
private array|object $value = new stdClass(),
private array|ArrayAccess $value = [],
) {
}

public function jsonSerialize(): array|object
public function jsonSerialize(): array|ArrayAccess
{
return $this->value;
}
Expand All @@ -71,12 +60,6 @@ public function has(string $path): bool
}

$data = $data[$key];
} elseif (is_object($data)) {
if (! property_exists($data, $key)) {
return false;
}

$data = $data->$key;
} else {
return false;
}
Expand All @@ -92,8 +75,6 @@ public function get(string $path): mixed
foreach ($this->expand($path) as $key) {
if (is_array($data) || $data instanceof ArrayAccess) {
$data = $data[$key] ?? null;
} elseif (is_object($data)) {
$data = $data->$key ?? null;
} else {
throw new RuntimeException(
message: vsprintf(format: 'Cannot follow path %s into type %s', values: [
Expand Down
33 changes: 33 additions & 0 deletions src/KeyedData/KeyedDataFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace Focus\Data\KeyedData;

use ArrayAccess;
use InvalidArgumentException;

use function gettype;
use function is_array;
use function is_object;
use function vsprintf;

final readonly class KeyedDataFactory
{
public static function from(mixed $value): KeyedDataArray|KeyedDataObject
{
if (is_array($value) || $value instanceof ArrayAccess) {
return new KeyedDataArray($value);
}

if (is_object($value)) {
return new KeyedDataObject($value);
}

throw new InvalidArgumentException(
message: vsprintf(format: 'Cannot create KeyedDataObject or KeyedDataArray from %s', values: [
gettype($value),
]),
);
}
}
99 changes: 99 additions & 0 deletions src/KeyedData/KeyedDataObject.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<?php

declare(strict_types=1);

namespace Focus\Data\KeyedData;

use Focus\Data\Data;
use JmesPath\Env as JmesPath;
use RuntimeException;
use stdClass;

use function explode;
use function gettype;
use function is_object;
use function preg_match;
use function property_exists;
use function trim;
use function vsprintf;

final readonly class KeyedDataObject implements Data
{
public static function from(object $value): self
{
return new self($value);
}

public static function tryFrom(mixed $value): KeyedDataObject
{
if ($value === null) {
return new self();
}

return KeyedDataFactory::from($value);
}

public function __construct(
private object $value = new stdClass(),
) {
}

public function jsonSerialize(): object
{
return $this->value;
}

public function has(string $path): bool
{
$data = $this->value;

foreach ($this->expand($path) as $key) {
if (is_object($data)) {
if (! property_exists($data, $key)) {
return false;
}

$data = $data->$key;
} else {
return false;
}
}

return true;
}

public function get(string $path): mixed
{
$data = $this->value;

foreach ($this->expand($path) as $key) {
if (is_object($data)) {
$data = $data->$key ?? null;
} else {
throw new RuntimeException(
message: vsprintf(format: 'Cannot follow path %s into type %s', values: [
$path,
gettype($data),
]),
);
}
}

return $data;
}

public function search(string $path): mixed
{
if (preg_match(pattern: '/^[a-zA-Z0-9_.-]+$/', subject: $path)) {
return $this->get($path);
}

return JmesPath::search($path, $this->value);
}

private function expand(string $path): array
{
// The path SHOULD be written as keys separated by periods.
return explode(separator: '.', string: trim($path, characters: '.'));
}
}
Loading

0 comments on commit 20d873c

Please sign in to comment.