refactor: improve validation command structure and add utility classes#7
refactor: improve validation command structure and add utility classes#7konradmichalik merged 4 commits intomainfrom
Conversation
|
Warning Rate limit exceeded@jackd248 has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 19 minutes and 4 seconds before requesting another review. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 📒 Files selected for processing (6)
""" WalkthroughThis update introduces a modular validation architecture for translation files. It adds utility classes for class and path validation, a collector for file gathering, and a registry-based system for parsers and validators. Validator interfaces and implementations are refactored for single-file processing and structured issue reporting, with rendering and explanation methods separated from core validation logic. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant ValidateTranslationCommand
participant ClassUtility
participant Collector
participant ValidatorRegistry
participant Validator
participant Output
User->>ValidateTranslationCommand: execute()
ValidateTranslationCommand->>ClassUtility: validateClass()
ClassUtility-->>ValidateTranslationCommand: validation result
ValidateTranslationCommand->>Collector: collectFiles(paths, detector, excludePatterns)
Collector-->>ValidateTranslationCommand: filesByParserAndPath
ValidateTranslationCommand->>ValidatorRegistry: getAvailableValidators()
ValidatorRegistry-->>ValidateTranslationCommand: validatorClasses
loop For each validator
loop For each parser and path
loop For each translation set
ValidateTranslationCommand->>Validator: processFile(file)
Validator-->>ValidateTranslationCommand: issues
end
end
ValidateTranslationCommand->>Validator: renderIssueSets(input, output, issues)
Validator-->>Output: rendered issues
end
ValidateTranslationCommand->>Output: summary and exit code
Possibly related PRs
Poem
✨ Finishing Touches
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (8)
src/Parser/ParserRegistry.php (1)
1-18: LGTM! Consider adding documentation for better maintainability.The registry pattern implementation is clean and serves its purpose well in the modular architecture. However, the static analysis correctly identifies missing documentation.
Consider adding class and method documentation:
+/** + * Registry for managing available parser implementations. + */ class ParserRegistry { + /** + * Get all available parser class names. + * + * @return array<int, class-string<ParserInterface>> + */ public static function getAvailableParsers(): arraysrc/Utility/PathUtility.php (1)
9-25: Solid path normalization logic with minor documentation need.The implementation correctly handles the main path normalization scenarios. The logic flow is clear and uses modern PHP string functions appropriately.
Consider adding method documentation as suggested by static analysis:
+ /** + * Normalize a folder path to be relative with trailing slash. + * + * @param string $path The path to normalize + * @return string The normalized relative path with trailing slash + */ public static function normalizeFolderPath(string $path): stringAlso consider handling the edge case where
getcwd()might returnfalse:- $cwd = getcwd(); + $cwd = getcwd() ?: '';src/Utility/ClassUtility.php (1)
11-30: Well-structured class validation with proper error handling.The validation logic correctly checks class existence before interface compliance, and the PSR-3 logging integration provides good error reporting. The null handling is appropriate for optional class validation.
Add documentation as suggested by static analysis:
+ /** + * Validate that a class exists and implements the required interface. + * + * @param string $interface The required interface class name + * @param LoggerInterface $logger Logger for error reporting + * @param string|null $class The class to validate (null is considered valid) + * @return bool True if validation passes, false otherwise + */ public static function validateClass(string $interface, LoggerInterface $logger, ?string $class): boolsrc/Validator/ValidatorRegistry.php (1)
7-20: LGTM! Well-implemented registry pattern.The
ValidatorRegistryeffectively centralizes validator management with proper type annotations. Consider adding documentation to address the static analysis hints:+/** + * Registry for available translation validators. + */ class ValidatorRegistry { /** + * Get all available validator classes. + * * @return array<int, class-string<ValidatorInterface>> */ public static function getAvailableValidators(): arraysrc/FileDetector/Collector.php (1)
34-34: Remove unnecessary method_exists check.Since
ParserRegistry::getAvailableParsers()returnsclass-string<ParserInterface>types andgetSupportedFileExtensions()is defined in the interface, themethod_exists()check is redundant.- if (method_exists($parserClass, 'getSupportedFileExtensions')) { $files = array_filter( glob($path.'/*'), static fn ($file) => in_array(pathinfo($file, PATHINFO_EXTENSION), $parserClass::getSupportedFileExtensions(), true) ); - }src/Validator/ValidatorInterface.php (1)
11-24: LGTM! Excellent interface refactoring with separation of concerns.The interface refactoring successfully separates file processing, issue rendering, and explanation into distinct methods, improving testability and maintainability. Consider adding comprehensive documentation:
+/** + * Interface for translation file validators. + */ interface ValidatorInterface { /** + * Process a single parsed file and return validation issues. + * + * @param ParserInterface $file The parsed file to validate * @return array<string, mixed> */ public function processFile(ParserInterface $file): array; /** + * Render collected validation issues to console output. + * + * @param InputInterface $input Console input interface + * @param OutputInterface $output Console output interface * @param array<string, mixed> $issueSets + * @return void */ public function renderIssueSets(InputInterface $input, OutputInterface $output, array $issueSets): void; + /** + * Provide a textual explanation of what this validator checks. + * + * @return string Description of the validator's purpose + */ public function explain(): string;src/Validator/MismatchValidator.php (1)
49-51: Simplify redundant array search operation.The ternary operation on line 50 searches for a key and returns the value at that position, which is the same as the key itself. This can be simplified.
- $this->issues[$key][$file] = in_array($key, $keys, true) - ? $keys[array_search($key, $keys, true)] - : null; + $this->issues[$key][$file] = in_array($key, $keys, true) ? $key : null;src/Command/ValidateTranslationCommand.php (1)
108-120: Address the TODO comment for nested loop simplification.The 4-level nested loop structure is indeed complex and should be simplified for better maintainability.
The nested structure processes: parsers → paths → translation sets → validators. Would you like me to help refactor this into a more maintainable structure, perhaps using a method extraction or a different data flow approach?
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (16)
README.md(1 hunks)src/Command/ValidateTranslationCommand.php(2 hunks)src/FileDetector/Collector.php(1 hunks)src/FileDetector/PrefixFileDetector.php(1 hunks)src/Parser/ParserInterface.php(1 hunks)src/Parser/ParserRegistry.php(1 hunks)src/Parser/ParserUtility.php(0 hunks)src/Utility/ClassUtility.php(1 hunks)src/Utility/PathUtility.php(1 hunks)src/Validator/AbstractValidator.php(1 hunks)src/Validator/DuplicatesValidator.php(1 hunks)src/Validator/MismatchValidator.php(1 hunks)src/Validator/SchemaValidator.php(2 hunks)src/Validator/ValidatorInterface.php(1 hunks)src/Validator/ValidatorRegistry.php(1 hunks)tests/Build/app/Language/XLIFF-fail/de.locallang_be.xlf(1 hunks)
💤 Files with no reviewable changes (1)
- src/Parser/ParserUtility.php
🧰 Additional context used
🧬 Code Graph Analysis (6)
src/Parser/ParserRegistry.php (1)
src/Parser/XliffParser.php (1)
XliffParser(11-98)
src/Parser/ParserInterface.php (1)
src/Parser/XliffParser.php (2)
getFileName(73-76)getFilePath(83-86)
src/FileDetector/Collector.php (4)
src/Parser/ParserRegistry.php (2)
ParserRegistry(7-18)getAvailableParsers(12-17)src/Parser/ParserInterface.php (2)
__construct(9-9)getSupportedFileExtensions(14-14)src/Validator/AbstractValidator.php (1)
__construct(16-18)src/FileDetector/PrefixFileDetector.php (1)
mapTranslationSet(14-25)
src/Validator/AbstractValidator.php (8)
src/Parser/ParserUtility.php (2)
ParserUtility(7-65)resolveParserClass(51-64)src/Parser/ParserInterface.php (2)
__construct(9-9)getFileName(23-23)src/FileDetector/Collector.php (1)
__construct(13-15)src/Parser/XliffParser.php (1)
getFileDirectory(78-81)src/Validator/DuplicatesValidator.php (1)
processFile(17-33)src/Validator/ValidatorInterface.php (1)
processFile(16-16)src/Validator/SchemaValidator.php (1)
processFile(16-32)src/Validator/MismatchValidator.php (2)
processFile(17-29)postProcess(31-55)
src/Validator/SchemaValidator.php (4)
src/Validator/AbstractValidator.php (2)
AbstractValidator(11-56)processFile(50-50)src/Validator/ValidatorInterface.php (3)
processFile(16-16)renderIssueSets(21-21)explain(23-23)src/Parser/ParserInterface.php (1)
getFilePath(25-25)src/Parser/XliffParser.php (1)
getFilePath(83-86)
src/Command/ValidateTranslationCommand.php (9)
src/FileDetector/Collector.php (2)
Collector(11-56)collectFiles(23-55)src/FileDetector/PrefixFileDetector.php (1)
PrefixFileDetector(7-26)src/Utility/ClassUtility.php (2)
ClassUtility(9-31)validateClass(11-30)src/Utility/PathUtility.php (2)
PathUtility(7-26)normalizeFolderPath(9-25)src/Validator/ValidatorRegistry.php (2)
ValidatorRegistry(7-20)getAvailableValidators(12-19)src/Validator/DuplicatesValidator.php (2)
explain(56-60)renderIssueSets(38-54)src/Validator/ValidatorInterface.php (2)
explain(23-23)renderIssueSets(21-21)src/Validator/SchemaValidator.php (2)
explain(65-69)renderIssueSets(37-63)src/Validator/MismatchValidator.php (2)
explain(84-88)renderIssueSets(60-82)
🪛 phpcs (3.7.2)
src/Validator/ValidatorRegistry.php
[ERROR] 2-2: Missing file doc comment
(PEAR.Commenting.FileComment.Missing)
[ERROR] 7-7: Missing doc comment for class ValidatorRegistry
(PEAR.Commenting.ClassComment.Missing)
[ERROR] 9-9: Missing short description in doc comment
(Generic.Commenting.DocComment.MissingShort)
src/Utility/PathUtility.php
[ERROR] 2-2: Missing file doc comment
(PEAR.Commenting.FileComment.Missing)
[ERROR] 7-7: Missing doc comment for class PathUtility
(PEAR.Commenting.ClassComment.Missing)
[ERROR] 9-9: Missing doc comment for function normalizeFolderPath()
(PEAR.Commenting.FunctionComment.Missing)
src/Utility/ClassUtility.php
[ERROR] 2-2: Missing file doc comment
(PEAR.Commenting.FileComment.Missing)
[ERROR] 9-9: Missing doc comment for class ClassUtility
(PEAR.Commenting.ClassComment.Missing)
[ERROR] 11-11: Missing doc comment for function validateClass()
(PEAR.Commenting.FunctionComment.Missing)
src/Validator/ValidatorInterface.php
[ERROR] 11-11: Missing doc comment for interface ValidatorInterface
(PEAR.Commenting.ClassComment.Missing)
[ERROR] 13-13: Missing short description in doc comment
(Generic.Commenting.DocComment.MissingShort)
[ERROR] 13-13: Doc comment for parameter "$file" missing
(PEAR.Commenting.FunctionComment.MissingParamTag)
[ERROR] 18-18: Missing short description in doc comment
(Generic.Commenting.DocComment.MissingShort)
[ERROR] 18-18: Doc comment for parameter "$input" missing
(PEAR.Commenting.FunctionComment.MissingParamTag)
[ERROR] 18-18: Doc comment for parameter "$output" missing
(PEAR.Commenting.FunctionComment.MissingParamTag)
[ERROR] 19-19: Missing parameter comment
(PEAR.Commenting.FunctionComment.MissingParamComment)
[ERROR] 19-19: Doc comment for parameter $issueSets does not match actual variable name $input
(PEAR.Commenting.FunctionComment.ParamNameNoMatch)
[ERROR] 20-20: Missing @return tag in function comment
(PEAR.Commenting.FunctionComment.MissingReturn)
[ERROR] 23-23: Missing doc comment for function explain()
(PEAR.Commenting.FunctionComment.Missing)
🪛 PHPStan (2.1.15)
src/FileDetector/Collector.php
34-34: Call to function method_exists() with class-string<MoveElevator\ComposerTranslationValidator\Parser\ParserInterface> and 'getSupportedFileExt…' will always evaluate to true.
Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.
(function.alreadyNarrowedType)
🔇 Additional comments (6)
src/FileDetector/PrefixFileDetector.php (1)
16-24: Excellent simplification that improves readability and maintainability.The refactored approach is much cleaner than the previous two-step process. The direct grouping by stripped basename is more straightforward and easier to understand.
The regex pattern correctly identifies language prefixes (e.g., "de.", "en.") and the grouping logic will properly associate translation files with their variants.
tests/Build/app/Language/XLIFF-fail/de.locallang_be.xlf (1)
14-17: Appropriate test case for duplicate validation.The duplicate
trans-unitwithid="key2"correctly creates a test scenario for theDuplicatesValidatorto detect and report duplicate translation keys.This test file will help ensure the refactored validation architecture properly identifies duplicate entries in XLIFF files.
README.md (1)
37-37: LGTM! Documentation path correction.The path correction from
test/totests/and the addition of the-dflag properly align the documentation with the project structure and ensure the command runs in the correct working directory.src/Parser/ParserInterface.php (1)
23-25: LGTM! Interface additions support modular architecture.The new
getFileName()andgetFilePath()methods provide necessary file metadata access for the refactored validation system. The methods are properly typed and align with the existing implementation inXliffParser.php.src/FileDetector/Collector.php (1)
40-42: LGTM! Efficient exclude pattern filtering.The double array_filter approach for exclude patterns is efficient and handles the logic correctly.
src/Validator/SchemaValidator.php (1)
16-32: Well-implemented schema validation logic.The method properly handles exceptions, logs errors appropriately, and returns the expected array structure. Good use of Symfony's XML utilities for schema validation.
| if (!ClassUtility::validateClass( | ||
| DetectorInterface::class, | ||
| $this->logger, | ||
| $input->getOption('file-detector')) | ||
| ) { | ||
| $this->io->error( | ||
| sprintf('The file detector class "%s" must implement %s.', | ||
| $input->getOption('file-detector'), | ||
| DetectorInterface::class | ||
| ) | ||
| ); | ||
|
|
||
| return Command::FAILURE; | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Extract duplicate class validation logic.
The class validation logic is duplicated for both file detector and validator. Consider extracting this into a helper method to follow DRY principles.
Add a private helper method:
private function validateAndInstantiate(string $interface, string $className, string $type): ?object
{
if (!ClassUtility::validateClass($interface, $this->logger, $className)) {
$this->io->error(
sprintf('The %s class "%s" must implement %s.', $type, $className, $interface)
);
return null;
}
return new $className();
}Then use it:
- if (!ClassUtility::validateClass(
- DetectorInterface::class,
- $this->logger,
- $input->getOption('file-detector'))
- ) {
- $this->io->error(
- sprintf('The file detector class "%s" must implement %s.',
- $input->getOption('file-detector'),
- DetectorInterface::class
- )
- );
-
- return Command::FAILURE;
- }
- $fileDetector = new ($input->getOption('file-detector'))();
+ $fileDetector = $this->validateAndInstantiate(
+ DetectorInterface::class,
+ $input->getOption('file-detector'),
+ 'file detector'
+ );
+ if (!$fileDetector) {
+ return Command::FAILURE;
+ }Also applies to: 90-103
🤖 Prompt for AI Agents
In src/Command/ValidateTranslationCommand.php around lines 61 to 74 and 90 to
103, the class validation logic for file detector and validator is duplicated.
Extract this logic into a private helper method named validateAndInstantiate
that takes the interface, class name, and type as parameters, performs the
validation, logs an error if invalid, and returns an instance of the class if
valid or null otherwise. Replace the existing validation code blocks with calls
to this new helper method to adhere to DRY principles.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
src/FileDetector/Collector.php (1)
28-33: Fix early return logic for invalid paths.The current implementation returns an empty array when encountering the first invalid path, preventing processing of subsequent valid paths. This could cause the entire validation to fail unnecessarily.
foreach ($paths as $path) { if (!(new Filesystem())->exists($path)) { - $this->logger->error('The provided path "'.$path.'" is not a valid directory.'); + $this->logger?->error('The provided path "'.$path.'" is not a valid directory.'); - - return []; + continue; }
🧹 Nitpick comments (3)
src/FileDetector/Collector.php (3)
36-36: Remove redundant method existence check.The static analysis tool correctly identifies that this check is always true since
ParserInterfaceguarantees thegetSupportedFileExtensionsmethod exists.- if (method_exists($parserClass, 'getSupportedFileExtensions')) { $files = array_filter( glob($path.'/*'), static fn ($file) => in_array(pathinfo($file, PATHINFO_EXTENSION), $parserClass::getSupportedFileExtensions(), true) ); - }
37-40: Improve file discovery efficiency and safety.The current glob approach has several limitations: it doesn't handle recursive directories, may not scale well for large directories, and doesn't validate that results are actually files.
Consider using Symfony's Finder component for more robust file discovery:
+use Symfony\Component\Finder\Finder; - $files = array_filter( - glob($path.'/*'), - static fn ($file) => in_array(pathinfo($file, PATHINFO_EXTENSION), $parserClass::getSupportedFileExtensions(), true) - ); + $finder = new Finder(); + $finder->files()->in($path)->name('/\.('.implode('|', $parserClass::getSupportedFileExtensions()).')$/'); + $files = array_map(fn ($file) => $file->getPathname(), iterator_to_array($finder));
47-48: Apply consistent null-safe logging.- $this->logger->debug('No files found for parser class "'.$parserClass.'" in path "'.$path.'".'); + $this->logger?->debug('No files found for parser class "'.$parserClass.'" in path "'.$path.'".');
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
composer.lockis excluded by!**/*.lock
📒 Files selected for processing (3)
composer.json(1 hunks)src/FileDetector/Collector.php(1 hunks)src/Validator/AbstractValidator.php(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- composer.json
🚧 Files skipped from review as they are similar to previous changes (1)
- src/Validator/AbstractValidator.php
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/FileDetector/Collector.php (4)
src/Parser/ParserRegistry.php (2)
ParserRegistry(7-18)getAvailableParsers(12-17)src/Validator/AbstractValidator.php (1)
__construct(16-18)src/Parser/ParserInterface.php (2)
__construct(9-9)getSupportedFileExtensions(14-14)src/FileDetector/PrefixFileDetector.php (1)
mapTranslationSet(14-25)
🪛 PHPStan (2.1.15)
src/FileDetector/Collector.php
36-36: Call to function method_exists() with class-string<MoveElevator\ComposerTranslationValidator\Parser\ParserInterface> and 'getSupportedFileExt…' will always evaluate to true.
Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.
(function.alreadyNarrowedType)
🔇 Additional comments (1)
src/FileDetector/Collector.php (1)
23-23: Clarify or remove ReflectionException from documentation.The method documentation indicates it can throw
ReflectionException, but the current implementation doesn't use reflection directly. This might be left over from a previous implementation or anticipate future changes.Please verify if
ReflectionExceptionis still relevant to this method's implementation or if it should be removed from the documentation.
| public function __construct(protected ?LoggerInterface $logger = null) | ||
| { | ||
| } |
There was a problem hiding this comment.
Add null check for logger usage.
The constructor accepts an optional logger but the class uses $this->logger without null checks, which could cause fatal errors.
Apply this pattern throughout the class:
- $this->logger->error('The provided path "'.$path.'" is not a valid directory.');
+ $this->logger?->error('The provided path "'.$path.'" is not a valid directory.');Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/FileDetector/Collector.php around lines 13 to 15, the constructor accepts
an optional LoggerInterface which can be null, but the class uses $this->logger
without checking if it is null. To fix this, add null checks before using
$this->logger throughout the class to ensure it is not null before calling any
methods on it, preventing potential fatal errors.
Summary by CodeRabbit
New Features
Refactor
Documentation
Style
Tests