Skip to content

Conversation

@koriym
Copy link
Member

@koriym koriym commented Nov 24, 2025

Summary

This PR restores method-level attribute support that was removed in commit da864a5e, but adds deprecation warnings and keeps src/ extremely clean by isolating all legacy code in src-deprecated/ with high-level helper methods.

Key distinction: LegacyAttributeHelper is marked @internal (not @deprecated) because it supports deprecated features rather than being deprecated itself.

Background

The removal of method-level attribute support in da864a5e broke backward compatibility with existing libraries like BEAR.AuraRouterModule (see bearsunday/BEAR.AuraRouterModule#31).

Architecture

This PR follows the project's convention of isolating legacy code in src-deprecated/, with minimal changes to src/:

src-deprecated/di/
└── LegacyAttributeHelper.php (@internal, not @deprecated)
    ├── High-level wrappers (1-line calls from src/)
    │   ├── getNameFromMethod()
    │   ├── parseNameWithWarning()
    │   └── convertArrayToStringWithWarning()
    └── Low-level implementation (all legacy logic)
        ├── getMethodLevelQualifiers()
        ├── parseName()
        └── getStringName()

src/di/
├── AnnotatedClassMethods.php  # 1-line calls only, no suppressions
├── Name.php                    # 1-line call only, no suppressions
└── Bind.php                    # 1-line call only, no suppressions

Why @internal not @deprecated?

  • Other files in src-deprecated/ (NullCache, Provider, EmptyModule) are @deprecated → users shouldn't use them
  • LegacyAttributeHelper is @internal → it's a support class, not deprecated itself
  • This distinction is important: the features are deprecated, not the helper class

Key Achievement: Ultra-clean src/

  • Each legacy feature = 1 line helper call
  • No trigger_error() in src/
  • No use const E_USER_DEPRECATED in src/
  • No @psalm-suppress needed (helper is not deprecated)
  • No complex legacy logic in src/

Changes

New file

  • src-deprecated/di/LegacyAttributeHelper.php
    • Marked @internal (supports deprecated features, not deprecated itself)
    • High-level wrappers for clean 1-line calls
    • All legacy implementation logic
    • All deprecation warnings centralized

Modified files (minimal changes)

  • src/di/AnnotatedClassMethods.php - 2 methods reduced to 1-line calls each
  • src/di/Name.php - Legacy string parsing = 1-line call
  • src/di/Bind.php - Array conversion = 1-line call

Code reduction in src/

- 6 trigger_error() calls → 0 (moved to helper)
- 3 use const E_USER_DEPRECATED → 0 (moved to helper)
- 6 @psalm-suppress annotations → 0 (helper is not deprecated)
- ~80 lines of legacy logic → 3 lines of helper calls

Example: AnnotatedClassMethods before/after

Before (complex):

$names = $this->getMethodLevelQualifiers($method);
if ($names !== []) {
    return new Name($names);
}
return new Name(Name::ANY);

After (1 clean line):

return LegacyAttributeHelper::getNameFromMethod($method) ?? new Name(Name::ANY);

Migration Path

Old (deprecated) ❌

#[Named("var1=name1,var2=name2")]
public function __construct($var1, $var2) { }

New (recommended) ✅

public function __construct(
    #[Named('name1')] $var1,
    #[Named('name2')] $var2
) { }

Test Results

  • ✅ All 168 tests pass
  • ✅ 13 deprecation warnings (expected and intentional)
  • ✅ Psalm passes without errors or baseline
  • ✅ PHPStan passes (memory limit issue is pre-existing)
  • ✅ Coding standards pass

Impact

  • Ultra-clean src/: Only 3 lines of helper calls total, no suppressions
  • Backward compatibility: Existing code continues to work
  • Clean architecture: All legacy code isolated in src-deprecated/
  • Migration guidance: Deprecation warnings help users transition
  • No breaking changes: Can be released as a minor version update

Related

  • Fixes compatibility issues reported in BEAR.AuraRouterModule PR fix issue koriym/Ray.Di#30 #31
  • Reverts commit da864a5 while adding proper deprecation path
  • Follows project convention of using src-deprecated/ for legacy code
  • Achieves minimal src/ footprint through high-level helper methods
  • Uses @internal (not @deprecated) to distinguish support code from deprecated code

🤖 Generated with Claude Code

@sourcery-ai
Copy link

sourcery-ai bot commented Nov 24, 2025

Reviewer's Guide

Restores deprecated method-level qualifier and string-based name mapping support for backward compatibility while steering users toward parameter-level attributes, and updates bindings, factories, and tests to exercise the legacy paths and new deprecation behavior.

Sequence diagram for resolving constructor names with deprecated method-level attributes

sequenceDiagram
  actor "Client" as Client
  participant "AnnotatedClassMethods" as ACM
  participant "ReflectionClass" as RClass
  participant "ReflectionMethod" as RMethod
  participant "Name" as Name

  "Client"->>"ACM": "getConstructorName(RClass)"
  "ACM"->>"RClass": "getName() for \"__construct\""
  "RClass"-->>"ACM": "class name for constructor"
  "ACM"->>"ReflectionMethod": "new ReflectionMethod(className, \"__construct\")"
  "ACM"->>"Name": "withAttributes(RMethod) for parameter-level attributes"
  alt "parameter-level attributes found"
    "Name"-->>"ACM": "\"Name\" instance with parameter mappings"
    "ACM"-->>"Client": "return \"Name\" from parameter-level attributes"
  else "no parameter-level attributes"
    "ACM"->>"ACM": "getMethodLevelQualifiers(RMethod)" 
    alt "method-level qualifiers or \"#[$Named]\" found (deprecated)"
      "ACM"-->>"Client": "return new \"Name\"(legacyMapping) with deprecation warning (E_USER_DEPRECATED)"
    else "no method-level qualifiers"
      "ACM"-->>"Client": "return new \"Name\"(ANY) as default qualifier"
    end
  end
Loading

Updated class diagram for DI naming and binding with deprecated legacy support

classDiagram

class AnnotatedClassMethods {
  +"getConstructorName(ReflectionClass $class): Name"
  +"getSetterMethod(ReflectionMethod $method): ?SetterMethod"
  -"getName(ReflectionMethod $method): Name"
  -"getMethodLevelQualifiers(ReflectionMethod $method): array<string,string> (deprecated)"
}

class Name {
  -"string $name"
  -"array<string,string> $names"
  +"__construct(string|array|null $name)"
  -"setName(string $name): void"
  -"parseName(string $name): array<string,string> (deprecated)"
}

class Bind {
  +"toConstructor(string $class, string|array $name, ?InjectionPoints $injectionPoints = null, ?string $postConstruct = null): self"
  -"getStringName(array $name): string (deprecated)"
}

class DependencyFactory {
  +"newToConstructor(ReflectionClass $class, string $name, ?InjectionPoints $injectionPoints = null, ?ReflectionMethod $postConstruct = null): Dependency"
}

class Named {
}

class Qualifier {
}

AnnotatedClassMethods --> Name : "returns \"Name\" objects based on attributes"
AnnotatedClassMethods ..> Named : "reads parameter and method-level \"#[$Named]\" attributes"
AnnotatedClassMethods ..> Qualifier : "detects qualifier attributes on method annotations"
Bind --> DependencyFactory : "uses \"newToConstructor(...)\" to build dependencies"
Bind --> Name : "provides constructor name mapping as string (legacy)"
Name ..> Named : "supports mapping for \"#[$Named]\" injection"
Bind ..> InvalidToConstructorNameParameter : "throws on non-string array keys in \"getStringName()\""
Loading

Flow diagram for Bind::toConstructor handling of deprecated array name mapping

flowchart TD
  START(["Start &quot;Bind::toConstructor()&quot;"])
  CHECK_TYPE{"Is &quot;$name&quot; an array?"}
  DEPRECATED_PATH["Trigger E_USER_DEPRECATED: &quot;Array parameter to toConstructor() is deprecated. Use Name class constructor with array directly.&quot;"]
  CONVERT_STRING["Call deprecated &quot;getStringName(array $name)&quot; to build legacy string &quot;var=name&quot; mapping"]
  LEGACY_NAME["Set &quot;$name&quot; to returned legacy string (e.g. &quot;varA=nameA,varB=nameB&quot;)"]
  REFLECT_POST{"Is &quot;$postConstruct&quot; non-null?"}
  BUILD_POST_REF["Create &quot;ReflectionMethod&quot; for post-construct method"]
  SKIP_POST_REF["No post-construct reflection created"]
  CREATE_REF_CLASS["Create &quot;ReflectionClass&quot; for target constructor class"]
  CALL_FACTORY["Call &quot;DependencyFactory::newToConstructor(ReflectionClass, string $name, ?InjectionPoints, ?ReflectionMethod)&quot;"]
  END(["Return updated &quot;Bind&quot; instance with constructor dependency"])

  START --> CHECK_TYPE
  CHECK_TYPE -- "yes" --> DEPRECATED_PATH
  DEPRECATED_PATH --> CONVERT_STRING
  CONVERT_STRING --> LEGACY_NAME
  CHECK_TYPE -- "no" --> REFLECT_POST
  LEGACY_NAME --> REFLECT_POST
  REFLECT_POST -- "yes" --> BUILD_POST_REF
  REFLECT_POST -- "no" --> SKIP_POST_REF
  BUILD_POST_REF --> CREATE_REF_CLASS
  SKIP_POST_REF --> CREATE_REF_CLASS
  CREATE_REF_CLASS --> CALL_FACTORY
  CALL_FACTORY --> END
Loading

File-Level Changes

Change Details Files
Restore deprecated method-level qualifier attribute handling while preferring parameter-level attributes.
  • In constructor and setter resolution, first attempt parameter-level attribute resolution and only fall back to method-level qualifiers if no parameter-level names are found
  • Introduce a deprecated helper that scans method-level attributes for Qualifier or Named annotations, emitting E_USER_DEPRECATED warnings when used
  • Map qualifier attributes either to a specific parameter via their value property or to all parameters when no value is present, and parse legacy Named strings of the form var1=name1,var2=name2 into per-parameter names
src/di/AnnotatedClassMethods.php
Reintroduce and deprecate string-based parameter-name mapping in Name while keeping array and single-name support.
  • Relax Name constructor to accept null and to early-return when no name is provided
  • Extend setName to distinguish simple single names from legacy mapping strings, emitting deprecation warnings and delegating parsing for the latter
  • Add a pure, deprecated parser that converts legacy "var=name" comma-separated strings into a ParameterNameMapping array, trimming optional leading '$' and whitespace
  • Add tests that cover key/value string mappings, mixed order and spacing, and non-matching mappings falling back to Name::ANY
src/di/Name.php
tests/di/NameTest.php
Add deprecated array-name handling for Bind::toConstructor and support conversion to legacy string format with validation.
  • Detect array $name arguments in toConstructor, emit a deprecation warning, and convert them to a legacy string representation before building the Dependency
  • Implement a deprecated helper that converts a ParameterNameMapping array into a comma-separated "var=name" string, throwing InvalidToConstructorNameParameter when keys are not strings
  • Tighten DependencyFactory::newToConstructor signature to accept only string names, reflecting the preferred usage
src/di/Bind.php
src/di/DependencyFactory.php
Adjust fake classes and tests to exercise method-level qualifiers and constructor name mappings.
  • Change several test fakes to apply Named and Qualifier attributes at the method level instead of the parameter level so that restored method-level processing is exercised
  • Narrow some Qualifier attribute targets (e.g., FakeConstant) to method/class only to ensure parameter-level usage is routed through legacy paths where intended
  • Update Bind and injector-related tests to pass both array and legacy string name formats via data providers and ensure invalid nested arrays cause InvalidToConstructorNameParameter exceptions
  • Update FakePdoModule and related tests to use the string-based constructor name mapping, covering the deprecation path while maintaining BC
tests/di/Fake/FakeInternalTypes.php
tests/di/Fake/FakeConstantConsumer.php
tests/di/Fake/FakeHandleBar.php
tests/di/Fake/FakePhp8Car.php
tests/di/Fake/FakeHandleProvider.php
tests/di/Fake/FakeInjectionPoint.php
tests/di/Fake/Annotation/FakeInjectOne.php
tests/di/Fake/FakeConstant.php
tests/di/Fake/FakePdoModule.php
tests/di/InjectorTest.php
tests/di/BindTest.php

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link

coderabbitai bot commented Nov 24, 2025

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@koriym koriym force-pushed the restore-method-level-attributes-with-deprecation branch 2 times, most recently from bef962e to 2d9319f Compare November 24, 2025 02:52
This commit restores method-level attribute support while keeping src/ extremely clean by isolating all legacy logic in src-deprecated/ and providing high-level helper methods.

Architecture:
- Created LegacyAttributeHelper in src-deprecated/di/ with:
  - High-level wrappers (getNameFromMethod, parseNameWithWarning, etc.)
  - Low-level implementation (getMethodLevelQualifiers, parseName, getStringName)
  - All deprecation warnings centralized here
  - @internal annotation (not @deprecated - this class supports deprecated features)
- Minimal changes to src/di/ classes:
  - AnnotatedClassMethods: 1 line call per method
  - Name: 1 line call for legacy string parsing
  - Bind: 1 line call for array conversion
  - No @psalm-suppress needed (helper is not deprecated)

Code reduction in src/:
- Removed all trigger_error() calls (moved to helper)
- Removed all use const E_USER_DEPRECATED (moved to helper)
- Removed all @psalm-suppress DeprecatedClass (helper is not deprecated)
- Removed all complex legacy logic (moved to helper)
- Each legacy support = 1 clean line helper call

Changes:
- Added LegacyAttributeHelper class in src-deprecated/di/ (marked @internal, not @deprecated)
- AnnotatedClassMethods: 2 methods simplified to 1-line calls
- Name: Legacy string parsing reduced to 1-line call
- Bind: Array conversion reduced to 1-line call

Impact:
- src/ directory extremely clean (1 line per legacy feature, no suppressions)
- All legacy implementation in src-deprecated/
- Existing code using method-level attributes continues to work
- Deprecation warnings guide users toward parameter-level attributes
- All 168 tests pass with 13 expected deprecation warnings
- No baseline files required

Migration path for users:
- Old (deprecated): #[Named("var1=name1,var2=name2")] on method
- New (recommended): #[Named('name1')] on each parameter

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@koriym koriym force-pushed the restore-method-level-attributes-with-deprecation branch from 2d9319f to 03a07cf Compare November 24, 2025 03:03
@sonarqubecloud
Copy link

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