Skip to content

Conversation

@acoulton
Copy link

Build on the existing implementation to allow users to specify patches as a list of operation objects, rather than as a JSON string.

Unlike the JSON form, the patch operation DTOs are strict about the parameters they accept. If a user wishes to include custom properties, they can implement a class extending the base PatchOperation.

Patch DTOs can be serialised to and from JSON, for convenience.

Completes #12

** note ** I have based this branch off the branch for #13 to avoid merge conflicts. The first 3 commits in this diff can therefore be ignored. Once #13 is merged I will rebase as required.

@acoulton acoulton force-pushed the 3.x-patch-dtos branch 3 times, most recently from ebe3f7b to a25fa9c Compare November 30, 2025 23:18
@acoulton acoulton changed the base branch from main to dev November 30, 2025 23:20
Comment on lines +219 to +220
#[DataProvider('validOperationsForDTOsProvider')]
public function testValidJsonPatchesAsDTOs(string $json, string $patches, string $expected): void
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potentially this test runs more cases than is strictly necessary to prove the DTOs are correctly read & handled.

However it seemed the most efficient way to have confidence that they support all the expected properties / types and work correctly with all handlers and to keep that in sync with any changes to the pure JSON objects over time.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm okay with your assumptions here, but I would move the tests into a dedicated class for each operation DTO. I suggest this because I think each DTO also needs a test that ensures it fails when incorrect properties are passed. This is important because the PatchOperationList::fromJson method bases its validation workflow (both for patch validation and invalid DTOs) on the assumption that the constructor throws an exception when invalid input is provided.

Adds a `Handler` suffix to all current final operation handler class names, and
moves them to a `handlers` namespace.

This is not expected to affect end-users as the in-built handler classes are not
generally expected to be used outside the package.

This will avoid naming conflicts and confusion when we subsequently introduce
support for representing a patch as a set of operation DTOs.
End-users are not expected to reference these classes directly. If users want to
customise handling for a particular operation, they should implement the interface
and/or extend from the abstract base class.
Renames all remaining classes, interfaces & methods related to patch
operation handlers to include the `Handler` suffix.

This reinstates consistency between the naming of these concerns, and
will provide the cleanest base for implementing patch operation DTOs
without naming conflicts or confusion.

It will, however, impact any end-users who have implemented & registered
custom patch handlers as they will need to update them for the new
naming as part of the 3.x upgrade.
Build on the existing implementation to allow users to specify patches
as a list of operation objects, rather than as a JSON string.

Unlike the JSON form, the patch operation DTOs are strict about the
parameters they accept. If a user wishes to include custom properties,
they can implement a class extending the base `PatchOperation`.

Patch DTOs can be serialised to and from JSON, for convenience.
Now that there are explicit operation classes for each standard
operation, it feels appropriate to define the expected schema for
unserialized operations alongside the DTO and import it to the handler.
This more clearly shows the relationship between these objects.

return ['op' => 'replace', 'path' => $patch->path, 'value' => $this->previous];
public function __construct(
public readonly string $path,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since PHP 8.1 is reaching EOL on January 1st, I'm considering bumping the minimum required PHP version to 8.2 for the 3.0 release. This would allow us to use the class-level readonly keyword.

What do you think about that?

JsonHandlerInterface $jsonHandler = new BasicJsonHandler(),
array $customClasses = [],
): self {
$patches = $jsonHandler->decode($jsonOperations, ['associative' => true]);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some JSON decoders may not support the associative array option at all because forcing objects into arrays introduces representation limitations that make it error-prone. Decoding the patch as objects instead is a more robust approach.

Comment on lines +219 to +220
#[DataProvider('validOperationsForDTOsProvider')]
public function testValidJsonPatchesAsDTOs(string $json, string $patches, string $expected): void
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm okay with your assumptions here, but I would move the tests into a dedicated class for each operation DTO. I suggest this because I think each DTO also needs a test that ensures it fails when incorrect properties are passed. This is important because the PatchOperationList::fromJson method bases its validation workflow (both for patch validation and invalid DTOs) on the assumption that the constructor throws an exception when invalid input is provided.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants