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
27 changes: 27 additions & 0 deletions components/ILIAS/Refinery/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ processed by the ILIAS project.
* [htmlAttributeValue](#htmlAttributeValue)
* [json](#json)
* [url](#url)
- [decode](#decode)
* [json](#json-1)
* [Custom Transformation](#custom-transformation)
+ [DeriveApplyToFromTransform](#deriveapplytofromtransform)
- [Error Handling](#error-handling)
Expand Down Expand Up @@ -329,6 +331,31 @@ The transformation prevents a value to change other URL parameters & values or t
$link = $ctrl->setParameterByClass(FooGUI::class, 'bar', $refinery->encode()->url()->transform($foobar));
```

##### decode

The `decode` group is the counterpart of the [encode](#encode) group and turns encoded strings back
into native PHP values.

###### json

This transformation is a wrapper around `json_decode`. In contrast to `json_decode` it never returns
`null` to signal a problem, because `null` cannot be told apart from the successfully decoded JSON
literal `null`. Undecodable input raises an `InvalidArgumentException` instead, as announced by the
`Transformation` interface, so `applyTo` can be used to reify the problem into a `Result`.

JSON objects are decoded into associative arrays rather than `stdClass`, so the result can be
processed further with the `container`, `to` and `kindlyTo` groups.

```php
$settings = $refinery->decode()->json()->transform('{"limit":10,"tags":["a","b"]}');
// $settings => ['limit' => 10, 'tags' => ['a', 'b']]

$refinery->decode()->json()->transform('{'); // throws an InvalidArgumentException

$result = $refinery->decode()->json()->applyTo(new ILIAS\Data\Result\Ok('{'));
// $result->isError() => true
```

##### Custom

The `Custom` group contains `Transformations` and `Constraints`
Expand Down
39 changes: 39 additions & 0 deletions components/ILIAS/Refinery/src/Decode/Group.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

/**
* This file is part of ILIAS, a powerful learning management system
* published by ILIAS open source e-Learning e.V.
*
* ILIAS is licensed with the GPL-3.0,
* see https://www.gnu.org/licenses/gpl-3.0.en.html
* You should have received a copy of said license along with the
* source code, too.
*
* If this is not the case or you just want to try ILIAS, you'll find
* us at:
* https://www.ilias.de
* https://github.com/ILIAS-eLearning
*
*********************************************************************/

declare(strict_types=1);

namespace ILIAS\Refinery\Decode;

use ILIAS\Refinery\Decode\Transformation\Json;
use ILIAS\Refinery\Transformation;

final class Group
{
/**
* Decodes a JSON string into native PHP values, JSON objects become associative arrays.
*
* @param int $max_depth Maximum nesting depth of the structure being decoded, counting the
* scalars at the very bottom as one level. Defaults to the depth PHP
* itself uses for `json_decode`.
*/
public function json(int $max_depth = Json::DEFAULT_MAX_DEPTH): Transformation
{
return new Json($max_depth);
}
}
86 changes: 86 additions & 0 deletions components/ILIAS/Refinery/src/Decode/Transformation/Json.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?php

/**
* This file is part of ILIAS, a powerful learning management system
* published by ILIAS open source e-Learning e.V.
*
* ILIAS is licensed with the GPL-3.0,
* see https://www.gnu.org/licenses/gpl-3.0.en.html
* You should have received a copy of said license along with the
* source code, too.
*
* If this is not the case or you just want to try ILIAS, you'll find
* us at:
* https://www.ilias.de
* https://github.com/ILIAS-eLearning
*
*********************************************************************/

declare(strict_types=1);

namespace ILIAS\Refinery\Decode\Transformation;

use ILIAS\Refinery\DeriveApplyToFromTransform;
use ILIAS\Refinery\DeriveInvokeFromTransform;
use ILIAS\Refinery\Transformation;
use InvalidArgumentException;
use JsonException;

/**
* This class is a wrapper around `json_decode` which rejects undecodable input with an
* `InvalidArgumentException` instead of returning `null`, which cannot be told apart from the
* successfully decoded JSON literal `null`.
*
* JSON objects are decoded into associative arrays instead of `stdClass`, so that results can be
* processed further with the `container`, `to` and `kindlyTo` groups and round-trip with the
* `encode` group.
*
* Please see https://www.php.net/manual/en/function.json-decode.php for more information.
*/
final class Json implements Transformation
{
use DeriveInvokeFromTransform;
use DeriveApplyToFromTransform;

/**
* PHP does not expose the default of its JSON parser (PHP_JSON_PARSER_DEFAULT_DEPTH) to userland.
*/
public const int DEFAULT_MAX_DEPTH = 512;
public const int MAX_DEPTH_LOWER_BOUND = 1;
public const int MAX_DEPTH_UPPER_BOUND = 2147483647;

public function __construct(private readonly int $max_depth = self::DEFAULT_MAX_DEPTH)
{
if ($max_depth < self::MAX_DEPTH_LOWER_BOUND || $max_depth > self::MAX_DEPTH_UPPER_BOUND) {
throw new InvalidArgumentException(
\sprintf(
'Maximum depth must be between %d and %d, got %d.',
self::MAX_DEPTH_LOWER_BOUND,
self::MAX_DEPTH_UPPER_BOUND,
$max_depth
)
);
}
}

public function transform($from): mixed
{
if (!\is_string($from)) {
throw new InvalidArgumentException(
\sprintf(
'The value of type "%s" is not a string and cannot be decoded as JSON.',
get_debug_type($from)
)
);
}

try {
return json_decode($from, true, $this->max_depth, JSON_THROW_ON_ERROR);
} catch (JsonException $exception) {
// The value itself is left out of the message, it may be large and carry sensitive data.
throw new InvalidArgumentException(
\sprintf('The value cannot be decoded as JSON: %s.', $exception->getMessage())
);
}
}
}
5 changes: 5 additions & 0 deletions components/ILIAS/Refinery/src/Factory.php
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,11 @@ public function encode(): Encode\Group
return new Encode\Group();
}

public function decode(): Decode\Group
{
return new Decode\Group();
}

/**
* Accepts Transformations and uses first successful one.
* @param Transformation[] $transformations
Expand Down
40 changes: 40 additions & 0 deletions components/ILIAS/Refinery/tests/Decode/GroupTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

/**
* This file is part of ILIAS, a powerful learning management system
* published by ILIAS open source e-Learning e.V.
*
* ILIAS is licensed with the GPL-3.0,
* see https://www.gnu.org/licenses/gpl-3.0.en.html
* You should have received a copy of said license along with the
* source code, too.
*
* If this is not the case or you just want to try ILIAS, you'll find
* us at:
* https://www.ilias.de
* https://github.com/ILIAS-eLearning
*
*********************************************************************/

declare(strict_types=1);

namespace ILIAS\Tests\Refinery\Decode;

use ILIAS\Refinery\Decode\Group;
use ILIAS\Refinery\Decode\Transformation\Json;
use PHPUnit\Framework\TestCase;

class GroupTest extends TestCase
{
public function testConstruct(): void
{
self::assertInstanceOf(Group::class, new Group());
}

public function testJson(): void
{
$group = new Group();

self::assertInstanceOf(Json::class, $group->json());
}
}
Loading
Loading