Skip to content

Commit

Permalink
feat(php): inheritance (#4792)
Browse files Browse the repository at this point in the history
  • Loading branch information
dcb6 authored Oct 2, 2024
1 parent 06876e3 commit 20f3c3b
Show file tree
Hide file tree
Showing 204 changed files with 8,857 additions and 43 deletions.
1 change: 1 addition & 0 deletions generators/php/codegen/src/AsIs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export enum AsIsFiles {
DateArrayTypeTest = "Json/DateArrayTypeTest.Template.php",
EmptyArraysTest = "Json/EmptyArraysTest.Template.php",
EnumTest = "Json/EnumTest.Template.php",
TraitTest = "Json/TraitTest.Template.php",
InvalidTypesTest = "Json/InvalidTypesTest.Template.php",
MixedDateArrayTypeTest = "Json/MixedDateArrayTypeTest.Template.php",
NestedUnionArrayTypeTest = "Json/NestedUnionArrayTypeTest.Template.php",
Expand Down
61 changes: 61 additions & 0 deletions generators/php/codegen/src/asIs/Json/TraitTest.Template.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

namespace <%= namespace%>;

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

trait IntegerPropertyTrait
{
/**
* @var int $integerProperty
*/
#[JsonProperty('integer_property')]
public int $integerProperty;
}

class TypeWithTrait extends SerializableType
{
use IntegerPropertyTrait;

/**
* @var string $stringProperty
*/
#[JsonProperty('string_property')]
public string $stringProperty;

/**
* @param array{
* integerProperty: int,
* stringProperty: string,
* } $values
*/
public function __construct(array $values)
{
$this->integerProperty = $values['integerProperty'];
$this->stringProperty = $values['stringProperty'];
}
}

class TraitTest extends TestCase
{
public function testTraitPropertyAndString(): void
{
$data = [
'integer_property' => 42,
'string_property' => 'Hello, World!',
];

$json = json_encode($data, JSON_THROW_ON_ERROR);

$object = TypeWithTrait::fromJson($json);

$serializedJson = $object->toJson();

$this->assertJsonStringEqualsJsonString($json, $serializedJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.');

$this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.');
$this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".');
}
}
39 changes: 31 additions & 8 deletions generators/php/codegen/src/ast/Class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { Field } from "./Field";
import { Method } from "./Method";
import { Comment } from "./Comment";
import { orderByAccess } from "./utils/orderByAccess";
import { ClassReference } from "./ClassReference";
import { Trait } from "./Trait";

export declare namespace Class {
interface Args {
Expand All @@ -20,6 +22,8 @@ export declare namespace Class {
docs?: string;
/* The class to inherit from if any */
parentClassReference?: AstNode;
/* The traits that this class uses, if any */
traits?: ClassReference[];
}

interface Constructor {
Expand All @@ -38,18 +42,20 @@ export class Class extends AstNode {
public readonly abstract: boolean;
public readonly docs: string | undefined;
public readonly parentClassReference: AstNode | undefined;
public readonly traits: ClassReference[];

public readonly fields: Field[] = [];
public readonly methods: Method[] = [];
private constructor_: Class.Constructor | undefined;

constructor({ name, namespace, abstract, docs, parentClassReference }: Class.Args) {
constructor({ name, namespace, abstract, docs, parentClassReference, traits }: Class.Args) {
super();
this.name = name;
this.namespace = namespace;
this.abstract = abstract ?? false;
this.docs = docs;
this.parentClassReference = parentClassReference;
this.traits = traits ?? [];
}

public addConstructor(constructor: Class.Constructor): void {
Expand All @@ -64,6 +70,10 @@ export class Class extends AstNode {
this.methods.push(method);
}

public addTrait(traitClassReference: ClassReference): void {
this.traits.push(traitClassReference);
}

public write(writer: Writer): void {
if (this.abstract) {
writer.write("abstract ");
Expand All @@ -77,6 +87,17 @@ export class Class extends AstNode {
writer.newLine();
writer.writeLine("{");
writer.indent();
if (this.traits.length > 0) {
writer.write("use ");
this.traits.forEach((trait, index) => {
if (index > 0) {
writer.write(",");
}
writer.writeNode(trait);
});
writer.writeTextStatement("");
writer.newLine();
}

this.writeFields({ writer, fields: orderByAccess(this.fields) });
if (this.constructor != null || this.methods.length > 0) {
Expand Down Expand Up @@ -141,13 +162,15 @@ export class Class extends AstNode {
}

private writeFields({ writer, fields }: { writer: Writer; fields: Field[] }): void {
fields.forEach((field, index) => {
if (index > 0) {
writer.newLine();
}
field.write(writer);
writer.writeNewLineIfLastLineNot();
});
fields
.filter((field) => !field.inherited)
.forEach((field, index) => {
if (index > 0) {
writer.newLine();
}
field.write(writer);
writer.writeNewLineIfLastLineNot();
});
}

private writeMethods({ writer, methods }: { writer: Writer; methods: Method[] }): void {
Expand Down
9 changes: 7 additions & 2 deletions generators/php/codegen/src/ast/DataClass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { Type } from "./Type";
import { orderByAccess } from "./utils/orderByAccess";
import { php } from "..";
import { convertFromPhpVariableName } from "./utils/convertFromPhpVariableName";
import { Trait } from "./Trait";
import { ClassReference } from "../php";

const CONSTRUCTOR_PARAMETER_NAME = "values";

Expand All @@ -21,11 +23,11 @@ export class DataClass extends AstNode {
public readonly namespace: string;
private class_: Class;

constructor({ name, namespace, abstract, docs, parentClassReference }: DataClass.Args) {
constructor({ name, namespace, abstract, docs, parentClassReference, traits }: DataClass.Args) {
super();
this.name = name;
this.namespace = namespace;
this.class_ = new Class({ name, namespace, abstract, docs, parentClassReference });
this.class_ = new Class({ name, namespace, abstract, docs, parentClassReference, traits });
}

public addField(field: Field): void {
Expand All @@ -35,6 +37,9 @@ export class DataClass extends AstNode {
public addMethod(method: Method): void {
this.class_.addMethod(method);
}
public addTrait(traitClassRefeference: ClassReference): void {
this.class_.addTrait(traitClassRefeference);
}

public write(writer: Writer): void {
const orderedFields = orderByAccess(this.class_.fields).map(
Expand Down
6 changes: 5 additions & 1 deletion generators/php/codegen/src/ast/Field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export declare namespace Field {
inlineDocs?: string;
/* Field attributes */
attributes?: Attribute[];
/* Indicates that this field is inherited and should not be written to the class. */
inherited?: boolean;
}
}

Expand All @@ -37,8 +39,9 @@ export class Field extends AstNode {
private docs: string | undefined;
private inlineDocs: string | undefined;
private attributes: Attribute[];
public readonly inherited: boolean;

constructor({ name, type, access, readonly_, initializer, docs, inlineDocs, attributes }: Field.Args) {
constructor({ name, type, access, readonly_, initializer, docs, inlineDocs, attributes, inherited }: Field.Args) {
super();
this.name = convertToPhpVariableName(name);
this.type = type;
Expand All @@ -48,6 +51,7 @@ export class Field extends AstNode {
this.docs = docs;
this.inlineDocs = inlineDocs;
this.attributes = attributes ?? [];
this.inherited = inherited ?? false;
}

public write(writer: Writer): void {
Expand Down
101 changes: 101 additions & 0 deletions generators/php/codegen/src/ast/Trait.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { AstNode } from "./core/AstNode";
import { Writer } from "./core/Writer";
import { Field } from "./Field";
import { Method } from "./Method";
import { Comment } from "./Comment";
import { orderByAccess } from "./utils/orderByAccess";
import { ClassReference } from "./ClassReference";

export declare namespace Trait {
interface Args {
/* The name of the PHP trait */
name: string;
/* The namespace of the PHP trait */
namespace: string;
/* Docs associated with the trait */
docs?: string;
/* The traits that this trait uses, if any */
traits?: ClassReference[];
}
}

export class Trait extends AstNode {
public readonly name: string;
public readonly namespace: string;
public readonly docs: string | undefined;
public readonly traits: ClassReference[];

public readonly fields: Field[] = [];
public readonly methods: Method[] = [];

constructor({ name, namespace, docs, traits }: Trait.Args) {
super();
this.name = name;
this.namespace = namespace;
this.docs = docs;
this.traits = traits ?? [];
}

public addField(field: Field): void {
this.fields.push(field);
}

public addMethod(method: Method): void {
this.methods.push(method);
}

public write(writer: Writer): void {
this.writeComment(writer);
writer.write(`trait ${this.name} `);
writer.newLine();
writer.writeLine("{");
writer.indent();

if (this.traits.length > 0) {
writer.write("use ");
this.traits.forEach((trait, index) => {
if (index > 0) {
writer.write(",");
}
writer.writeNode(trait);
});
writer.writeTextStatement("");
writer.newLine();
}

this.writeFields({ writer, fields: orderByAccess(this.fields) });
this.writeMethods({ writer, methods: orderByAccess(this.methods) });

writer.dedent();
writer.writeLine("}");
return;
}

private writeComment(writer: Writer): void {
if (this.docs == null) {
return undefined;
}
const comment = new Comment({ docs: this.docs });
comment.write(writer);
}

private writeFields({ writer, fields }: { writer: Writer; fields: Field[] }): void {
fields.forEach((field, index) => {
if (index > 0) {
writer.newLine();
}
field.write(writer);
writer.writeNewLineIfLastLineNot();
});
}

private writeMethods({ writer, methods }: { writer: Writer; methods: Method[] }): void {
methods.forEach((method, index) => {
if (index > 0) {
writer.newLine();
}
method.write(writer);
writer.writeNewLineIfLastLineNot();
});
}
}
1 change: 1 addition & 0 deletions generators/php/codegen/src/ast/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export { Array_ as Array } from "./Array";
export { Attribute } from "./Attribute";
export { Class } from "./Class";
export { Trait } from "./Trait";
export { ClassInstantiation } from "./ClassInstantiation";
export { ClassReference } from "./ClassReference";
export { CodeBlock } from "./CodeBlock";
Expand Down
1 change: 1 addition & 0 deletions generators/php/codegen/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const TRAITS_DIRECTORY = "Traits";
Loading

0 comments on commit 20f3c3b

Please sign in to comment.