Skip to content

Commit

Permalink
feat(php): use strings for request enums + other small fixes (#4741)
Browse files Browse the repository at this point in the history
  • Loading branch information
dcb6 authored Sep 25, 2024
1 parent 6d3572a commit 47d5c9d
Show file tree
Hide file tree
Showing 724 changed files with 13,986 additions and 1,185 deletions.
2 changes: 1 addition & 1 deletion generators/commons/src/utils/parseIR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export async function parseIR<IR>({ absolutePathToIR, parse }: parseIR.Args<IR>)
if (!parsedIR.ok) {
// eslint-disable-next-line no-console
console.log(`Failed to parse ${absolutePathToIR}`);
throw new Error(`Failed to parse IR: ${parsedIR.errors}`);
throw new Error(`Failed to parse IR: ${JSON.stringify(parsedIR.errors, null, 4)}`);
}

return parsedIR.value;
Expand Down
1 change: 1 addition & 0 deletions generators/php/codegen/src/AsIs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export enum AsIsFiles {
NullableArrayTypeTest = "NullableArrayTypeTest.Template.php",
NullPropertyTypeTest = "NullPropertyTypeTest.Template.php",
ScalarTypesTest = "ScalarTypesTest.Template.php",
EnumTest = "EnumTest.Template.php",
TestTypeTest = "TestTypeTest.Template.php",
UnionArrayTypeTest = "UnionArrayTypeTest.Template.php",
ArrayType = "ArrayType.Template.php",
Expand Down
73 changes: 73 additions & 0 deletions generators/php/codegen/src/asIs/EnumTest.Template.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

namespace <%= namespace%>;

use JsonSerializable;
use PHPUnit\Framework\TestCase;
use <%= coreNamespace%>\SerializableType;
use <%= coreNamespace%>\JsonProperty;
use <%= coreNamespace%>\ArrayType;

enum Shape: string implements JsonSerializable
{
case Square = "SQUARE";
case Circle = "CIRCLE";
case Triangle = "TRIANGLE";

/**
* @return string
*/
public function jsonSerialize(): string
{
return $this->value;
}
}

class ShapeType extends SerializableType
{
/**
* @var Shape $shape
*/
#[JsonProperty('shape')]
public Shape $shape;

/**
* @var Shape[] $shapes
*/
#[ArrayType([Shape::class])]
#[JsonProperty('shapes')]
public array $shapes;

/**
* @param Shape $shape
* @param Shape[] $shapes
*/
public function __construct(
Shape $shape,
array $shapes,
) {
$this->shape = $shape;
$this->shapes = $shapes;
}
}

class EnumTest extends TestCase
{
public function testShapeEnumSerialization(): void
{
$object = new ShapeType(
Shape::Circle,
[Shape::Square, Shape::Circle, Shape::Triangle]
);

$expectedJson = json_encode([
'shape' => 'CIRCLE',
'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE']
], JSON_THROW_ON_ERROR);

$serializedJson = $object->toJson();

$this->assertJsonStringEqualsJsonString($expectedJson, $serializedJson,
'Serialized JSON does not match expected JSON for shape and shapes properties.');
}
}
12 changes: 10 additions & 2 deletions generators/php/codegen/src/asIs/JsonDeserializer.Template.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ private static function deserializeValue(mixed $data, mixed $type): mixed
}
$readableType = Utils::getReadableType($data);
throw new JsonException(
"Cannot deserialize value of type $readableType with any of the union types: " . $type);
"Cannot deserialize value of type $readableType with any of the union types: " . $type
);
}
if (is_array($type)) {
return self::deserializeArray((array)$data, $type);
Expand Down Expand Up @@ -117,6 +118,12 @@ private static function deserializeSingleValue(mixed $data, string $type): mixed
return self::deserializeObject($data, $type);
}

// Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because
// floats make come through from json_decoded as integers
if ($type === 'float' && (is_numeric($data))) {
return (float) $data;
}

if (gettype($data) === $type) {
return $data;
}
Expand All @@ -134,7 +141,8 @@ private static function deserializeSingleValue(mixed $data, string $type): mixed
*
* @throws JsonException If the type does not implement SerializableType.
*/
public static function deserializeObject(array $data, string $type): object {
public static function deserializeObject(array $data, string $type): object
{
if (!is_subclass_of($type, SerializableType::class)) {
throw new JsonException("$type is not a subclass of SerializableType.");
}
Expand Down
8 changes: 7 additions & 1 deletion generators/php/codegen/src/asIs/JsonSerializer.Template.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ private static function serializeSingleValue(mixed $data, string $type): mixed
return self::serializeObject($data);
}

// Handle floats as a special case since gettype($data) returns "double" for float values in PHP.
if ($type === 'float' && is_float($data)) {
return $data;
}

if (gettype($data) === $type) {
return $data;
}
Expand All @@ -118,7 +123,8 @@ private static function serializeSingleValue(mixed $data, string $type): mixed
* @return mixed The serialized data.
* @throws JsonException If the object does not implement JsonSerializable.
*/
public static function serializeObject(object $data): mixed {
public static function serializeObject(object $data): mixed
{
if (!is_subclass_of($data, JsonSerializable::class)) {
$type = get_class($data);
throw new JsonException("Class $type must implement JsonSerializable.");
Expand Down
27 changes: 22 additions & 5 deletions generators/php/codegen/src/asIs/ScalarTypesTest.Template.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
use <%= coreNamespace%>\ArrayType;
use <%= coreNamespace%>\Union;

class ScalarTypesTest extends SerializableType
class ScalarTypesTestType extends SerializableType
{
/**
* @var int $integerProperty
Expand All @@ -21,6 +21,11 @@ class ScalarTypesTest extends SerializableType
*/
#[JsonProperty('float_property')]
public float $floatProperty;
/**
* @var float $otherFloatProperty
*/
#[JsonProperty('other_float_property')]
public float $otherFloatProperty;

/**
* @var bool $booleanProperty
Expand All @@ -41,6 +46,13 @@ class ScalarTypesTest extends SerializableType
#[JsonProperty('int_float_array')]
public array $intFloatArray;

/**
* @var array<float> $floatArray
*/
#[ArrayType(['float'])]
#[JsonProperty('float_array')]
public array $floatArray;

/**
* @var bool|null $nullableBooleanProperty
*/
Expand All @@ -51,9 +63,11 @@ class ScalarTypesTest extends SerializableType
* @param array{
* integerProperty: int,
* floatProperty: float,
* otherFloatProperty: float,
* booleanProperty: bool,
* stringProperty: string,
* intFloatArray: array<int|float>,
* floatArray: array<float>,
* nullableBooleanProperty?: bool|null,
* } $values
*/
Expand All @@ -62,30 +76,33 @@ public function __construct(
) {
$this->integerProperty = $values['integerProperty'];
$this->floatProperty = $values['floatProperty'];
$this->otherFloatProperty = $values['otherFloatProperty'];
$this->booleanProperty = $values['booleanProperty'];
$this->stringProperty = $values['stringProperty'];
$this->intFloatArray = $values['intFloatArray'];
$this->floatArray = $values['floatArray'];
$this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null;
}
}

class ScalarTypesTestTest extends TestCase
class ScalarTypesTest extends TestCase
{
public function testAllScalarTypesIncludingFloat(): void
{
// Create test data
$data = [
'integer_property' => 42,
'float_property' => 3.14159,
'other_float_property' => 3,
'boolean_property' => true,
'string_property' => 'Hello, World!',
'nullable_boolean_property' => null,
'int_float_array' => [1, 2.5, 3, 4.75]
'int_float_array' => [1, 2.5, 3, 4.75],
'float_array' => [1, 2, 3, 4] // ensure we handle "integer-looking" floats
];

$json = json_encode($data, JSON_THROW_ON_ERROR);

$object = ScalarTypesTest::fromJson($json);
$object = ScalarTypesTestType::fromJson($json);

$serializedJson = $object->toJson();

Expand Down
3 changes: 2 additions & 1 deletion generators/php/codegen/src/ast/Class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,12 @@ export class Class extends AstNode {
writer.write("abstract ");
}
this.writeComment(writer);
writer.writeLine(`class ${this.name} `);
writer.write(`class ${this.name} `);
if (this.parentClassReference != null) {
writer.write("extends ");
this.parentClassReference.write(writer);
}
writer.newLine();
writer.writeLine("{");
writer.indent();

Expand Down
33 changes: 31 additions & 2 deletions generators/php/codegen/src/ast/Enum.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { ClassReference } from "./ClassReference";
import { CodeBlock } from "./CodeBlock";
import { Comment } from "./Comment";
import { AstNode } from "./core/AstNode";
import { Writer } from "./core/Writer";
import { Method } from "./Method";
import { Type } from "./Type";

export declare namespace Enum {
interface Args {
Expand All @@ -12,6 +16,8 @@ export declare namespace Enum {
backing?: "string" | "int";
/* Docs associated with the class */
docs?: string;
/* Whether the class should implement the JsonSerializable interface */
serializable?: boolean;
}

interface Member {
Expand All @@ -28,13 +34,15 @@ export class Enum extends AstNode {
public readonly backing: "string" | "int" | undefined;
public readonly docs: string | undefined;
public readonly members: Enum.Member[] = [];
public readonly serializable: boolean;

constructor({ name, namespace, backing, docs }: Enum.Args) {
constructor({ name, namespace, backing, docs, serializable }: Enum.Args) {
super();
this.name = name;
this.namespace = namespace;
this.backing = backing;
this.docs = docs;
this.serializable = serializable ?? false;
}

public addMember(member: Enum.Member): void {
Expand All @@ -48,8 +56,16 @@ export class Enum extends AstNode {
if (this.backing != null) {
writer.write(` : ${this.backing}`);
}
if (this.serializable) {
writer.addReference(
new ClassReference({
name: "JsonSerializable",
namespace: ""
})
);
writer.writeLine(" implements JsonSerializable");
}
writer.writeLine(" {");

writer.indent();
for (const member of this.members) {
writer.write(`case ${member.name}`);
Expand All @@ -62,6 +78,19 @@ export class Enum extends AstNode {
}
writer.writeTextStatement("");
}
if (this.serializable) {
writer.newLine();
writer.writeNode(
new Method({
name: "jsonSerialize",
return_: Type.string(),
access: "public",
parameters: [],
body: new CodeBlock("return $this->value;")
})
);
}

writer.writeNewLineIfLastLineNot();
writer.dedent();
writer.writeLine("}");
Expand Down
Loading

0 comments on commit 47d5c9d

Please sign in to comment.