Skip to content

Integrate Respect/Fluent and Respect/FluentGen#1729

Merged
alganet merged 1 commit into
Respect:mainfrom
alganet:fluent-integration
Jun 25, 2026
Merged

Integrate Respect/Fluent and Respect/FluentGen#1729
alganet merged 1 commit into
Respect:mainfrom
alganet:fluent-integration

Conversation

@alganet

@alganet alganet commented Mar 23, 2026

Copy link
Copy Markdown
Member

Replace the in-house factory and codegen infrastructure with Fluent and FluentGen packages. The runtime now uses ComposingLookup with ComposableMap for prefix resolution via a FluentValidatorFactory adapter that preserves exception types. Validators use #[Composable] from Fluent instead of the custom #[Mixin] attribute. Mixin generation uses FluentGen, producing PrefixConstants in place of PrefixMap. The old src-dev/CodeGen directory is removed. All public APIs and BC are preserved.


This PR is currently molded as a 3.x release, but we could push the integration further and remove more code by integrating deeper with Respect\Fluent. However, that breaks BC, and it would be a 4.x release instead.

That BC break would be kind of "soft", in the sense that validators keep working. It would only affect really advanced users that have their own ValidatorFactory implementations. This is related to #1710 somehow: if we had automated backports, a 4.x release would make more sense (easier to keep both 3.x and this 4.x iteration, since the bulk of validators don't change, just some wiring on fluent stuff).


Integration with Respect/FluentAnalysis is not yet in this PR, but its use should be possible with it with some boilerplate configuration.

Those three together unlock reusable, declarative advanced fluent composition, type inference and IDE support with extensibility. For example, Validation extensions with custom namespaces could ship #[Composable] annotations and end users would get IDE support for custom and extended rules too (requires some wiring on composer scripts).


This PR introduces two new projects. I also welcome reviews on them as issues on their specific repositories, linked to this PR number so we can track them as a group.

@codecov

codecov Bot commented Mar 23, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.26%. Comparing base (e30d333) to head (22a357c).

Additional details and impacted files
@@             Coverage Diff              @@
##               main    #1729      +/-   ##
============================================
- Coverage     99.42%   99.26%   -0.16%     
- Complexity     1034     1040       +6     
============================================
  Files           195      196       +1     
  Lines          2416     2438      +22     
============================================
+ Hits           2402     2420      +18     
- Misses           14       18       +4     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR migrates Respect/Validation’s fluent rule resolution and mixin/codegen infrastructure from the in-house implementation to the external respect/fluent runtime and respect/fluentgen generator, while keeping the validation rule surface largely the same.

Changes:

  • Introduces FluentValidatorFactory and wires it into ContainerRegistry using ComposingLookup + NamespaceLookup + ComposableMap for prefix-aware rule resolution.
  • Replaces the custom #[Mixin] attribute usages on validators with #[Composable] (from respect/fluent) and switches generated prefix metadata from PrefixMap to PrefixConstants.
  • Removes the legacy src-dev/CodeGen implementation and updates the mixin lint command to use respect/fluentgen; updates composer dependencies accordingly.

Reviewed changes

Copilot reviewed 63 out of 64 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
tests/unit/FluentValidatorFactoryTest.php Adds unit coverage for the new Fluent-based validator factory behavior.
src/FluentValidatorFactory.php New Fluent-backed ValidatorFactory adapter mapping Fluent exceptions to existing Validation exceptions.
src/ContainerRegistry.php Switches default DI wiring to the Fluent-based factory and ComposableMap prefix resolution.
src/Transformers/Prefix.php Updates prefix metadata reference from PrefixMap to PrefixConstants.
src/Mixins/PrefixMap.php Removes the previous generated prefix metadata class.
src/Mixins/PrefixConstants.php Adds new generated prefix metadata constants (replacing PrefixMap).
src/Mixins/Builder.php Updates generated fluent builder signature union ordering (`int
src/Mixins/Chain.php Updates generated fluent chain signature union ordering (`int
src/Mixins/NotBuilder.php Updates generated fluent builder signature union ordering (`int
src/Mixins/NotChain.php Updates generated fluent chain signature union ordering (`int
src/Mixins/NullOrBuilder.php Updates generated fluent builder signature union ordering (`int
src/Mixins/NullOrChain.php Updates generated fluent chain signature union ordering (`int
src/Mixins/UndefOrBuilder.php Updates generated fluent builder signature union ordering (`int
src/Mixins/UndefOrChain.php Updates generated fluent chain signature union ordering (`int
src/Validators/All.php Replaces custom #[Mixin] with Fluent #[Composable].
src/Validators/Attributes.php Replaces custom #[Mixin] with Fluent #[Composable].
src/Validators/Between.php Replaces custom #[Mixin] with Fluent #[Composable].
src/Validators/BetweenExclusive.php Replaces custom #[Mixin] with Fluent #[Composable].
src/Validators/Blank.php Replaces custom #[Mixin] with Fluent #[Composable].
src/Validators/Equals.php Replaces custom #[Mixin] with Fluent #[Composable].
src/Validators/Equivalent.php Replaces custom #[Mixin] with Fluent #[Composable].
src/Validators/Even.php Replaces custom #[Mixin] with Fluent #[Composable].
src/Validators/Exists.php Replaces custom #[Mixin] with Fluent #[Composable].
src/Validators/Factor.php Replaces custom #[Mixin] with Fluent #[Composable].
src/Validators/Finite.php Replaces custom #[Mixin] with Fluent #[Composable].
src/Validators/Formatted.php Replaces custom #[Mixin] with Fluent #[Composable].
src/Validators/GreaterThan.php Replaces custom #[Mixin] with Fluent #[Composable].
src/Validators/GreaterThanOrEqual.php Replaces custom #[Mixin] with Fluent #[Composable].
src/Validators/Identical.php Replaces custom #[Mixin] with Fluent #[Composable].
src/Validators/In.php Replaces custom #[Mixin] with Fluent #[Composable].
src/Validators/Infinite.php Replaces custom #[Mixin] with Fluent #[Composable].
src/Validators/Key.php Replaces custom #[Mixin] with Fluent #[Composable].
src/Validators/KeyExists.php Replaces custom #[Mixin] with Fluent #[Composable].
src/Validators/KeyOptional.php Replaces custom #[Mixin] with Fluent #[Composable].
src/Validators/KeySet.php Replaces custom #[Mixin] with Fluent #[Composable].
src/Validators/Length.php Replaces custom #[Mixin] with Fluent #[Composable] and updates opt-in semantics.
src/Validators/LessThan.php Replaces custom #[Mixin] with Fluent #[Composable].
src/Validators/LessThanOrEqual.php Replaces custom #[Mixin] with Fluent #[Composable].
src/Validators/Max.php Replaces custom #[Mixin] with Fluent #[Composable] and updates opt-in semantics.
src/Validators/Min.php Replaces custom #[Mixin] with Fluent #[Composable] and updates opt-in semantics.
src/Validators/Multiple.php Replaces custom #[Mixin] with Fluent #[Composable].
src/Validators/Named.php Replaces custom #[Mixin] with Fluent #[Composable].
src/Validators/Not.php Replaces custom #[Mixin] with Fluent #[Composable].
src/Validators/NullOr.php Replaces custom #[Mixin] with Fluent #[Composable].
src/Validators/Odd.php Replaces custom #[Mixin] with Fluent #[Composable].
src/Validators/Positive.php Replaces custom #[Mixin] with Fluent #[Composable].
src/Validators/Property.php Replaces custom #[Mixin] with Fluent #[Composable].
src/Validators/PropertyExists.php Replaces custom #[Mixin] with Fluent #[Composable].
src/Validators/PropertyOptional.php Replaces custom #[Mixin] with Fluent #[Composable].
src/Validators/Templated.php Replaces custom #[Mixin] with Fluent #[Composable].
src/Validators/Undef.php Replaces custom #[Mixin] with Fluent #[Composable].
src/Validators/UndefOr.php Replaces custom #[Mixin] with Fluent #[Composable].
src-dev/Commands/LintMixinCommand.php Switches mixin/prefix constant generation linting from in-house codegen to respect/fluentgen.
src-dev/CodeGen/CodeGenerator.php Removes legacy in-house codegen API.
src-dev/CodeGen/Config.php Removes legacy in-house codegen config.
src-dev/CodeGen/InterfaceConfig.php Removes legacy in-house interface config.
src-dev/CodeGen/NamespaceScanner.php Removes legacy in-house namespace scanner.
src-dev/CodeGen/OutputFormatter.php Removes legacy in-house output formatter.
src-dev/CodeGen/FluentBuilder/MethodBuilder.php Removes legacy in-house fluent method builder.
src-dev/CodeGen/FluentBuilder/Mixin.php Removes legacy in-house #[Mixin] attribute.
src-dev/CodeGen/FluentBuilder/MixinGenerator.php Removes legacy in-house mixin generator.
src-dev/CodeGen/FluentBuilder/PrefixMapGenerator.php Removes legacy in-house prefix map generator.
composer.json Adds respect/fluent and respect/fluentgen dependencies.
composer.lock Locks new Fluent/FluentGen dependencies and updates other dependency versions accordingly.

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

Comment thread src-dev/Commands/LintMixinCommand.php
Comment thread composer.json
Comment thread src/ContainerRegistry.php Outdated
Comment thread src/FluentValidatorFactory.php Outdated
Comment thread src/FluentValidatorFactory.php
Comment thread src/FluentValidatorFactory.php Outdated
Comment thread src/Mixins/PrefixConstants.php
Comment thread src/ContainerRegistry.php Outdated
@alganet alganet force-pushed the fluent-integration branch 3 times, most recently from b2cc661 to d8cbcf8 Compare March 23, 2026 22:43
@alganet

alganet commented Mar 23, 2026

Copy link
Copy Markdown
Member Author

@TheRespectPanda benchmark

@TheRespectPanda

Copy link
Copy Markdown
Contributor

Triggered phpbench benchmarks for PR #1729 — workflow run: https://github.com/Respect/Validation/actions/workflows/ci-perf.yml

@alganet alganet force-pushed the fluent-integration branch from d8cbcf8 to 001b6b4 Compare March 23, 2026 22:51
@alganet alganet marked this pull request as ready for review March 23, 2026 22:51
@alganet alganet requested a review from henriquemoody March 23, 2026 22:52
@alganet

alganet commented Mar 25, 2026

Copy link
Copy Markdown
Member Author

Some updates and issues I see with the current design:

  • Composable could reuse class-string entries instead of plain string.
  • Composable parameter targetting would be better off if we split the annotation into a TARGET_CLASS attribute and a TARGET_PARAMETER attribute.
  • FluentAssertion is likely better to be AssuranceAssertion (following an upcoming FluentAnalysis version), and to be targetted on individual methods instead of TARGET_CLASS.

Basically, removing all references we have for parameters and methods that are stored in strings like 'methodName' or 'paramName'. Illustrative example:

// Before:
#[Composable(prefixParameter: 'foo')]
class Instance {
    public function __construct(private string $foo) {}
}

// After:
#[Composable]
class Instance {
    public function __construct(
        #[ComposableParameter] private string $foo,
    ) {}
}

For future type narrowing (as described in #1681), I just can't quite get a good design for the type algebra parameters. It's likely they will be strings with phpdoc union types that are parsed by the extension.

@alganet alganet force-pushed the fluent-integration branch 2 times, most recently from db0f2bb to 64c1262 Compare March 25, 2026 06:12
@alganet

alganet commented Mar 25, 2026

Copy link
Copy Markdown
Member Author

I addressed the concerns in my previous comment. Much cleaner #[Composable(with: [Length::class, Max::class, Min::class])] style attributes, more modular attributes (ValidationBuilder methods now addresses by #[AssuranceAssertion] directly.

I also went full static analysis on a separate PR #1730 that takes this approach even further and it in draft. The line count looks bigger, but it's mostly the fluent.neon phpstan configuration file that has all the type inference and narrow information compiled (2KLOC) (I was able to reduce it actually). That PR also breaks BC (also migrates ValidationBuilder, requires no factory), while this one does not.

@alganet

alganet commented Mar 26, 2026

Copy link
Copy Markdown
Member Author

I was able to integrate more of my local changes into #1730.

I'm still not satisfied with some things, like:

#[Assurance(type: 'array|Countable')]
final class Countable extends Simple

I can't find a good way of expressing those union types for phpstan narrowing.

The obvious:

#[Assurance(type: ['array', Countable::class])]
final class Countable extends Simple

... is not so good future-proofing for more complex expressions. We can't assume it will always be unions.

@henriquemoody henriquemoody left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is absolutely awesome!

I haven't read the code from Fluent and FluentGen yet, but it seems really great!

Comment thread src/Mixins/Builder.php
Comment thread src/ContainerRegistry.php Outdated
Comment thread src/ValidatorBuilder.php Outdated
@alganet alganet force-pushed the fluent-integration branch from 64c1262 to 5c982d2 Compare March 31, 2026 20:44
@alganet

alganet commented Apr 5, 2026

Copy link
Copy Markdown
Member Author

I decided I'll work more on Fluent, FluentGen and FluentAnalysis and first integrate them into Data+Relational ($mapper->table auto-complete, syntax analysis), Rest (routine composition, static analysis that potentially understand basic HTTP semantics) and Config (container auto-complete).

Those projects are in incubation mode and I can iterate much faster on them, which presents more dogfooding opportunities. Once I'm happy with the abstractions there, I'll come back to Validation.

@alganet alganet marked this pull request as draft April 5, 2026 03:52
@henriquemoody

Copy link
Copy Markdown
Member

Hey, @alganet ! Do you have any plans of getting back to this? I'm considering upgrading Assertion and this would be incredibly useful!

@alganet

alganet commented Jun 12, 2026

Copy link
Copy Markdown
Member Author

@henriquemoody I do have plans for it!

Is this the PR you actually need? (this replaces stub generation, mostly).

The one with narrowing is #1730, related to #1681. That one needs more work (and minor releases of Fluent and FluentAnalysis).

We can chose either one or the other, #1730 obsoletes this one, but it's also larger and less incremental. I'm fine with either sequence (#1729 first, then a revised #1730; or #1730 directly).

Let me know, and I'll update a new amend for review in the one you actually need right now.

@henriquemoody

Copy link
Copy Markdown
Member

In fact, #1730 is really what I need, even though I suspect there would be a some work to make it work with assertion.

My goal is allowing this:

Assertion::intType($var);

echo substr('String', $var);

not to fail because $var is not an integer, but also allow IDEs to identify those as much possible, which probably means reading the attributes and translating them into docblocks with @phpstan-assert.

No rush, though, it might take some time for me to get deep into that.

@alganet

alganet commented Jun 12, 2026

Copy link
Copy Markdown
Member Author

The way FluentAnalysis does is without @phpstan-assert though. It registers an extension to do things that docblocks cannot cleanly do (which happen a lot on Validation). It works only for phpstan for now, I have not tested it in IDEs (and I suspect it doesn't work).

The division is:

  • Fluent: core reusable DSL runtimes ("make your own chain like Validation") and chain-enriching annotations.
  • FluentGen: generates stubs. This one works with IDEs (which are often stub based). It does what you said (read attributes and generate shit).
  • FluentAnalysis: static analysis extensions, for phpstan (now), maybe psalm later (haven't investigated it yet).

If we want stubs with @phpstan-assert and @phpstan-assert-if-true (which very likely can coexist with extensions but less rich), FluentGen would be the place to do it.

Respect\Assertion would probably need nothing for case 1 (the substr not failing phpstan) if the extension works as intended. The information is ingested by phpstan in runtime and it knows Assertion is an adapter for Validation (which carries semantics of the types). I haven't tested case 2 (the IDE itself knowing this deeply).

@alganet alganet force-pushed the fluent-integration branch from 5c982d2 to 0eaddb8 Compare June 12, 2026 00:47
@alganet alganet force-pushed the fluent-integration branch from 0eaddb8 to d4f238a Compare June 25, 2026 06:37
@alganet alganet force-pushed the fluent-integration branch from d4f238a to 6f864a8 Compare June 25, 2026 06:41
@alganet alganet marked this pull request as ready for review June 25, 2026 06:42
@alganet alganet requested a review from Copilot June 25, 2026 06:43

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 64 out of 65 changed files in this pull request and generated 2 comments.

Comment thread tests/unit/FluentValidatorFactoryTest.php
Comment thread src-dev/Commands/LintMixinCommand.php
Replace the in-house factory and codegen infrastructure with Fluent and
FluentGen packages. The runtime now uses ComposingLookup with ComposableMap
for prefix resolution via a FluentValidatorFactory adapter that preserves
exception types. Validators use #[Composable] from Fluent instead of the
custom #[Mixin] attribute. Mixin generation uses FluentGen, producing
PrefixConstants in place of PrefixMap. The old src-dev/CodeGen directory is
removed. All public APIs and BC are preserved.
@alganet alganet force-pushed the fluent-integration branch from 6f864a8 to 22a357c Compare June 25, 2026 07:01
@alganet alganet requested a review from henriquemoody June 25, 2026 07:03

@henriquemoody henriquemoody left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is really awesome!

I have just a few questions, but it looks good. Another question of mine is whether you've benchmarked those changes. Have you seen any significant differences?

Comment thread src-dev/Commands/LintMixinCommand.php
Comment thread src/ContainerRegistry.php
@henriquemoody

Copy link
Copy Markdown
Member

Another thought that occurred to me is how we'll integrate Respect\Parameters with the FluentValidatorFactory. Have you thought about that already?

@alganet

alganet commented Jun 25, 2026

Copy link
Copy Markdown
Member Author

@henriquemoody I'm working on the container cleanup, that will come soon.

@alganet

alganet commented Jun 25, 2026

Copy link
Copy Markdown
Member Author

@TheRespectPanda benchmark

@TheRespectPanda

Copy link
Copy Markdown
Contributor

Triggered phpbench benchmarks for PR #1729 — workflow run: https://github.com/Respect/Validation/actions/workflows/ci-perf.yml

@alganet

alganet commented Jun 25, 2026

Copy link
Copy Markdown
Member Author

@henriquemoody benchmarks will be here

https://github.com/Respect/Validation/actions/runs/28156799528/job/83387267406

CompositeValidatorsBench is the most relevant here. There seems to be a cost, but I don't know if it's a stale baseline or not (I remember no regression, so I need to double check, and I will before merging).

@henriquemoody

Copy link
Copy Markdown
Member

I'm working on the container cleanup, that will come soon.

Sounds good to me! I think we should hold version 3.2 until we finish that stuff, so we don't generate BC breaks, do you agree?

@alganet

alganet commented Jun 25, 2026

Copy link
Copy Markdown
Member Author

@henriquemoody what BC break are we talking about? So much stuff lately that I'm lost 😅

Either way, probably yes (lots of new features, this is 3.2 or 4.0)

@henriquemoody

Copy link
Copy Markdown
Member

I was talking about the FluentValidatorFactory. We'd probably need to inject the container to it, or the resolver. It might be an optional argument, so not to break the BC, but if we can just sort this out before releasing it will be easier for us.

No API breaks in main so far.

@alganet

alganet commented Jun 25, 2026

Copy link
Copy Markdown
Member Author

@henriquemoody for the Parameter thing, you're talking about?

We can implement a local FluentFactory that receives the parameter resolver. Then, we pass it to the FluentValidatorFactory instead of the ComposingLookup.

It's a composite pattern, these lookup classes can be nested and arranged and a user of the Fluent project can design their own. This way, we can keep Fluent without needing to depend on a dead-weight respect/parameter (or later move the auto-wiring lookup to Fluent with an optional dependency if we discover the design is reusable).

@alganet

alganet commented Jun 25, 2026

Copy link
Copy Markdown
Member Author

I ran a rebaseline through manual workflow invokation, then, I ran a benchmark again.

There are regressions in the composite validator timings, likely due to the indirection of going through one more class. They're more than I would like, but manageable.

I will be investigating them, but likely merge as it is. It's probable that the parameter work will add even more delay if my hypothesis is correct (if it is, indirection in the builder is bad and I'll work towards perf optimizing it).

@alganet alganet merged commit 42f08d7 into Respect:main Jun 25, 2026
7 checks passed
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.

4 participants