Skip to content

Conversation

@henriquemoody
Copy link
Member

For a while now, our public Stringifier interface has been leaking internal control flow. By returning string|null, we were forcing consumers to deal with a "null" state that only existed to support our internal chain of responsibility. It made the public API clunky and less type-safe than it should be.

I’ve resolved this by splitting the responsibilities into two distinct roles. The public Stringifier now provides a clean, guaranteed contract: it always returns a string. This removes the burden from the caller to handle nulls that they shouldn't have seen in the first place.

Internally, the logic now lives in the Handler interface. This is where returning null still makes perfect sense—it’s how the chain-of-responsibility negotiates which handler takes the lead. By moving existing stringifiers into the Handlers/ namespace, we’ve made a clear distinction between the implementation details and the public-facing service.

The result is a more predictable API that hides its complexity without sacrificing the flexibility of the underlying pattern.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request refactors the stringifier architecture to separate public API concerns from internal implementation details. The Stringifier interface now provides a clean contract that always returns a string, while the new Handler interface supports the internal chain-of-responsibility pattern with nullable return types.

Changes:

  • Introduced Handler interface for internal chain-of-responsibility logic that can return null
  • Modified Stringifier interface to guarantee a string return type
  • Moved and renamed all stringifier implementations from Stringifiers/ to Handlers/ namespace
  • Created HandlerStringifier and DumpStringifier as public-facing implementations

Reviewed changes

Copilot reviewed 53 out of 53 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/Stringifier.php Updated interface to return only string (removed nullable return)
src/Handler.php New interface for internal handlers with nullable return type
src/HandlerStringifier.php New public stringifier that delegates to handlers with fallback
src/DumpStringifier.php New fallback stringifier using print_r
src/Handlers/*.php All handler implementations moved from Stringifiers namespace
src/Stringify.php Removed (replaced by HandlerStringifier)
src/Stringifiers/CompositeStringifier.php Removed (replaced by CompositeHandler)
tests/unit/Handlers/*.php All tests updated to reflect Handler naming and namespace
tests/src/Double/FakeHandler.php Test double updated but has signature mismatch
tests/src/Double/LameHandler.php Test double properly updated to implement Handler
stringify.php Helper function updated to use new HandlerStringifier
README.md Documentation updated to reflect new API
phpcs.xml.dist Configuration updated for new file paths
Comments suppressed due to low confidence (1)

tests/src/Double/FakeHandler.php:21

  • The method signature doesn't match the Handler interface. The Handler interface defines the return type as "string|null", but FakeHandler returns only "string". Additionally, the depth parameter has a default value here but not in the interface. For consistency with the Handler contract, the return type should be "string|null" and the default parameter should be removed.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

For a while now, our public `Stringifier` interface has been leaking internal
control flow. By returning `string|null`, we were forcing consumers to deal with
a "null" state that only existed to support our internal chain of
responsibility. It made the public API clunky and less type-safe than it should
be.

I’ve resolved this by splitting the responsibilities into two distinct roles.
The public `Stringifier` now provides a clean, guaranteed contract: it always
returns a `string`. This removes the burden from the caller to handle nulls
that they shouldn't have seen in the first place.

Internally, the logic now lives in the `Handler` interface. This is where
returning `null` still makes perfect sense—it’s how the chain-of-responsibility
negotiates which handler takes the lead. By moving existing stringifiers into
the `Handlers/` namespace, we’ve made a clear distinction between the
implementation details and the public-facing service.

The result is a more predictable API that hides its complexity without
sacrificing the flexibility of the underlying pattern.
@henriquemoody henriquemoody merged commit c082c6f into Respect:main Jan 19, 2026
5 checks passed
@henriquemoody henriquemoody deleted the refactoring branch January 19, 2026 09:16
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.

1 participant