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
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "0.11.2"
".": "0.11.3"
}
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## 0.11.3 (2026-04-18)

Full Changelog: [v0.11.2...v0.11.3](https://github.com/moderation-api/sdk-php/compare/v0.11.2...v0.11.3)

### Bug Fixes

* populate enum-typed properties with enum instances ([9187f82](https://github.com/moderation-api/sdk-php/commit/9187f8218b177950d895987b087d3ee6b742aaf4))

## 0.11.2 (2026-04-18)

Full Changelog: [v0.11.1...v0.11.2](https://github.com/moderation-api/sdk-php/compare/v0.11.1...v0.11.2)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ The REST API documentation can be found on [docs.moderationapi.com](https://docs
<!-- x-release-please-start-version -->

```
composer require "moderation-api/sdk-php 0.11.2"
composer require "moderation-api/sdk-php 0.11.3"
```

<!-- x-release-please-end -->
Expand Down
20 changes: 17 additions & 3 deletions src/Core/Conversion/EnumOf.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@ final class EnumOf implements Converter

/**
* @param list<bool|float|int|string|null> $members
* @param class-string<\BackedEnum>|null $class
*/
public function __construct(private readonly array $members)
{
public function __construct(
private readonly array $members,
private readonly ?string $class = null,
) {
$type = 'NULL';
foreach ($this->members as $member) {
$type = gettype($member);
Expand All @@ -33,13 +36,24 @@ public function __construct(private readonly array $members)
public static function fromBackedEnum(string $enum): self
{
// @phpstan-ignore-next-line argument.type
return self::$cache[$enum] ??= new self(array_column($enum::cases(), column_key: 'value'));
return self::$cache[$enum] ??= new self(
array_column($enum::cases(), column_key: 'value'),
class: $enum,
);
}

public function coerce(mixed $value, CoerceState $state): mixed
{
$this->tally($value, state: $state);

if ($value instanceof \BackedEnum) {
return $value;
}

if (null !== $this->class && (is_int($value) || is_string($value))) {
return ($this->class)::tryFrom($value) ?? $value;
}

return $value;
}

Expand Down
2 changes: 1 addition & 1 deletion src/Version.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
namespace ModerationAPI;

// x-release-please-start-version
const VERSION = '0.11.2';
const VERSION = '0.11.3';
// x-release-please-end
62 changes: 62 additions & 0 deletions tests/Core/ModelTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,30 @@ public function __construct(
}
}

enum TicketPriority: string
{
case Low = 'low';
case High = 'high';
}

class Ticket implements BaseModel
{
/** @use SdkModel<array<string, mixed>> */
use SdkModel;

#[Required(enum: TicketPriority::class)]
public TicketPriority $priority;

/** @var list<TicketPriority> */
#[Required(list: TicketPriority::class)]
public array $labels;

public function __construct()
{
$this->initialize();
}
}

/**
* @internal
*
Expand Down Expand Up @@ -141,4 +165,42 @@ public function testSerializeModelWithExplicitNull(): void
json_encode($model)
);
}

#[Test]
public function testScalarEnumCoercesToInstance(): void
{
$model = Ticket::fromArray(['priority' => 'low', 'labels' => []]);
$this->assertSame(TicketPriority::Low, $model->priority);
}

#[Test]
public function testListOfEnumCoercesElementsToInstances(): void
{
$model = Ticket::fromArray(['priority' => 'low', 'labels' => ['low', 'high']]);
$this->assertCount(2, $model->labels);
$this->assertSame(TicketPriority::Low, $model->labels[0]);
$this->assertSame(TicketPriority::High, $model->labels[1]);
}

#[Test]
public function testEnumInstancePassesThrough(): void
{
$model = Ticket::fromArray(['priority' => TicketPriority::High, 'labels' => []]);
$this->assertSame(TicketPriority::High, $model->priority);
}

#[Test]
public function testInvalidEnumScalarFallsBackToData(): void
{
$model = Ticket::fromArray(['priority' => 'urgent', 'labels' => []]);
$this->assertSame('urgent', $model['priority']);
}

#[Test]
public function testEnumWireFormatStableAcrossConstruction(): void
{
$fromScalar = Ticket::fromArray(['priority' => 'low', 'labels' => ['high']]);
$fromInstance = Ticket::fromArray(['priority' => TicketPriority::Low, 'labels' => [TicketPriority::High]]);
$this->assertSame(json_encode($fromScalar), json_encode($fromInstance));
}
}