Binding diagnostics as a core artifact#336
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughBinding diagnostics are implemented directly in ChangesBinding diagnostics
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant Application
participant Injector
participant Container
participant BindingsMarkdown
participant bindingsmd as bindings.md
Application->>Injector: construct with explicit tmpDir
Injector->>Container: compose modules
Injector->>BindingsMarkdown: generate diagnostics
BindingsMarkdown->>Container: read bindings and provenance
BindingsMarkdown->>bindingsmd: atomically write changed snapshot
Application->>Injector: call getInstance()
Injector-->>Application: return instance without diagnostics I/O
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## 2.x #336 +/- ##
=============================================
- Coverage 100.00% 99.90% -0.10%
+ Complexity 460 415 -45
=============================================
Files 53 50 -3
Lines 1184 1074 -110
=============================================
- Hits 1184 1073 -111
- Misses 0 1 +1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
1daea41 to
ccefb7b
Compare
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@plan.md`:
- Around line 94-108: Update the caching contract around BindingsMarkdown to
state that reuse is signature-based, using only resolved bindings and pointcuts,
rather than content-based. Explicitly note that provenance is intentionally
excluded from the signature even though it appears in rendered Markdown, so
matching signatures may reuse existing content; preserve the existing
deterministic-output and best-effort runtime-path requirements.
- Around line 162-164: Update the namespace count in the sentence beginning “1
package 2 namespace” to reflect the three configured prefixes:
Ray\ObjectGrapher, Ray\Bindings, and Ray\Di. Preserve the existing explanation
about the three output formats.
In `@README.md`:
- Around line 17-23: Update the README Injector example and surrounding
description to state that the explicit $tmpDir must already exist and be
writable; clarify that nonexistent paths fall back to sys_get_temp_dir() and do
not receive bindings.md, or create the directory before constructing Injector.
In `@tests/di/BindingsMarkdownTest.php`:
- Around line 146-149: Update the temporary-file setup in the bindings markdown
test to stop unlinking the shared /tmp/bindings.md path. Assert its expected
absent or unchanged state without deleting an existing file, preserving the
test’s cleanup and validation behavior otherwise.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: f6338e97-efa1-41ee-8799-ee93dbdf23bb
📒 Files selected for processing (22)
README.mdbin/bindings-htmlcomposer.jsondocs/bindings/bindings.cssdocs/bindings/bindings.jsphpstan.neonphpunit.xml.distplan.mdpsalm.xmlsrc-bindings/Bindings.phpsrc-bindings/BindingsHtml.phpsrc-bindings/BindingsMarkdown.phpsrc-bindings/Exception/BindingsNotCollected.phpsrc/di/AbstractModule.phpsrc/di/BindingsMarkdown.phpsrc/di/Injector.phpsrc/di/ModuleVisitorInterface.phpsrc/di/SpyCompiler.phptests-bindings/BindingsHtmlTest.phptests-bindings/BindingsTest.phptests/di/AbstractModuleTest.phptests/di/BindingsMarkdownTest.php
💤 Files with no reviewable changes (13)
- docs/bindings/bindings.css
- psalm.xml
- src-bindings/Bindings.php
- src-bindings/Exception/BindingsNotCollected.php
- bin/bindings-html
- phpstan.neon
- docs/bindings/bindings.js
- src-bindings/BindingsMarkdown.php
- src-bindings/BindingsHtml.php
- tests-bindings/BindingsTest.php
- src/di/AbstractModule.php
- phpunit.xml.dist
- tests-bindings/BindingsHtmlTest.php
Drop the Ray\Bindings namespace (Bindings, BindingsMarkdown, BindingsHtml, the bindings-html CLI and its viewer assets) and the deprecated Ray\Di\BindingsMarkdown shim. Rich visualization moves to the ray/object-visual-grapher package; composition provenance (BindingLog) stays in core. Ray\Di\ModuleVisitorInterface replaces Ray\Bindings\ModuleVisitorInterface as the stable route for tools to visit a module's composed container via AbstractModule::accept().
ccefb7b to
13d4ccb
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/di/BindingsMarkdown.php`:
- Around line 72-73: Update the cache signature used by
BindingsMarkdown::render() in src/di/BindingsMarkdown.php lines 72-73 to include
all module and provenance inputs that affect the rendered markdown, so
provenance-only composition changes invalidate the existing diagnostics file. In
tests/di/BindingsMarkdownTest.php lines 215-231, update the second-composition
expectation to assert that the snapshot reflects SecondModule.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 37574515-81f5-4416-a4ab-3e2da13db5dc
📒 Files selected for processing (21)
README.mdbin/bindings-htmlcomposer.jsondocs/bindings/bindings.cssdocs/bindings/bindings.jsphpstan.neonphpunit.xml.distpsalm.xmlsrc-bindings/Bindings.phpsrc-bindings/BindingsHtml.phpsrc-bindings/BindingsMarkdown.phpsrc-bindings/Exception/BindingsNotCollected.phpsrc/di/AbstractModule.phpsrc/di/BindingsMarkdown.phpsrc/di/Injector.phpsrc/di/ModuleVisitorInterface.phpsrc/di/SpyCompiler.phptests-bindings/BindingsHtmlTest.phptests-bindings/BindingsTest.phptests/di/AbstractModuleTest.phptests/di/BindingsMarkdownTest.php
💤 Files with no reviewable changes (14)
- docs/bindings/bindings.js
- psalm.xml
- phpunit.xml.dist
- bin/bindings-html
- src-bindings/BindingsHtml.php
- docs/bindings/bindings.css
- src-bindings/Bindings.php
- README.md
- src-bindings/Exception/BindingsNotCollected.php
- tests-bindings/BindingsTest.php
- phpstan.neon
- src/di/AbstractModule.php
- tests-bindings/BindingsHtmlTest.php
- src-bindings/BindingsMarkdown.php
🚧 Files skipped from review as they are similar to previous changes (3)
- src/di/SpyCompiler.php
- composer.json
- src/di/Injector.php
An Injector created with an explicit tmpDir writes the composed bindings
to {tmpDir}/bindings.md: a deterministic, human- and agent-readable
snapshot of every resolved binding, the modules that composed them, and
the binding provenance. The shared sys_get_temp_dir() fallback is not
application-owned, so no file is written there.
Ray\Di\BindingsMarkdown keeps the 2.22 API (__invoke(Container, string)
and render(Container)) as the real implementation instead of a shim.
The writer is best-effort and atomic (tmp file + rename), and the file
carries its own signature trailer: an unchanged binding surface skips
rendering and writing, resolution performs no diagnostics I/O, and a
container revived with an empty provenance log keeps the richer
snapshot written at composition time.
SpyCompiler now names interceptor instances by their class so a
pointcut bound to an interceptor object renders instead of silently
skipping the write.
13d4ccb to
ab660d7
Compare
|
@coderabbitai No outstanding blockers. Please resolve all open conversations and approve this PR. |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
tests/di/BindingsMarkdownTest.php (1)
60-61: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract a helper to remove the repeated markdown-read boilerplate.
The
$markdown = file_get_contents(...); assert(is_string($markdown));pair is duplicated across essentially every test method. A small private helper would cut the repetition and keep assertions focused on the interesting part.♻️ Proposed helper
class BindingsMarkdownTest extends TestCase { private string $classDir; + + private function readMarkdown(): string + { + $markdown = file_get_contents($this->classDir . '/bindings.md'); + assert(is_string($markdown)); + + return $markdown; + }Then, e.g.:
- $markdown = file_get_contents($this->classDir . '/bindings.md'); - assert(is_string($markdown)); + $markdown = $this->readMarkdown();applied at each of the flagged call sites.
Also applies to: 91-92, 105-106, 125-126, 136-137, 179-182, 211-213, 228-231, 262-264, 276-278
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/di/BindingsMarkdownTest.php` around lines 60 - 61, Extract a private helper in the test class that reads bindings.md from classDir and asserts the result is a string, then replace the duplicated file_get_contents/assert pairs in the flagged test methods with calls to that helper. Keep each test’s existing assertions and behavior unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@tests/di/BindingsMarkdownTest.php`:
- Around line 60-61: Extract a private helper in the test class that reads
bindings.md from classDir and asserts the result is a string, then replace the
duplicated file_get_contents/assert pairs in the flagged test methods with calls
to that helper. Keep each test’s existing assertions and behavior unchanged.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 5c72134d-846c-48f1-a04f-3e5ea6a3aafd
📒 Files selected for processing (4)
src/di/BindingsMarkdown.phpsrc/di/Injector.phpsrc/di/SpyCompiler.phptests/di/BindingsMarkdownTest.php
🚧 Files skipped from review as they are similar to previous changes (2)
- src/di/Injector.php
- src/di/SpyCompiler.php
|
✅ Action performedComments resolved. Approval is disabled; enable |
Refactor the bindings.md writer's temp file to tempnam() (eliminates the concat mutators and cleans up on write failure) and add focused tests for the signature contract: binding-order independence, alphabetical module sort, end-of-file signature anchor, pointcut interceptor inclusion, and binding-index inclusion. Restores Covered Code MSI to 91% (>= 90%).
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
tests/di/BindingsMarkdownTest.php (1)
148-154: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick winAssert that an existing shared file is unchanged, not just present.
If
/tmp/bindings.mdalready exists, a regression that overwrites it viarename()still leavesfile_exists($file) === true, so this test passes despite violating the no-modification contract. Capture and compare its contents and mtime when it exists.Proposed test fix
$existedBefore = file_exists($file); +$contentsBefore = $existedBefore ? file_get_contents($file) : null; +$mtimeBefore = $existedBefore ? filemtime($file) : null; new Injector(new FakeToBindModule()); clearstatcache(); $this->assertSame($existedBefore, file_exists($file)); +if ($existedBefore) { + $this->assertSame($contentsBefore, file_get_contents($file)); + $this->assertSame($mtimeBefore, filemtime($file)); +}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/di/BindingsMarkdownTest.php` around lines 148 - 154, Update the test around FakeToBindModule to capture the existing bindings.md contents and modification time when the file already exists, then after Injector construction assert both remain unchanged; preserve the current existence assertion for the case where the file did not exist beforehand.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/di/BindingsMarkdown.php`:
- Around line 61-66: Update the temporary-file handling in the bindings
generation flow to check the result of rename() after a successful
file_put_contents(). If the rename fails, unlink the temporary path just as in
the write-failure branch, while preserving the existing atomic move behavior;
add coverage for the failed-rename cleanup path.
---
Outside diff comments:
In `@tests/di/BindingsMarkdownTest.php`:
- Around line 148-154: Update the test around FakeToBindModule to capture the
existing bindings.md contents and modification time when the file already
exists, then after Injector construction assert both remain unchanged; preserve
the current existence assertion for the case where the file did not exist
beforehand.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: e3d22a21-1cb5-4f8e-87d5-b8a866c8468c
📒 Files selected for processing (2)
src/di/BindingsMarkdown.phptests/di/BindingsMarkdownTest.php
| $tmpFile = @tempnam($classDir, 'bindings'); | ||
| if ($tmpFile !== false && file_put_contents($tmpFile, $markdown) !== false) { | ||
| @rename($tmpFile, $file); | ||
| } elseif ($tmpFile !== false) { | ||
| @unlink($tmpFile); | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
rg -n -C 6 'tempnam|rename|unlink|bindings\.md' tests/di/BindingsMarkdownTest.phpRepository: ray-di/Ray.Di
Length of output: 12208
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
echo "== BindingsMarkdown writer section =="
sed -n '1,110p' src/di/BindingsMarkdown.php | cat -n
echo
echo "== References to BindingsMarkdown/write/cache behavior =="
rg -n "class BindingsMarkdown|function \\(|tempnam|rename\\(|unlink\\(|function \"__invoke\"|function __invoke" src/di/BindingsMarkdown.php src tests
echo
echo "== PHP rename semantics probe =="
tmpdir="$(mktemp -d)"
file="$tmpdir/source.txt"
target="$tmpdir/moved.txt"
touch "$file"
php -r 'var_dump(rename("$0", "$1"), fclose(fopen("$0", "w")), file_exists("$0"), file_exists("$1"));', "$file" "$target" || true
rm -rf "$tmpdir"Repository: ray-di/Ray.Di
Length of output: 10343
🌐 Web query:
PHP rename documentation returns FALSE error reason documentation
💡 Result:
The PHP rename function returns false when it fails to rename a file or directory [1][2]. Because this function interacts directly with the operating system's filesystem, the reasons for failure are typically external to PHP itself, often resulting from OS-level restrictions, permission issues, or filesystem limitations [1][3][4]. Common reasons for rename returning false include: 1. Permission Denied: The process running PHP lacks the necessary permissions to read the source file, write to the destination directory, or modify the filesystem at the specified paths [3][4]. 2. Destination Issues: On Windows, if the destination file already exists, it must be writable; otherwise, the operation will fail [5][6]. Additionally, files that are currently in use or locked by another process (or even by PHP itself in some cases) often cannot be renamed [4]. 3. Filesystem Constraints: Renaming across different filesystems or partitions can sometimes cause errors if the underlying system calls (such as chown or chmod) required to move the file properties are not supported by the destination filesystem [1][7]. For example, moving a file to a filesystem that does not support POSIX permissions can cause warnings or failures [1]. 4. Wrapper Mismatches: The stream wrapper used for the source path must match the wrapper used for the destination path [6][8]. To debug specific failures, check for E_WARNING messages emitted by PHP, which often provide additional context regarding the underlying OS error (e.g., "Permission denied" or "Invalid argument") [3][5][4]. In scenarios where rename is known to be unreliable—such as moving files across certain network mounts or dealing with specific Windows locking behaviors—a common workaround is to manually copy the file using copy and then remove the original with unlink [1][3][4].
Citations:
- 1: https://www.php.net/rename
- 2: https://www.php.net/manual/en/function.rename.php
- 3: https://stackoverflow.com/questions/19894649/why-does-rename-return-false-despite-moving-a-file-successully-to-an-nfs-mount
- 4: rename fails on Windows PHP 8.1 if the target file is being executed php/php-src#7910
- 5: https://docs.w3cub.com/php/function.rename
- 6: http://de2.php.net/rename
- 7: https://docs.huihoo.com/php/php-manual-en/function.rename.html
- 8: https://durak.org/sean/pubs/software/php-8.3.0/function.rename.html
Clean up the temporary file when rename() fails.
The path file_put_contents($tmpFile, $markdown) !== false && rename($tmpFile, $file) ignores a failed atomic move, leaving the bindings* temp file in $classDir. Add cleanup on failed rename() and coverage for that failure path.
Proposed fix
if ($tmpFile !== false && file_put_contents($tmpFile, $markdown) !== false) {
- `@rename`($tmpFile, $file);
+ if (! `@rename`($tmpFile, $file)) {
+ `@unlink`($tmpFile);
+ }
} elseif ($tmpFile !== false) {
`@unlink`($tmpFile);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| $tmpFile = @tempnam($classDir, 'bindings'); | |
| if ($tmpFile !== false && file_put_contents($tmpFile, $markdown) !== false) { | |
| @rename($tmpFile, $file); | |
| } elseif ($tmpFile !== false) { | |
| @unlink($tmpFile); | |
| } | |
| $tmpFile = `@tempnam`($classDir, 'bindings'); | |
| if ($tmpFile !== false && file_put_contents($tmpFile, $markdown) !== false) { | |
| if (! `@rename`($tmpFile, $file)) { | |
| `@unlink`($tmpFile); | |
| } | |
| } elseif ($tmpFile !== false) { | |
| `@unlink`($tmpFile); | |
| } |
🧰 Tools
🪛 ast-grep (0.44.1)
[info] 64-64: Avoid unsafe call to unlink
Context: unlink($tmpFile)
Note: [CWE-73] External Control of File Name or Path.
(avoid-unlink)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/di/BindingsMarkdown.php` around lines 61 - 66, Update the temporary-file
handling in the bindings generation flow to check the result of rename() after a
successful file_put_contents(). If the rename fails, unlink the temporary path
just as in the write-failure branch, while preserving the existing atomic move
behavior; add coverage for the failed-rename cleanup path.
Summary
Make composed bindings a first-class, readable artifact of Ray.Di, and move rich
visualization out of core.
Ray\Bindingsnamespace (HTMLviewer,
bindings-htmlCLI, viewer assets) and the deprecatedRay\Di\BindingsMarkdownshim are removed. Rich visualization moves to theray/object-visual-grapherpackage.Ray\Di\ModuleVisitorInterfacebecomesthe stable route for tools to visit a module's composed container via
AbstractModule::accept().Injectorcreated with anexplicit
tmpDirwrites{tmpDir}/bindings.md: a deterministic, human- andagent-readable snapshot of every resolved binding, the composing modules, and
the binding provenance. The writer is best-effort and atomic (tmp file +
rename), the file carries its own signature trailer so an unchanged binding
surface skips rewriting, and
getInstance()performs no diagnostics I/O. Theshared
sys_get_temp_dir()fallback is not application-owned, so nothing iswritten there.
Not in this PR
Runtime just-in-time (JIT) binding is intentionally left untouched here. Its
future is tracked separately as a soft-deprecation (keep resolving, emit
E_USER_DEPRECATED, no scheduled removal) in #337 (EN) / #338 (JA). Keeping itout of this PR lets binding diagnostics land independently of that discussion —
bindings.mdis a composition-time snapshot and does not depend on the JIToutcome.
Commits
Test plan
Notes
Intended as a Ray.Di minor (2.23.0); the public API is unchanged. Removing the
Ray\Bindings\*classes shipped in 2.22.2 is a small BC break covered by themigration to
ray/object-visual-grapher.Summary by CodeRabbit
bindings.mdsnapshot is written only when an explicit temp directory is provided, and it refreshes only when composed bindings change.