Ray.Di icon indicating copy to clipboard operation
Ray.Di copied to clipboard

Add NoHint exception and improve Unbound error messages

Open koriym opened this issue 4 months ago • 6 comments

Motivation / モチベーション

従来のUnbound例外メッセージには以下の問題がありました:

  1. 型もなく名前もない場合のエラーが不明瞭

    • dependency '' with name '' という無意味なメッセージ
    • 「束縛がない」ではなく「識別できない」という本質が伝わらない
  2. メッセージ形式が冗長

    • dependency 'FooInterface' with name '' used in file.php:10 ($foo) は長い
    • 末尾ハイフン FooInterface- がバグっぽく見える
  3. 依存チェーンの可読性

    • クラス名が左揃えでないとスキャンしにくい
    • ファイルパスが可変長だと視線移動が多くなる

Design Decisions / 設計判断

NoHint例外の追加

型ヒントも#[Named]#[Qualifier]もない場合はUnboundとは本質的に異なる:

  • Unbound: 識別子はあるが束縛がない
  • NoHint: そもそも識別子がない(Ray.Diが解決しようがない)

セマンティック例外としてクラス名で意味を伝える。

バインディングキー形式の採用

内部のバインディングキー '{type}-{name}' をそのまま使用:

  • 一貫性がある
  • DIの「型-名前」という概念が明確
  • (name: db) のような括弧は補助情報だけになり不自然

メッセージ形式

クラス名を左端に固定し、依存グラフの構造を一目で把握できるように:

- 'EInterface-' in D.php:7 ($e)
- 'D-' in C.php:7 ($d)
- 'C-' in B.php:7 ($c)
- 'B-' in A.php:7 ($b)

D.php:7の$eEInterface-Unbound

それは..(下から) Aが B- を必要として Bが C- を必要として Cが D- を必要として

Dが EInterface-を必要としてそれが解決できなかったから!

$injector->getInstance(A::class);

とAを取得しようとして出たエラーでけど真の問題はAではなく、Eの問題だからEの例外として報告しています。

Summary / 変更内容

  • Add NoHint exception for parameters without type hint or binding name
  • Improve Unbound exception message format using binding key notation
  • Update demo chain-error files with distinct variable names

Message Formats

Unbound: '{type}-{name}' in file:line ($var)

  • 'FooInterface-' for type only
  • 'FooInterface-db' for type with name
  • '-db' for name only

NoHint: $var (file:line)

Test Plan

  • [x] All existing tests pass
  • [x] New NoHintTest added
  • [x] Static analysis passes

Summary by CodeRabbit

  • New Features

    • Added a NoHint exception to distinguish DI errors for parameters without type hints or binding names.
  • Bug Fixes

    • Improved error handling and diagnostic messages for unbound injection targets; clearer messages in edge cases.
    • Adjusted constructor parameter naming across demo classes (no behavioral change).
  • Documentation

    • Added docblock describing Unbound exception message formats.
  • Tests

    • Added tests and supporting test fixtures to cover NoHint behavior.

✏️ Tip: You can customize this high-level summary in your review settings.

koriym avatar Dec 13 '25 09:12 koriym

[!NOTE]

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Parameter names in demo classes were renamed. A new NoHint exception was added and used when a parameter has neither a type hint nor a binding name; container/arguments code now detects that case and throws NoHint with a constructed message. Argument meta string formatting was adjusted. New tests and fixtures were added.

Changes

Cohort / File(s) Summary
Demo parameter renaming
demo/chain-error/A.php, demo/chain-error/B.php, demo/chain-error/C.php, demo/chain-error/D.php
Constructors renamed parameter names from generic $dep to $b, $c, $d, $e respectively; type hints unchanged
New NoHint exception
src/di/Exception/NoHint.php
New final NoHint class extending Unbound to represent parameters with neither a type hint nor a binding name
Exception documentation
src/di/Exception/Unbound.php
Added PHPDoc describing purpose and message format of Unbound
DI resolution changes
src/di/Container.php, src/di/Arguments.php
When index is empty (no class/name), NoHint is returned/thrown; Arguments::getParameter rethrows Unbound as NoHint for NoHint cases and a helper builds the NoHint message
Argument metadata formatting
src/di/Argument.php
Meta sprintf format changed from "dependency '%s' with name '%s' used in %s:%d ($%s)" to "'%s-%s' in %s:%d ($%s)"
Test fixtures
tests/di/Fake/FakeUnNamedClass.php, tests/di/Fake/FakeUnNamedModule.php
Added test class and module to reproduce a no-type-no-name injection scenario
NoHint tests
tests/di/NoHintTest.php
New tests asserting NoHint is an Unbound and that injection without type/name throws NoHint

Sequence Diagram(s)

sequenceDiagram
    participant Test as NoHintTest
    participant Injector as Injector
    participant Container as Container
    participant Arguments as Arguments
    participant NoHint as NoHint (Exception)

    Test->>Injector: request injection for target with unnamed param
    Injector->>Container: resolve dependency
    Container->>Arguments: getParameter(argument)
    Arguments-->>Container: detect Unbound (NoHint case)
    Container->>NoHint: construct message and throw NoHint
    NoHint-->>Injector: exception propagates
    Injector-->>Test: NoHint observed by test

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Pay attention to error-path changes in src/di/Arguments.php and src/di/Container.php to ensure NoHint is constructed and propagated correctly.
  • Verify tests in tests/di/NoHintTest.php and fixtures (tests/di/Fake/*) align with exception hierarchy and behavior.
  • Check consumers of Argument::meta for compatibility with the new meta format.

Possibly related PRs

  • ray-di/Ray.Di#294 — Modifies DI core classes (Argument/Unbound docs); likely related to metadata/exception handling changes.

Poem

🐇 I hopped from A down to D, renaming each small paw,

When hints go missing, I shout — "NoHint!" — and point to the flaw.
I nibble on messages, bind tests in a row,
A little rabbit cheering when errors grow. 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add NoHint exception and improve Unbound error messages' accurately captures the main changes: introducing a new NoHint exception class and enhancing error message formatting for Unbound exceptions.
✨ Finishing touches
  • [ ] 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • [ ] Create PR with unit tests
  • [ ] Post copyable unit tests in a comment
  • [ ] Commit unit tests in branch improve-unbound-error-messages

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d941f5c324dea575b6daa0562f98c8f7e4b4af6a and 429359dc16a617d3f2a8a751b615a58056aaefe0.

📒 Files selected for processing (1)
  • src/di/Arguments.php (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/di/Arguments.php
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: ci / PHPUnit - PHP 8.5 (windows-latest, highest)
  • GitHub Check: Sourcery review

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.

coderabbitai[bot] avatar Dec 13 '25 09:12 coderabbitai[bot]

Reviewer's Guide

Introduces a dedicated NoHint exception for parameters lacking both type hints and binding names, refines Unbound exception messages to use a unified '{type}-{name}' binding-key format with richer context, updates demo chain-error constructors for clearer variable naming, and adds tests and fakes to cover the new behavior.

Sequence diagram for resolving a parameter and raising NoHint or Unbound

sequenceDiagram
    actor Developer
    participant Container
    participant Arguments
    participant Argument
    participant BindingResolver as BindingStorage_or_Resolver
    participant NoHint
    participant Unbound

    Developer->>Container: resolve(parameter)
    Container->>BindingResolver: lookup(class, name)
    BindingResolver-->>Container: missing binding
    Container->>Container: unbound(index)
    alt class == '' and name == ''
        Container-->>Arguments: NoHint instance
    else binding key identifiable
        Container-->>Arguments: Unbound("'{type}-{name}'")
    end

    Arguments->>Argument: get()
    Arguments->>Container: request dependency for Argument
    Container-->>Arguments: throws NoHint or Unbound

    alt caught NoHint
        Arguments->>Argument: get()
        Arguments->>Arguments: getNoHintMsg(argument)
        Arguments-->>Developer: throws NoHint("$var (file:line)")
    else caught Unbound
        Arguments->>Argument: getMeta()
        Arguments-->>Developer: throws Unbound(meta)
    end

File-Level Changes

Change Details Files
Add dedicated NoHint exception type and integrate it into the resolution flow when no type hint and no binding name are present.
  • Introduce final Exception\NoHint class extending Unbound to represent parameters without type hints or names.
  • Modify Container::unbound to return NoHint when both class and name are empty, otherwise wrap the binding key in single quotes for Unbound messages.
  • Update Arguments::getParameter to detect underlying NoHint causes and rethrow them with a formatted message including variable name and source location via getNoHintMsg.
src/di/Exception/NoHint.php
src/di/Container.php
src/di/Arguments.php
Standardize Unbound exception message format around the '{type}-{name}' binding key and improve metadata used in error-chain output.
  • Document Unbound message format and examples in a class-level docblock.
  • Change Argument meta string to "'%s-%s' in %s:%d ($%s)" so chained Unbound errors show binding key, file, line, and variable name consistently.
  • Ensure Container::unbound uses the same "'%s-%s'" key format that the Argument meta and documented Unbound messages expect.
src/di/Exception/Unbound.php
src/di/Argument.php
src/di/Container.php
Adjust demo chain-error classes and add tests/fakes to validate NoHint behavior and maintain documentation quality of error chains.
  • Rename constructor parameters in demo chain-error A–D classes from generic $dep to meaningful variables ($b, $c, $d, $e) to align with chain display examples.
  • Add NoHintTest to verify that NoHint is a subtype of Unbound and is thrown for untyped, unnamed constructor parameters.
  • Introduce FakeUnNamedModule and FakeUnNamedClass test fixtures to exercise the NoHint code path in the injector.
demo/chain-error/A.php
demo/chain-error/B.php
demo/chain-error/C.php
demo/chain-error/D.php
tests/di/NoHintTest.php
tests/di/Fake/FakeUnNamedModule.php
tests/di/Fake/FakeUnNamedClass.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

  • Contact our support team for questions or feedback.
  • Visit our documentation for detailed guides and information.
  • Keep in touch with the Sourcery team by following us on X/Twitter, LinkedIn or GitHub.

sourcery-ai[bot] avatar Dec 13 '25 09:12 sourcery-ai[bot]

Codecov Report

:white_check_mark: All modified and coverable lines are covered by tests. :white_check_mark: Project coverage is 100.00%. Comparing base (55cabee) to head (429359d).

Additional details and impacted files
@@             Coverage Diff             @@
##                 2.x      #310   +/-   ##
===========================================
  Coverage     100.00%   100.00%           
- Complexity       326       331    +5     
===========================================
  Files             47        47           
  Lines            839       852   +13     
===========================================
+ Hits             839       852   +13     

:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.

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

codecov[bot] avatar Dec 13 '25 13:12 codecov[bot]

@coderabbitai FakeUnNamedClass and FakeUnNamedModule are in the same namespace (Ray\Di) as the test, so no imports are needed. Tests pass without the suggested use statements.

koriym avatar Dec 14 '25 02:12 koriym

[!TIP] For best results, initiate chat on the files or code changes.

@koriym You're absolutely right! Since all three classes are in the same Ray\Di namespace, PHP will resolve them without needing explicit use statements. I apologize for the unnecessary suggestion. Thank you for the clarification!

coderabbitai[bot] avatar Dec 14 '25 02:12 coderabbitai[bot]