{"id":16078294,"url":"https://github.com/sandrokeil/php-value-object-redux","last_synced_at":"2025-08-28T02:45:12.589Z","repository":{"id":74519500,"uuid":"589745508","full_name":"sandrokeil/php-value-object-redux","owner":"sandrokeil","description":"Opinionated PHP immutable value object example with deep nesting, JsonSerializable, snake_case and camelCase","archived":false,"fork":false,"pushed_at":"2023-03-29T20:10:56.000Z","size":66,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-05T10:43:06.608Z","etag":null,"topics":["immutable-objects","php","readonly","value-object","value-objects","valueobject"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sandrokeil.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-01-16T20:49:44.000Z","updated_at":"2024-07-28T21:41:28.000Z","dependencies_parsed_at":"2024-10-31T02:40:54.925Z","dependency_job_id":"a4efb88d-9e2c-4b21-b5ff-1c0467f081f8","html_url":"https://github.com/sandrokeil/php-value-object-redux","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/sandrokeil/php-value-object-redux","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sandrokeil%2Fphp-value-object-redux","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sandrokeil%2Fphp-value-object-redux/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sandrokeil%2Fphp-value-object-redux/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sandrokeil%2Fphp-value-object-redux/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sandrokeil","download_url":"https://codeload.github.com/sandrokeil/php-value-object-redux/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sandrokeil%2Fphp-value-object-redux/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":272424506,"owners_count":24932894,"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","status":"online","status_checked_at":"2025-08-28T02:00:10.768Z","response_time":74,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["immutable-objects","php","readonly","value-object","value-objects","valueobject"],"created_at":"2024-10-09T10:11:35.563Z","updated_at":"2025-08-28T02:45:12.557Z","avatar_url":"https://github.com/sandrokeil.png","language":"PHP","readme":"# PHP ^8.1 Value Objects Redux\n\nOpinionated PHP immutable value object example with deep nesting, `\\JsonSerializable`, `\\IteratorAggregate`, `snake_case` and `camelCase`. Please see `example-php`, source and tests for more details.\n\n- simple and short immutable value objects\n- plain value objects have 4 methods `fromNative(...)`, `toNative()`, `equals(...)` and `jsonSerialize()`\n- records have additional 2 methods `with(iterable|self $data)` and `getIterator()` \n- records are traversable, you can iterate over the properties\n- union types for creating value objects from scalar or same value object type\n- `init()` method example to initialize optional properties with default values\n- `snake_case` and `camelCase` keys\n\n\n**Example for a String value object:**\n\n```php\nfinal readonly class FirstName implements Immutable, \\Stringable\n{\n    use FuncTrait;\n\n    private function __construct(public readonly string $v)\n    {\n    }\n\n    public static function fromNative(string|self $firstName): self\n    {\n        return $firstName instanceof self ? $firstName : new self($firstName);\n    }\n\n    public function toNative(): string\n    {\n        return $this-\u003ejsonSerialize();\n    }\n\n    public function jsonSerialize(): string\n    {\n        return $this-\u003ev;\n    }\n\n    public function __toString(): string\n    {\n        return $this-\u003ev;\n    }\n}\n```\n\n**Example for a date value object:**\n\n```php\nfinal readonly class LastLogin implements \\Stringable, Immutable\n{\n    use FuncTrait;\n\n    private const OUTPUT_FORMAT = 'Y-m-d\\TH:i:sP';\n\n    public static function fromNative(string|int|DateTimeImmutable|self $v): self\n    {\n        switch (\\gettype($v)) {\n            case 'integer':\n                $datetime = (new DateTimeImmutable())-\u003esetTimestamp($v);\n\n                break;\n            case 'string':\n                $datetime = new DateTimeImmutable($v);\n\n                break;\n            default:\n                if ($v instanceof self) {\n                    return $v;\n                }\n                $datetime = $v;\n\n                break;\n        }\n\n        return new self(self::ensureUtc($datetime));\n    }\n\n    public function toNative(): string\n    {\n        return $this-\u003ejsonSerialize();\n    }\n\n    private function __construct(public readonly DateTimeImmutable $v)\n    {\n    }\n\n    private static function ensureUtc(DateTimeImmutable $timestamp): DateTimeImmutable\n    {\n        if ($timestamp-\u003egetTimezone()-\u003egetName() !== 'UTC') {\n            $timestamp = $timestamp-\u003esetTimezone(new \\DateTimeZone('UTC'));\n        }\n\n        return $timestamp;\n    }\n\n    public function __toString(): string\n    {\n        return $this-\u003ejsonSerialize();\n    }\n\n    public function jsonSerialize(): string\n    {\n        return $this-\u003ev-\u003eformat(self::OUTPUT_FORMAT);\n    }\n}\n```\n\n**Example for a record value object:**\n\n```php\nfinal class Account implements ImmutableRecord\n{\n    use FuncTrait;\n\n    private static array $__objectKeys;\n\n    private function __construct(\n        public readonly FirstName $firstName,\n        public readonly LastName $lastName,\n        public readonly Address $address,\n        public readonly Active $active,\n        public readonly ?Age $age = null\n    ) {\n    }\n\n    /**\n     * @param iterable{firstName : string, lastName : string, age : int|null, address : array} $data\n     */\n    public static function fromNative(iterable|self $data): static\n    {\n        return $data instanceof self ? $data : new self(...self::convertFromNative($data));\n    }\n\n    public function toNative(): array\n    {\n        return $this-\u003ejsonSerialize();\n    }\n\n    public function with(iterable|self $data): static\n    {\n        return new self(...self::convertFromNative([...\\get_object_vars($this), ...$data]));\n    }\n\n    public function getIterator(): Traversable\n    {\n        yield 'firstName' =\u003e $this-\u003efirstName;\n        yield 'lastName' =\u003e $this-\u003elastName;\n        yield 'age' =\u003e $this-\u003eage;\n        yield 'address' =\u003e $this-\u003eaddress-\u003egetIterator();\n        yield 'active' =\u003e $this-\u003eactive;\n    }\n\n    public function jsonSerialize(): array\n    {\n        return [\n            'firstName' =\u003e $this-\u003efirstName-\u003ev,\n            'lastName' =\u003e $this-\u003elastName-\u003ev,\n            'age' =\u003e $this-\u003eage?-\u003ev,\n            'address' =\u003e $this-\u003eaddress-\u003ejsonSerialize(),\n            'active' =\u003e $this-\u003eactive-\u003ev,\n        ];\n    }\n\n    private static function convertFromNative(iterable $data): iterable\n    {\n        if (! isset(self::$__objectKeys)) {\n            self::$__objectKeys = \\array_keys(\\get_class_vars(self::class));\n        }\n\n        $initialized = [];\n\n        foreach ($data as $field =\u003e $value) {\n            // order independent\n            switch ($field) {\n                case 'lastName':\n                case 'last_name':\n                    yield 'lastName' =\u003e LastName::fromNative($value);\n\n                    break;\n                case 'firstName': // camelCase\n                case 'first_name': // snakeCase\n                    yield 'firstName' =\u003e FirstName::fromNative($value);\n\n                    break;\n                case 'address': // deep nesting\n                    yield $field =\u003e Address::fromNative($value);\n\n                    break;\n                case 'age': // can be null\n                    if ($value !== null) {\n                        yield $field =\u003e Age::fromNative($value);\n                    }\n\n                    break;\n                case 'active':\n                    yield $field =\u003e Active::fromNative($value);\n\n                    break;\n                default:\n                    throw new \\InvalidArgumentException(\\sprintf(\n                        'Invalid property passed to Record %s. Got property with key ' . $field,\n                        __CLASS__\n                    ));\n            }\n            $initialized[] = $field;\n        }\n\n        yield from self::init(\\array_diff(self::$__objectKeys, $initialized));\n    }\n\n    private static function init(array $properties): \\Generator\n    {\n        foreach ($properties as $property) {\n            switch ($property) {\n                case 'active':\n                    yield $property =\u003e Active::fromNative(true);\n\n                    break;\n                default:\n                    // intentionally omitted\n                    break;\n            }\n        }\n    }\n}\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsandrokeil%2Fphp-value-object-redux","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsandrokeil%2Fphp-value-object-redux","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsandrokeil%2Fphp-value-object-redux/lists"}