Skip to content

Support readonly input classes with constructors #565

Closed
@oprypkhantc

Description

@oprypkhantc

Currently, the best you can do when defining an update #[Input(update: true)] is to define regular properties without readonly:

#[Input(default: true, update: true)]
class UpdateDTO
{
	#[Field] public Layout $layout;
}

Then, if you try to

  • mark the class/property as readonly, it fails trying to set it: Cannot initialize readonly property UpdateDTO::$layout from scope TheCodingMachine\\GraphQLite\\Utils\\PropertyAccessor. This can be fixed by using reflection to set the value instead of setting it directly using $object->$field = $value;
  • mark the property as private (in case you instead want to provide a getter for it), it fails again with a similar error
  • lastly, use promoted properties (constructor hydration) - it fails again with Parameter 'layout' is missing for class 'UpdateDTO' constructor. It should be mapped as required field..

Modern deserialization (Java - Moshi, GSON) libraries default to creating an instance without constructor and then hydrating the properties rather than calling the constructor, even if it exists. This makes sense because deserialization usually requires more complex logic than just create an instance, as is the case with #[Input(update: true)].

The reason I want to use promoted properties is because those inputs classes are used as value objects throughout the project, so it makes perfect sense for them to have a regular constructor. One more thing is unit testing - with a constructor I can do this:

$service->doSomething(new UpdateDTO(layout: Layout::BASIC))

So what would sound better to me is always using field-based hydration:

#[Input(default: true, update: true)]
readonly class UpdateDTO
{
	public function __construct() {
		#[Field] public Layout $layout,
	}
}

If constructor hydration is deemed important (at the very least for compatibility with older code that could break), we can instead make it opt-in:

#[Input(default: true, update: true)]
class UpdateDTO
{
	#[Hydrate]
	public function __construct() {
		#[Field] public Layout $layout,
	}
}

Thoughts on using reflection to set the property? And the hydration?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions