{"id":27091286,"url":"https://github.com/dgame/php-dto","last_synced_at":"2025-04-06T07:26:13.981Z","repository":{"id":45874168,"uuid":"386668718","full_name":"Dgame/php-dto","owner":"Dgame","description":"A data transfer object inspired by Rust's serde","archived":false,"fork":false,"pushed_at":"2022-03-10T19:03:37.000Z","size":165,"stargazers_count":41,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-05T01:19:28.743Z","etag":null,"topics":["data-transfer-object","dto","php"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Dgame.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-07-16T14:40:44.000Z","updated_at":"2025-03-18T16:41:20.000Z","dependencies_parsed_at":"2022-09-02T12:50:55.483Z","dependency_job_id":null,"html_url":"https://github.com/Dgame/php-dto","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Dgame%2Fphp-dto","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Dgame%2Fphp-dto/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Dgame%2Fphp-dto/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Dgame%2Fphp-dto/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Dgame","download_url":"https://codeload.github.com/Dgame/php-dto/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247447427,"owners_count":20940318,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["data-transfer-object","dto","php"],"created_at":"2025-04-06T07:26:13.300Z","updated_at":"2025-04-06T07:26:13.970Z","avatar_url":"https://github.com/Dgame.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Data Transfer Object\n\nWant to deserialize an object with data on the fly? Go for it by using the `From` trait.\n\n---\n\nHow is this package any different from [spaties](https://github.com/spatie) popular [data-transfer-object](https://github.com/spatie/data-transfer-object), you may ask?\nWell, it's not meant to be a replacement by any means. But while using it I've often come across some things I've missed since I knew them from [serde](https://serde.rs/), like renaming and ignoring properties, something that spatie's _data-transfer-object_ [might not get](https://github.com/spatie/data-transfer-object/issues/142#issuecomment-690418112) in the near future.\nSo there it is, my own little DTO package :) I hope it helps someone, as it helps me in my daily work.\nFeel free to open issues or pull requests - any help is greatly appreciated!\n\n### Requirements\n\nThis package is designed for PHP \u0026GreaterEqual; 8.0 only since it's using [PHP 8.0 Attributes](https://stitcher.io/blog/attributes-in-php-8).\n\n# Attributes\n\n## Name\n\nYou get a parameter which is not named as the parameter in your class? `#[Name(...)]` to the rescue - just specify the name from the Request:\n\n```php\nuse Dgame\\DataTransferObject\\Annotation\\Name;\nuse Dgame\\DataTransferObject\\DataTransfer;\n\nfinal class Limit\n{\n    use DataTransfer;\n\n    public int $offset;\n    #[Name('size')]\n    public int $limit;\n}\n```\n\nNow the key `size` will be mapped to the property `$limit` - but keep in mind: the name `limit` is no longer known\n since you overwrote it with `size`. If that is not your intention, take a look at the [Alias](#alias) Attribute.  \n\n## Alias\n\nYou get a parameter which is not **always** named as the parameter in your class? `#[Alias(...)]` can help you - just specify the alias from the Request:\n\n```php\nuse Dgame\\DataTransferObject\\Annotation\\Alias;\nuse Dgame\\DataTransferObject\\DataTransfer;\n\nfinal class Limit\n{\n    use DataTransfer;\n\n    public int $offset;\n    #[Alias('size')]\n    public int $limit;\n}\n```\n\nNow the keys `size` **and** `limit`  will be mapped to the property `$limit`. You can mix `#[Name(...)]` and `#[Alias(...)]` as you want:\n\n```php\nuse Dgame\\DataTransferObject\\Annotation\\Alias;\nuse Dgame\\DataTransferObject\\Annotation\\Name;\nuse Dgame\\DataTransferObject\\DataTransfer;\n\nfinal class Foo\n{\n    use DataTransfer;\n\n    #[Name('a')]\n    #[Alias('z')]\n    public int $id;\n}\n```\n\nThe keys `a` and `z` are mapped to the property `id` - but not the key `id` since you overwrote it with `a`. But the following\n\n```php\nuse Dgame\\DataTransferObject\\Annotation\\Alias;\nuse Dgame\\DataTransferObject\\DataTransfer;\n\nfinal class Foo\n{\n    use DataTransfer;\n\n    #[Alias('a')]\n    #[Alias('z')]\n    public int $id;\n}\n```\n\nwill accept the keys `a`, `z` and `id`.\n\n## Transformations\n\nIf you want to _transform_ a value **before** it is assigned to the property, you can use Transformations.\nYou just need to implement the _Transformation_ interface. \n\n### Cast\n\n_Cast_  is currently the only built-in Transformation and let you apply a Type-Cast **before** the value is assigned to the property:\n\nIf not told otherwise, a simple type-cast is performed. In the example below it would just call something like `$this-\u003eid = (int) $id`:\n\n```php\nuse Dgame\\DataTransferObject\\Annotation\\Cast;\n\nfinal class Foo\n{\n    use DataTransfer;\n\n    #[Cast]\n    public int $id;\n}\n```\n\nBut that would be tried for **any** input. If you want to limit this to certain types, you can use `types`:\n\n```php\nuse Dgame\\DataTransferObject\\Annotation\\Cast;\n\nfinal class Foo\n{\n    use DataTransfer;\n\n    #[Cast(types: ['string', 'float', 'bool'])]\n    public int $id;\n}\n```\n\nHere the cast would only be performed if the incoming value is either an `int`, `string`, `float` or `bool`. \n\nIf you want more control, you can use a static method inside of the class:\n\n```php\nuse Dgame\\DataTransferObject\\Annotation\\Cast;\n\nfinal class Foo\n{\n    use DataTransfer;\n\n    #[Cast(method: 'toInt', class: self::class)]\n    public int $id;\n\n    public static function toInt(string|int|float|bool $value): int\n    {\n        return (int) $value;\n    }\n}\n```\n\nor a function:\n\n```php\nuse Dgame\\DataTransferObject\\Annotation\\Cast;\n\nfunction toInt(string|int|float|bool $value): int\n{\n    return (int) $value;\n}\n\nfinal class Foo\n{\n    use DataTransfer;\n\n    #[Cast(method: 'toInt')]\n    public int $id;\n}\n```\n\nIf a class is given but not a `method`, by default `__invoke` will be used:\n\n```php\nuse Dgame\\DataTransferObject\\Annotation\\Cast;\n\nfinal class Foo\n{\n    use DataTransfer;\n\n    #[Cast(class: self::class)]\n    public int $id;\n\n    public function __invoke(string|int|float|bool $value): int\n    {\n        return (int) $value;\n    }\n}\n```\n\n## Validation\n\nYou want to validate the value before it is assigned? We can do that. There are a few pre-defined validations prepared, but you can easily write your own by implementing the `Validation`-interface.\n\n### Min\n\n```php\nuse Dgame\\DataTransferObject\\Annotation\\Min;\nuse Dgame\\DataTransferObject\\DataTransfer;\n\nfinal class Limit\n{\n    use DataTransfer;\n    \n    #[Min(0)]\n    public int $offset;\n    #[Min(0)]\n    public int $limit;\n}\n```\n\nBoth `$offset` and `$limit` must be at least have the value `0` (so they must be positive-integers). If not, an exception is thrown. You can configure the message of the exception by specifying the `message` parameter:\n\n```php\nuse Dgame\\DataTransferObject\\Annotation\\Min;\nuse Dgame\\DataTransferObject\\DataTransfer;\n\nfinal class Limit\n{\n    use DataTransfer;\n    \n    #[Min(0, message: 'Offset must be positive!')]\n    public int $offset;\n    #[Min(0, message: 'Limit must be positive!')]\n    public int $limit;\n}\n```\n\n### Max\n\n```php\nuse Dgame\\DataTransferObject\\Annotation\\Max;\nuse Dgame\\DataTransferObject\\DataTransfer;\n\nfinal class Limit\n{\n    use DataTransfer;\n    \n    #[Max(1000)]\n    public int $offset;\n    #[Max(1000)]\n    public int $limit;\n}\n```\n\nBoth `$offset` and `$limit` may not exceed `1000`. If they do, an exception is thrown. You can configure the message of the exception by specifying the `message` parameter:\n\n```php\nuse Dgame\\DataTransferObject\\Annotation\\Max;\nuse Dgame\\DataTransferObject\\DataTransfer;\n\nfinal class Limit\n{\n    use DataTransfer;\n    \n    #[Max(1000, message: 'Offset may not be larger than 1000')]\n    public int $offset;\n    #[Max(1000, message: 'Limit may not be larger than 1000')]\n    public int $limit;\n}\n```\n\n### Instance\n\nDo you want to make sure that a property is an instance of a certain class or that each item in an array is an instance of that said class?\n\n```php\nuse Dgame\\DataTransferObject\\Annotation\\Instance;\n\nfinal class Collection\n{\n    #[Instance(class: Entity::class, message: 'We need an array of Entities!')]\n    private array $entities;\n}\n```\n\n### Type\n\nIf you are trying to cover objects or other class instances, you should probably take a look at [Instance](#instance).\n\nAs long as you specify a type for your properties, the `Type` validation is automatically added to ensure that the specified values can be assigned to the specified types. If not, a validation exception will be thrown.\nWithout this validation, a `TypeError` would be thrown, which may not be desirable.\n\nSo this code\n```php\nfinal class Foo\n{\n    private ?int $id;\n}\n```\n\nis actually seen as this:\n```php\nuse Dgame\\DataTransferObject\\Annotation\\Type;\n\nfinal class Foo\n{\n    #[Type(name: '?int')]\n    private ?int $id;\n}\n```\n\nThe following snippets are equivalent to the snippet above:\n\n```php\nuse Dgame\\DataTransferObject\\Annotation\\Type;\n\nfinal class Foo\n{\n    #[Type(name: 'int|null')]\n    private ?int $id;\n}\n```\n\n```php\nuse Dgame\\DataTransferObject\\Annotation\\Type;\n\nfinal class Foo\n{\n    #[Type(name: 'int', allowsNull: true)]\n    private ?int $id;\n}\n```\n\n---\n\nIf you want to change the exception message, you can do so using the `message` parameter:\n\n```php\nuse Dgame\\DataTransferObject\\Annotation\\Type;\n\nfinal class Foo\n{\n    #[Type(name: '?int', message: 'id is expected to be int or null')]\n    private ?int $id;\n}\n```\n\n### Custom\n\nDo you want your own Validation? Just implement the `Validation`-interface:\n\n```php\nuse Dgame\\DataTransferObject\\Annotation\\Validation;\nuse Dgame\\DataTransferObject\\DataTransfer;\n\n#[Attribute(Attribute::TARGET_PROPERTY)]\nfinal class NumberBetween implements Validation\n{\n    public function __construct(private int|float $min, private int|float $max)\n    {\n    }\n\n    public function validate(mixed $value): void\n    {\n        if (!is_numeric($value)) {\n            throw new InvalidArgumentException(var_export($value, true) . ' must be a numeric value');\n        }\n\n        if ($value \u003c $this-\u003emin) {\n            throw new InvalidArgumentException(var_export($value, true) . ' must be \u003e= ' . $this-\u003emin);\n        }\n\n        if ($value \u003e $this-\u003emax) {\n            throw new InvalidArgumentException(var_export($value, true) . ' must be \u003c= ' . $this-\u003emax);\n        }\n    }\n}\n\nfinal class ValidationStub\n{\n    use DataTransfer;\n\n    #[NumberBetween(18, 125)]\n    private int $age;\n\n    public function getAge(): int\n    {\n        return $this-\u003eage;\n    }\n}\n```\n\n## Ignore\n\nYou don't want a specific key-value to override your property? Just ignore it:\n\n```php\nuse Dgame\\DataTransferObject\\Annotation\\Ignore;\nuse Dgame\\DataTransferObject\\DataTransfer;\n\nfinal class Foo\n{\n    use DataTransfer;\n\n    #[Ignore]\n    public string $uuid = 'abc';\n    public int $id = 0;\n}\n\n$foo = Foo::from(['uuid' =\u003e 'xyz', 'id' =\u003e 42]);\necho $foo-\u003eid; // 42\necho $foo-\u003euuid; // abc\n```\n\n## Reject\n\nYou want to go one step further than simply ignoring a value? Then `Reject` it:\n\n```php\nuse Dgame\\DataTransferObject\\Annotation\\Reject;\nuse Dgame\\DataTransferObject\\DataTransfer;\n\nfinal class Foo\n{\n    use DataTransfer;\n\n    #[Reject(reason: 'The attribute \"uuid\" is not supposed to be set')]\n    public string $uuid = 'abc';\n}\n\n$foo = Foo::from(['id' =\u003e 42]); // Works fine\necho $foo-\u003eid; // 42\necho $foo-\u003euuid; // abc\n\n$foo = Foo::from(['uuid' =\u003e 'xyz', 'id' =\u003e 42]); // throws 'The attribute \"uuid\" is not supposed to be set'\n```\n\n## Required\n\nNormally, a nullable-property or a property with a provided default value is treated with said default-value or null if the property cannot be assigned from the provided data.\nIf no default-value is provided and the property is not nullable, an error is thrown in case the property was not found.\nBut in some cases you might want to specify the reason, why the property is required or even want to require an otherwise default-able property. You can do that by using `Required`:\n\n```php\nuse Dgame\\DataTransferObject\\Annotation\\Required;\nuse Dgame\\DataTransferObject\\DataTransfer;\n\nfinal class Foo\n{\n    use DataTransfer;\n\n    #[Required(reason: 'We need an \"id\"')]\n    public ?int $id;\n    \n    #[Required(reason: 'We need a \"name\"')]\n    public string $name;\n}\n\nFoo::from(['id' =\u003e 42, 'name' =\u003e 'abc']); // Works\nFoo::from(['name' =\u003e 'abc']); // Fails but would work without the `Required`-Attribute since $id is nullable\nFoo::from(['id' =\u003e 42]); // Fails and would fail regardless of the `Required`-Attribute since $name is not nullable and has no default-value - but the reason why it is required is now more clear.\n```\n\n## Optional\n\nThe counterpart of [Required](#required).\nIf you don't want to or can't provide a default/nullable value, `Optional` will assign the **default value** of the property-type in case of a missing value:\n\n```php\nfinal class Foo\n{\n    use DataTransfer;\n    \n    #[Optional]\n    public int $id;\n}\n\n$foo = Foo::from([]);\nassert($foo-\u003eid === 0);\n```\n\nOf course you can specify which value should be used if no data is provided:\n\n```php\nfinal class Foo\n{\n    use DataTransfer;\n    \n    #[Optional(value: 42)]\n    public int $id;\n}\n\n$foo = Foo::from([]);\nassert($foo-\u003eid === 42);\n```\n\nIn case you're using `Optional` together with a provided default-value, the default-value has always priority:\n\n```php\nfinal class Foo\n{\n    use DataTransfer;\n    \n    #[Optional(value: 42)]\n    public int $id = 23;\n}\n\n$foo = Foo::from([]);\nassert($foo-\u003eid === 23);\n```\n\n## Numeric\n\nYou have `int` or `float` properties but aren't sure if those aren't delivered as e.g. `string`? `Numeric` to the rescue! It will translate the value to a numeric representation (to `int` or `float`):\n\n```php\nuse Dgame\\DataTransferObject\\Annotation\\Numeric;\n\nfinal class Foo\n{\n    use DataTransfer;\n    \n    #[Numeric(message: 'id must be numeric')]\n    public int $id;\n}\n\n$foo = Foo::from(['id' =\u003e '23']);\nassert($foo-\u003eid === 23);\n```\n\n## Boolean\n\nYou have `bool` properties but aren't sure if those aren't delivered as `string` or `int`? `Boolean` can help you with that!\n\n```php\nuse Dgame\\DataTransferObject\\Annotation\\Boolean;\n\nfinal class Foo\n{\n    use DataTransfer;\n    \n    #[Boolean(message: 'checked must be a bool')]\n    public bool $checked;\n    #[Boolean(message: 'verified must be a bool')]\n    public bool $verified;\n}\n\n$foo = Foo::from(['checked' =\u003e 'yes', 'verified' =\u003e 0]);\nassert($foo-\u003echecked === true);\nassert($foo-\u003everified === false);\n```\n\n## Date\n\nYou want a `DateTime` but got a string? No problem:\n\n```php\nuse Dgame\\DataTransferObject\\Annotation\\Date;\nuse \\DateTime;\n\nfinal class Foo\n{\n    use DataTransfer;\n    \n    #[Date(format: 'd.m.Y', message: 'Your birthday must be a date')]\n    public DateTime $birthday;\n}\n\n$foo = Foo::from(['birthday' =\u003e '19.09.1979']);\nassert($foo-\u003ebirthday === DateTime::createFromFormat('d.m.Y', '19.09.1979'));\n```\n\n\n## In\n\nYour value must be one of a specific range or enumeration? You can ensure that with `In`:\n\n```php\nuse Dgame\\DataTransferObject\\Annotation\\In;\n\nfinal class Foo\n{\n    use DataTransfer;\n    \n    #[In(values: ['beginner', 'advanced', 'difficult'], message: 'Must be either \"beginner\", \"advanced\" or \"difficult\"')]\n    public string $difficulty;\n}\n\nFoo::from(['difficulty' =\u003e 'foo']); // will throw a error, since difficulty is not in the provided values\n$foo = Foo::from(['difficulty' =\u003e 'advanced']);\nassert($foo-\u003edifficulty === 'advanced');\n```\n\n## NotIn\n\nYour value must **not** be one of a specific range or enumeration? You can ensure that with `NotIn`:\n\n```php\nuse Dgame\\DataTransferObject\\Annotation\\NotIn;\n\nfinal class Foo\n{\n    use DataTransfer;\n    \n    #[NotIn(values: ['holy', 'shit', 'wtf'], message: 'Must not be a swear word')]\n    public string $word;\n}\n```\n\n## Matches\n\nYou must be sure that your values match a specific pattern? You can do that for **all scalar** values by using `Matches`:\n\n```php\nuse Dgame\\DataTransferObject\\Annotation\\Matches;\n\nfinal class Foo\n{\n    use DataTransfer;\n    \n    #[Matches(pattern: '/^[a-z]+\\w*/', message: 'Your name must start with a-z')]\n    public string $name;\n    \n    #[Matches(pattern: '/[1-9][0-9]+/', message: 'products must be at least 10')]\n    public int $products;\n}\n\nFoo::from(['name' =\u003e '_', 'products' =\u003e 99]); // will throw a error, since name does not start with a-z\nFoo::from(['name' =\u003e 'John', 'products' =\u003e 9]); // will throw a error, since products must be at least 10\n```\n\n## Trim\n\nYou have to make sure, that `string` values are trimmed? No worries, we have `Trim`:\n\n```php\nuse Dgame\\DataTransferObject\\Annotation\\Trim;\n\nfinal class Foo\n{\n    use DataTransfer;\n    \n    #[Trim]\n    public string $name;\n}\n\n$foo = Foo::from(['name' =\u003e ' John   ']);\nassert($foo-\u003ename === 'John');\n```\n\n## Path\n\nDid you ever wanted to extract a value from a provided array? `Path` to the rescue:\n\n```php\nfinal class Person\n{\n    use DataTransfer;\n\n    #[Path('person.name')]\n    public string $name;\n}\n```\n\nIt helps while with JSON's special `$value` attribute\n\n```php\nfinal class Person\n{\n    use DataTransfer;\n\n    #[Path('married.$value')]\n    public bool $married;\n}\n```\n\nand with XML's `#text`.\n\n```php\nfinal class Person\n{\n    use DataTransfer;\n\n    #[Path('first.name.#text')]\n    public string $firstname;\n}\n```\n\n---\n\nBut we can do even more. You can choose which parts of the field are taken\n\n```php\nfinal class Person\n{\n    use DataTransfer;\n\n    #[Path('child.{born, age}')]\n    public array $firstChild = [];\n}\n```\n\nand can even assign them directly to an object:\n\n```php\nfinal class Person\n{\n    use DataTransfer;\n    \n    public int $id;\n    public string $name;\n    public ?int $age = null;\n\n    #[Path('ancestor.{id, name}')]\n    public ?self $parent = null;\n}\n```\n\n## SelfValidation\n\nIn addition to the [customary validations](#validation) you can specify a class-wide validation after **all** assignments are done:\n\n```php\n#[SelfValidation(method: 'validate')]\nfinal class SelfValidationStub\n{\n    use DataTransfer;\n\n    public function __construct(public int $id)\n    {\n    }\n\n    public function validate(): void\n    {\n        assert($this-\u003eid \u003e 0);\n    }\n}\n```\n\n## ValidationStrategy\n\nThe default validation strategy is **fail-fast** which means an Exception is thrown as soon as an error is detected.\nBut that might not desirable, so you can configure this with a `ValidationStrategy`:\n\n```php\n#[ValidationStrategy(failFast: false)]\nfinal class Foo\n{\n    use DataTransfer;\n\n    #[Min(3)]\n    public string $name;\n    #[Min(0)]\n    public int $id;\n}\n\nFoo::from(['name' =\u003e 'a', 'id' =\u003e -1]);\n```\n\nThe example above would throw a combined exception that `name` is not long enough and `id` must be at least 0.\nYou can configure this as well by extending the `ValidationStrategy` and provide a `FailureHandler` and/or a `FailureCollection`.\n\n# Property promotion\n\nIn the above examples, [property promotion](https://stitcher.io/blog/constructor-promotion-in-php-8) is not always used because it is more readable that way, but property promotion is supported. So the following example\n\n```php\nuse Dgame\\DataTransferObject\\Annotation\\Min;\nuse Dgame\\DataTransferObject\\DataTransfer;\n\nfinal class Limit\n{\n    use DataTransfer;\n    \n    #[Min(0)]\n    public int $offset;\n    #[Min(0)]\n    public int $limit;\n}\n```\n\ncan be rewritten as shown below\n\n```php\nuse Dgame\\DataTransferObject\\Annotation\\Min;\nuse Dgame\\DataTransferObject\\DataTransfer;\n\nfinal class Limit\n{\n    use DataTransfer;\n\n    public function __construct(\n        #[Min(0)] public int $offset,\n        #[Min(0)] public int $limit\n    ) { }\n}\n```\n\nand it still works.\n\n# Nested object detection\n\nYou have nested objects and want to deserialize them all at once? That is a given:\n\n```php\nuse Dgame\\DataTransferObject\\DataTransfer;\n\nfinal class Bar\n{\n    public int $id;\n}\n\nfinal class Foo\n{\n    use DataTransfer;\n    \n    public Bar $bar;\n}\n\n$foo = Foo::from(['bar' =\u003e ['id' =\u003e 42]]);\necho $foo-\u003ebar-\u003eid; // 42\n```\n\nHave you noticed the missing `From` in `Bar`? `From` is just a little wrapper for the actual DTO. So your nested classes don't need to use it at all.\n\nThere is no limit to the depth of nesting, the responsibility is yours! :)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdgame%2Fphp-dto","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdgame%2Fphp-dto","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdgame%2Fphp-dto/lists"}