⚡ Optimize ServiceDiscovery using PSR-4 path resolution#71
⚡ Optimize ServiceDiscovery using PSR-4 path resolution#71
Conversation
Replaces heavy file I/O and regex parsing with efficient path-to-class resolution using Composer's PSR-4 map for compliant files. Falls back to parsing for non-compliant files. Optimizes cold start performance.
|
👋 Jules, reporting for duty! I'm here to lend a hand with this pull request. When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down. I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job! For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with New to Jules? Learn more at jules.google/docs. For security, I will only act on instructions from the user who triggered this task. |
QA Pipeline Results
Artifacts
Generated by core php qa pipeline |
Removes `2>&1` redirection from Psalm, PHPStan, Pint, and Composer Audit commands in `.github/workflows/qa.yml`. This prevents stderr output (such as progress bars or debug messages) from corrupting the JSON/SARIF output files, which caused the CI pipeline to fail with JSON syntax errors.
Sanitizes Psalm's SARIF output in `.github/workflows/qa.yml` using `jq` to ensure all physical location regions (lines/columns) have values of at least 1. This prevents GitHub Code Scanning from rejecting the report due to invalid 0 values.
📝 WalkthroughWalkthroughThis PR updates GitHub Actions workflow configuration for static analysis and quality checks, refining error handling and artifact generation. It also enhances ServiceDiscovery with Composer PSR-4 prefix caching and class name resolution optimization to improve discovery performance. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@src/Core/Service/ServiceDiscovery.php`:
- Around line 201-217: The properties and comments use American English
("normalized" and "loaded") but the codebase requires UK spelling; rename the
property normalizedPrefixes to normalisedPrefixes and update any references
(e.g., accesses, assignments, type annotations) accordingly, rename any related
boolean like prefixesLoaded to prefixesNormalisedLoaded or similar UK-spelled
variant, and update docblocks and inline comments (e.g., "Normalized PSR-4
prefixes map" -> "Normalised PSR-4 prefixes map", "Whether prefixes have been
loaded" -> "Whether prefixes have been normalised/loaded") so all identifiers
and comment text use UK spelling consistently (search for occurrences of
normalizedPrefixes and prefixesLoaded and update callers and tests).
- Around line 759-782: The resolveClass method currently uses str_replace to
remove ".php" which can remove occurrences anywhere in the path; update
resolveClass so that after computing $relativePath you strip the ".php" suffix
only (e.g. use a suffix-only removal like preg_replace('/\.php$/', '',
$relativePath) or check str_ends_with and substr) before converting separators
into namespace separators and assigning $relativeClass, ensuring you still
return $namespace.$relativeClass.
🧹 Nitpick comments (1)
.github/workflows/qa.yml (1)
184-195: Emit a minimal SARIF stub when Psalm outputs nothing.An empty
psalm.sarifisn’t valid JSON and can still trip the upload step; a minimal SARIF payload avoids that.🔧 Suggested tweak
- else - touch psalm.sarif - fi + else + cat << 'EOF' > psalm.sarif + {"version":"2.1.0","runs":[]} + EOF + fi
| /** | ||
| * The Composer ClassLoader instance. | ||
| */ | ||
| protected ?\Composer\Autoload\ClassLoader $loader = null; | ||
|
|
||
| /** | ||
| * Normalized PSR-4 prefixes map. | ||
| * | ||
| * @var array<string, array<string>> | ||
| */ | ||
| protected array $normalizedPrefixes = []; | ||
|
|
||
| /** | ||
| * Whether prefixes have been loaded. | ||
| */ | ||
| protected bool $prefixesLoaded = false; | ||
|
|
There was a problem hiding this comment.
Use UK spelling for new identifiers and comments.
Please switch to “normalised/normalise/optimisation” (e.g., normalisedPrefixes, “Normalise scan path”) to comply.
✏️ Suggested rename
- * Normalized PSR-4 prefixes map.
+ * Normalised PSR-4 prefixes map.
@@
- protected array $normalizedPrefixes = [];
+ protected array $normalisedPrefixes = [];
@@
- // Normalize scan path
+ // Normalise scan path
@@
- foreach ($this->normalizedPrefixes as $namespace => $paths) {
+ foreach ($this->normalisedPrefixes as $namespace => $paths) {
@@
- // Optimization: Resolve class from path
+ // Optimisation: Resolve class from path
@@
- * Load and normalize Composer PSR-4 prefixes.
+ * Load and normalise Composer PSR-4 prefixes.
@@
- $this->normalizedPrefixes[$namespace][] = rtrim($realPath, '/').'/';
+ $this->normalisedPrefixes[$namespace][] = rtrim($realPath, '/').'/';🤖 Prompt for AI Agents
In `@src/Core/Service/ServiceDiscovery.php` around lines 201 - 217, The properties
and comments use American English ("normalized" and "loaded") but the codebase
requires UK spelling; rename the property normalizedPrefixes to
normalisedPrefixes and update any references (e.g., accesses, assignments, type
annotations) accordingly, rename any related boolean like prefixesLoaded to
prefixesNormalisedLoaded or similar UK-spelled variant, and update docblocks and
inline comments (e.g., "Normalized PSR-4 prefixes map" -> "Normalised PSR-4
prefixes map", "Whether prefixes have been loaded" -> "Whether prefixes have
been normalised/loaded") so all identifiers and comment text use UK spelling
consistently (search for occurrences of normalizedPrefixes and prefixesLoaded
and update callers and tests).
| /** | ||
| * Resolve class name from real file path using a subset of prefixes. | ||
| * | ||
| * @param string $realPath Normalized real path of the file | ||
| * @param array<string, array<string>> $prefixes Subset of prefixes to check | ||
| */ | ||
| protected function resolveClass(string $realPath, array $prefixes): ?string | ||
| { | ||
| foreach ($prefixes as $namespace => $paths) { | ||
| foreach ($paths as $path) { | ||
| if (str_starts_with($realPath, $path)) { | ||
| // Found matching prefix | ||
| $relativePath = substr($realPath, strlen($path)); | ||
|
|
||
| // Convert path to namespace | ||
| $relativeClass = str_replace(['/', '.php'], ['\\', ''], $relativePath); | ||
|
|
||
| return $namespace.$relativeClass; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return null; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
head -30 src/Core/Service/ServiceDiscovery.phpRepository: host-uk/core-php
Length of output: 825
🏁 Script executed:
sed -n '759,782p' src/Core/Service/ServiceDiscovery.phpRepository: host-uk/core-php
Length of output: 926
🏁 Script executed:
sed -n '730,790p' src/Core/Service/ServiceDiscovery.phpRepository: host-uk/core-php
Length of output: 1894
Use a suffix-only replacement for the .php file extension.
Using str_replace to remove .php will replace all occurrences in the path, not just the file extension. If a class path contains .php elsewhere (though unlikely with PSR-4), it could corrupt the namespace. Use preg_replace with a suffix-only pattern:
Safer extension removal
- $relativeClass = str_replace(['/', '.php'], ['\\', ''], $relativePath);
+ $relativeClass = str_replace('/', '\\', $relativePath);
+ $relativeClass = preg_replace('/\.php$/i', '', $relativeClass);🤖 Prompt for AI Agents
In `@src/Core/Service/ServiceDiscovery.php` around lines 759 - 782, The
resolveClass method currently uses str_replace to remove ".php" which can remove
occurrences anywhere in the path; update resolveClass so that after computing
$relativePath you strip the ".php" suffix only (e.g. use a suffix-only removal
like preg_replace('/\.php$/', '', $relativePath) or check str_ends_with and
substr) before converting separators into namespace separators and assigning
$relativeClass, ensuring you still return $namespace.$relativeClass.
💡 What:
Implemented a performance optimization in
src/Core/Service/ServiceDiscovery.phpthat resolves class names directly from file paths using Composer's PSR-4 class loader configuration, instead of opening and parsing every PHP file in the module directories.🎯 Why:
The previous implementation scanned all files recursively, read their content (
file_get_contents), and used regex (preg_match) to extract the namespace and class name. This caused heavy I/O and CPU overhead during cold starts or when the discovery cache was cold.📊 Measured Improvement:
Benchmark results on a hot cache environment with 500 simulated service files (mimicking large files) show a modest improvement:
While the improvement on a hot cache/RAM disk environment is small (~5%), the primary benefit is the reduction of file I/O operations. The optimization eliminates the need to read the full content of every PHP file in the module paths, which will yield significant performance gains on systems with slower I/O (e.g., cloud storage mounts, spinning disks) or during actual cold starts where file content is not yet in the OS page cache.
The implementation is robust:
Verified with
tests/Unit/ServiceDiscoveryBenchmarkTest.php(deleted before submission) and confirmed no regressions with the full test suite.PR created automatically by Jules for task 7866122941605317835 started by @Snider
Summary by CodeRabbit