Skip to content

Commit

Permalink
Improve Item named constructor
Browse files Browse the repository at this point in the history
  • Loading branch information
nyamsprod committed Dec 11, 2024
1 parent 3d508b2 commit b939cf4
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 28 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ All Notable changes to `bakame/http-strucured-fields` will be documented in this
- methods `getByKey`, `hasKeys`, `indexByKey`, `keyByIndex`, `toAssociative` to all ordered map classes (`Dictionary` and `Parameters`).
- `StructuredFieldProvider` interface
- `Item::parameterByKey` and `Item::parameterByIndex` methods to mirror the new public API.
- `Item::tryNew` and `Item::tryFromPair` which return `null` instead of throwing an exception
- All `Item` named constructors have a `try` equivalent which return `null` instead of throwing an exception
- `Parameters::valueByKey`
- `Parameters::valueByIndex`
- Added a validation mechanism to facilitate `Item` and `Parameters` validation against field definition.
Expand Down
10 changes: 9 additions & 1 deletion docs/2.0/field-values.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,6 @@ use Bakame\Http\StructuredFields\Item;
use Bakame\Http\StructuredFields\Token;

Item:new(DateTimeInterface|Byte|Token|DisplayString|string|int|array|float|bool $value): self
Item:tryNew(mixed $value): ?self
Item::fromDecodedBytes(Stringable|string $value): self;
Item::fromEncodedBytes(Stringable|string $value): self;
Item::fromEncodedDisplayString(Stringable|string $value): self;
Expand All @@ -165,6 +164,15 @@ Item::true(): self;
Item::false(): self;
```

On error, those named constructor will throw an exception, if you do not want an exception
and prefer a softer approache where the named constructor would instead return `null`, you
can prepend the named constructor with the `try` prefix.

```php
Item::fromDecimal('42'); // will throw because the value is a string;
Item::tryFromDecimal('42'); // will return null instead
```

To update the `Item` instance value, use the `withValue` method:

```php
Expand Down
72 changes: 46 additions & 26 deletions src/Item.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@

namespace Bakame\Http\StructuredFields;

use BadMethodCallException;
use Bakame\Http\StructuredFields\Validation\Violation;
use DateTimeImmutable;
use DateTimeInterface;
use DateTimeZone;
use Exception;
use ReflectionMethod;
use Stringable;
use Throwable;
use TypeError;
Expand All @@ -27,6 +29,24 @@
* @phpstan-import-type SfItemInput from StructuredFieldProvider
* @phpstan-import-type SfItemPair from StructuredFieldProvider
* @phpstan-import-type SfTypeInput from StructuredFieldProvider
*
* @method static ?Item tryFromPair(array{0: SfItemInput, 1?: Parameters|iterable<array{0:string, 1:SfItemInput}>}|array<mixed> $pair) try to create a new instance from a Pair
* @method static ?Item tryFromRfc9651(Stringable|string $httpValue) try to create a new instance from a string using RFC9651
* @method static ?Item tryFromRfc8941(Stringable|string $httpValue) try to create a new instance from a string using RFC8941
* @method static ?Item tryFromHttpValue(Stringable|string $httpValue) try to create a new instance from a string
* @method static ?Item tryFromAssociative(Bytes|Token|DisplayString|DateTimeInterface|string|int|float|bool $value, StructuredFieldProvider|Parameters|iterable<string, SfItemInput> $parameters) try to create a new instance from a value and a parameters as associative construct
* @method static ?Item tryNew(mixed $value) try to create a new bare instance from a value
* @method static ?Item tryFromEncodedBytes(Stringable|string $value) try to create a new instance from an encoded byte sequence
* @method static ?Item tryFromDecodedBytes(Stringable|string $value) try to create a new instance from a decoded byte sequence
* @method static ?Item tryFromEncodedDisplayString(Stringable|string $value) try to create a new instance from an encoded display string
* @method static ?Item tryFromDecodedDisplayString(Stringable|string $value) try to create a new instance from a decoded display string
* @method static ?Item tryFromToken(Stringable|string $value) try to create a new instance from a token string
* @method static ?Item tryFromTimestamp(int $timestamp) try to create a new instance from a timestamp
* @method static ?Item tryFromDateFormat(string $format, string $datetime) try to create a new instance from a date format
* @method static ?Item tryFromDateString(string $datetime, DateTimeZone|string|null $timezone = null) try to create a new instance from a date string
* @method static ?Item tryFromDate(DateTimeInterface $datetime) try to create a new instance from a DateTimeInterface object
* @method static ?Item tryFromDecimal(int|float $value) try to create a new instance from a float
* @method static ?Item tryFromInteger(int|float $value) try to create a new instance from an integer
*/
final class Item
{
Expand All @@ -47,6 +67,32 @@ private function __construct(Token|Bytes|DisplayString|DateTimeInterface|int|flo
$this->type = Type::fromVariable($value);
}

/**
* @throws BadMethodCallException
*/
public static function __callStatic(string $name, array $arguments): ?self /* @phpstan-ignore-line */
{
if (!str_starts_with($name, 'try')) {
throw new BadMethodCallException('The method "'.self::class.'::'.$name.'" does not exist.');
}

$namedConstructor = lcfirst(substr($name, strlen('try')));
if (!method_exists(self::class, $namedConstructor)) {
throw new BadMethodCallException('The method "'.self::class.'::'.$name.'" does not exist.');
}

$method = new ReflectionMethod(self::class, $namedConstructor);
if (!$method->isPublic() || !$method->isStatic()) {
throw new BadMethodCallException('The method "'.self::class.'::'.$name.'" can not be accessed directly.');
}

try {
return self::$namedConstructor(...$arguments); /* @phpstan-ignore-line */
} catch (Throwable) { /* @phpstan-ignore-line */
return null;
}
}

public static function fromRfc9651(Stringable|string $httpValue): self
{
return self::fromHttpValue($httpValue, Ietf::Rfc9651);
Expand Down Expand Up @@ -97,18 +143,6 @@ public static function fromAssociative(
return new self($value, $parameters);
}

/**
* @param array{0: SfItemInput, 1?: Parameters|iterable<array{0:string, 1:SfItemInput}>}|array<mixed> $pair
*/
public static function tryFromPair(array $pair): ?self
{
try {
return self::fromPair($pair);
} catch (StructuredFieldError) {
return null;
}
}

/**
* @param array{0: SfItemInput, 1?: Parameters|iterable<array{0:string, 1:SfItemInput}>}|array<mixed> $pair
*
Expand Down Expand Up @@ -157,20 +191,6 @@ public static function new(mixed $value): self
return new self($value); /* @phpstan-ignore-line */
}

/**
* Returns a new bare instance from value or null on error.
*
* @param SfTypeInput|Item|array{0:SfTypeInput, 1:Parameters|iterable<array{0:string, 1:SfTypeInput}>} $value
*/
public static function tryNew(StructuredFieldProvider|Item|DateTimeInterface|Bytes|DisplayString|Token|array|int|float|string|bool $value): ?self
{
try {
return self::new($value);
} catch (SyntaxError) {
return null;
}
}

/**
* Returns a new instance from a string.
*
Expand Down

0 comments on commit b939cf4

Please sign in to comment.