Search code examples
phpserializationimmutabilitylaravel-11

Best way to get immutable objects that are also serializable


I use PHP 8.3.11 and Laravel 11.22.0, and I want to have immutable DTOs that are also serializable.

My best idea so far was this:

trait SerializePrivate
{
    public function jsonSerialize()
    {
        $vars = get_object_vars($this);
        //dd($vars);
        return $vars;
    }
}

trait ImmutableAccess
{
    public function __get($key)
    {
        return $this->$key;
    }
}


class User
{
    use SerializePrivate, ImmutableAccess;

    protected int $id;
    protected string $name;
    protected string $surname;

    //public function __construct(...) {....}
}

This seemed like a good idea but it doesn't work. For some weird reason, when serializing the User DTO, no properties get serialized.

I have tried like this:

    public function getUser()
    {
        // ...
        return response()->json($user);
    }

...and like this:

    public function getUser()
    {
        // ...
        return new UserResource($user);
    }

class UserResource extends \Illuminate\Http\Resources\Json\JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @return array<string, mixed>
     */
    public function toArray(\Illuminate\Http\Request $request): array
    {
        //dd($this->resource); // <---- This shows the properties.
        //dd(get_object_vars($this->resource)); // <---- This shows no properties.
        return get_object_vars($this->resource);
    }
}

Any idea how to fix my existing solution or use a better alternative?


Solution

  • The User class needs to implement the JsonSerializable interface, the return type also needs to be declared as mixed:

    trait SerializePrivate
    {
        public function jsonSerialize() : mixed
        {
            return get_object_vars($this);
        }
    }
    
    class User implements \JsonSerializable
    {
        use SerializePrivate, ImmutableAccess;
    
        protected int $id;
        protected string $name;
        protected string $surname;
    }
    

    You can also use the new initializer with readonly properties since PHP 8.1, or use readonly class since PHP 8.2:

    # 8.1
    class User
    {
        public function __construct(
            public readonly int $id,
            public readonly string $name,
            public readonly string $surname,
        ) {}
    }
    
    # 8.2
    readonly class User
    {
        public function __construct(
            public int $id,
            public string $name,
            public string $surname,
        ) {}
    }