Skip to content

HP-2419 extended functional of BillingRegistry for easier use #99

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 16 commits into
base: master
Choose a base branch
from

Conversation

VadymHrechukha
Copy link
Contributor

@VadymHrechukha VadymHrechukha commented May 6, 2025

Summary by CodeRabbit

  • New Features

    • Introduced a service interface and implementation for advanced billing registry operations, including retrieval of representations, behaviors, aggregates, and tariff/price type definitions.
    • Added new interfaces and methods for comparing tariff types and associating price types with tariff types.
    • Added a new interface for traffic collectors.
  • Improvements

    • Simplified billing registry interfaces by removing unused or redundant methods.
    • Enhanced type safety and clarity in collection interfaces and method signatures.
    • Refined method signatures to use more general interfaces and improved iteration return types.
  • Tests

    • Updated and refactored test classes to align with new interface and inheritance structures.
    • Added abstract base class for tariff type tests with comparison helpers.
    • Added comprehensive tests for the new billing registry service covering behavior retrieval and exception handling.
    • Removed redundant tests from the billing registry test suite.

Copy link
Contributor

coderabbitai bot commented May 6, 2025

Walkthrough

This change refactors the billing product registry by removing behavior, representation, quantity formatter, and aggregate logic from BillingRegistry and its interface, introducing a new BillingRegistryService to encapsulate those responsibilities. Several interfaces and classes are updated with new comparison and accessor methods, and new abstractions for traffic collectors are introduced. Test classes are adjusted for the new inheritance structure and coverage is shifted to the new service tests.

Changes

File(s) Change Summary
src/product/BillingRegistry.php, src/product/BillingRegistryInterface.php Removed methods related to behaviors, representations, aggregates, and quantity formatters; simplified to only handle tariff types and price types; added getTariffTypeDefinitions() returning an array instead of generator.
src/product/Application/BillingRegistryService.php, src/product/Application/BillingRegistryServiceInterface.php Added new service and interface encapsulating logic for behaviors, representations, aggregates, and quantity formatters previously in BillingRegistry.
src/product/Domain/Model/TariffTypeInterface.php, tests/unit/product/Domain/Model/TariffType.php Added equals and equalsName methods for tariff type comparison; introduced abstract test class implementing these methods.
src/product/Domain/Model/Unit/UnitInterface.php Added name() method to interface.
src/product/TariffTypeDefinition.php, src/product/TariffTypeDefinitionInterface.php Added belongToTariffType method to associate tariff type definitions with tariff types.
src/product/behavior/BehaviorCollectionInterface.php, src/product/invoice/RepresentationCollection.php, src/product/price/PriceTypeDefinitionCollectionInterface.php Updated to use imported IteratorAggregate and Traversable types; improved PHPDoc annotations and return types for iterators.
src/product/price/PriceTypeDefinition.php, src/product/price/PriceTypeDefinitionInterface.php, src/product/price/PriceTypeDefinitionCollection.php Added methods for accessing tariff type definitions and checking tariff type membership; updated parameter types for measuredWith to use a more general interface.
src/product/measure/TrafCollectorInterface.php Introduced new interface for traffic collectors with a getClassName() method.
tests/unit/product/Domain/Model/DummyTariffType.php, tests/unit/product/Domain/Model/FakeTariffType.php, tests/unit/product/Domain/Model/MockTariffType.php Changed test classes to extend the new abstract TariffType class instead of implementing the interface directly.
tests/unit/product/Application/BillingRegistryServiceTest.php Added new test class covering BillingRegistryService behavior and exception handling.
tests/unit/product/BillingRegistryTest.php Removed tests related to behaviors, aggregates, representations, and quantity formatters that were moved to the service; retained tests focused on tariff types and locking.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant BillingRegistryService
    participant BillingRegistry
    participant PriceTypeDefinition

    Client->>BillingRegistryService: getBehavior(type, behaviorClass)
    BillingRegistryService->>BillingRegistry: priceTypes()
    loop for each PriceTypeDefinition
        BillingRegistryService->>PriceTypeDefinition: getBehaviors()
        alt behavior found
            BillingRegistryService-->>Client: return Behavior
        end
    end
    BillingRegistryService-->>Client: throw BehaviorNotFoundException (if not found)
Loading

Possibly related PRs

  • hiqdev/php-billing#93: Introduced the initial full implementation of BillingRegistry with the methods now being refactored and moved to a service.

Suggested reviewers

  • SilverFire

Poem

In the warren of billing, a registry grew,
But too many carrots made logic askew.
Now a service hops in, with duties anew,
While tariff types gather, and tests hop through.
The code is more tidy, the bunnies agree—
Simpler, more nimble, and bug-free as can be! 🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need 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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (5)
src/product/BillingRegistryService.php (1)

113-133: Fix typo in PHPDoc annotation.

There's a typo in the PHPDoc annotation for the getBehaviors method.

-     * @inerhitDoc
+     * @inheritDoc

Otherwise, the method implementation is correct, efficiently yielding behaviors from both tariff type definitions and price types that match the specified class.

src/product/BillingRegistry.php (4)

19-25: Constructor OK but missing explicit import for the service

The delegation pattern looks good; however, to improve readability and IDE support consider adding an explicit use statement for BillingRegistryService above rather than relying on the same-namespace resolution.

+use hiqdev\php\billing\product\BillingRegistryService;

33-36: Add PHPDoc for new public getter

Public API should be documented for consumers. A short @return TariffTypeDefinitionInterface[] doc-block will help.

/**
 * @return TariffTypeDefinitionInterface[]
 */
public function getTariffTypeDefinitions(): array

64-66: Typo in annotation tag

@inerhitDoc@inheritDoc

- * @inerhitDoc
+ * @inheritDoc

77-84: Consistent explicit imports for domain interfaces

TariffTypeDefinitionInterface (and the price-related interfaces) are referenced multiple times but not imported.
While same-namespace resolution works, adding explicit use statements increases clarity and avoids errors when classes move.

Consider:

+use hiqdev\php\billing\product\TariffTypeDefinitionInterface;

(and the corresponding price interface if it lives in another sub-namespace).

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9220708 and a9e3868.

📒 Files selected for processing (13)
  • src/product/BillingRegistry.php (2 hunks)
  • src/product/BillingRegistryInterface.php (2 hunks)
  • src/product/BillingRegistryService.php (1 hunks)
  • src/product/Domain/Model/TariffTypeInterface.php (1 hunks)
  • src/product/Domain/Model/Unit/UnitInterface.php (1 hunks)
  • src/product/TariffTypeDefinition.php (2 hunks)
  • src/product/TariffTypeDefinitionInterface.php (1 hunks)
  • src/product/behavior/BehaviorCollectionInterface.php (1 hunks)
  • src/product/invoice/RepresentationCollection.php (2 hunks)
  • src/product/price/PriceTypeDefinition.php (2 hunks)
  • src/product/price/PriceTypeDefinitionCollection.php (1 hunks)
  • src/product/price/PriceTypeDefinitionCollectionInterface.php (1 hunks)
  • src/product/price/PriceTypeDefinitionInterface.php (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (10)
src/product/price/PriceTypeDefinitionCollection.php (3)
src/product/price/PriceTypeDefinitionCollectionInterface.php (1)
  • getTariffTypeDefinition (32-32)
src/product/price/PriceTypeDefinition.php (1)
  • getTariffTypeDefinition (203-206)
src/product/price/PriceTypeDefinitionInterface.php (1)
  • getTariffTypeDefinition (56-56)
src/product/Domain/Model/Unit/UnitInterface.php (1)
src/product/Domain/Model/TariffTypeInterface.php (1)
  • name (7-7)
src/product/price/PriceTypeDefinitionInterface.php (3)
src/product/price/PriceTypeDefinitionCollectionInterface.php (1)
  • getTariffTypeDefinition (32-32)
src/product/price/PriceTypeDefinitionCollection.php (1)
  • getTariffTypeDefinition (59-62)
src/product/price/PriceTypeDefinition.php (1)
  • getTariffTypeDefinition (203-206)
src/product/invoice/RepresentationCollection.php (3)
src/product/behavior/BehaviorCollectionInterface.php (1)
  • getIterator (15-15)
src/product/price/PriceTypeDefinitionCollectionInterface.php (1)
  • getIterator (15-15)
src/product/price/PriceTypeDefinitionCollection.php (1)
  • getIterator (34-37)
src/product/behavior/BehaviorCollectionInterface.php (1)
src/product/behavior/BehaviorCollection.php (1)
  • getIterator (19-22)
src/product/Domain/Model/TariffTypeInterface.php (2)
src/product/TariffTypeDefinitionInterface.php (1)
  • tariffType (15-15)
src/product/TariffTypeDefinition.php (1)
  • tariffType (32-35)
src/product/TariffTypeDefinition.php (2)
src/product/TariffTypeDefinitionInterface.php (2)
  • belongToTariffType (23-23)
  • tariffType (15-15)
src/product/Domain/Model/TariffTypeInterface.php (1)
  • equals (11-11)
src/product/TariffTypeDefinitionInterface.php (1)
src/product/TariffTypeDefinition.php (2)
  • belongToTariffType (37-40)
  • tariffType (32-35)
src/product/BillingRegistryService.php (10)
src/product/behavior/BehaviorNotFoundException.php (1)
  • BehaviorNotFoundException (7-9)
src/product/behavior/InvalidBehaviorException.php (1)
  • InvalidBehaviorException (7-9)
src/product/Exception/AggregateNotFoundException.php (1)
  • AggregateNotFoundException (7-9)
src/product/Exception/TariffTypeDefinitionNotFoundException.php (1)
  • TariffTypeDefinitionNotFoundException (7-10)
src/product/invoice/InvalidRepresentationException.php (1)
  • InvalidRepresentationException (7-10)
src/product/price/PriceTypeDefinition.php (9)
  • PriceTypeDefinition (25-212)
  • type (139-142)
  • __construct (41-53)
  • documentRepresentation (125-130)
  • createQuantityFormatter (102-111)
  • hasType (144-147)
  • withBehaviors (154-159)
  • getAggregate (188-195)
  • hasBehavior (161-170)
src/product/quantity/FractionQuantityData.php (1)
  • FractionQuantityData (7-14)
src/product/quantity/QuantityFormatterNotFoundException.php (1)
  • QuantityFormatterNotFoundException (7-9)
src/type/Type.php (2)
  • Type (18-103)
  • anyId (99-102)
src/product/BillingRegistry.php (11)
  • __construct (21-24)
  • BillingRegistry (10-91)
  • getRepresentationsByType (47-50)
  • priceTypes (38-45)
  • createQuantityFormatter (52-57)
  • getBehavior (59-62)
  • getBehaviors (67-70)
  • getTariffTypeDefinitions (33-36)
  • getAggregate (72-75)
  • findTariffTypeDefinitionByBehavior (77-80)
  • findPriceTypeDefinitionsByBehavior (82-85)
src/product/price/PriceTypeDefinitionCollectionInterface.php (7)
src/product/behavior/BehaviorCollectionInterface.php (2)
  • getIterator (15-15)
  • end (22-22)
src/product/price/PriceTypeDefinitionCollection.php (4)
  • getIterator (34-37)
  • priceType (39-47)
  • end (49-52)
  • getTariffTypeDefinition (59-62)
src/product/price/PriceTypeDefinition.php (3)
  • type (139-142)
  • end (116-120)
  • getTariffTypeDefinition (203-206)
src/product/TariffTypeDefinitionInterface.php (1)
  • end (37-37)
src/product/TariffTypeDefinition.php (1)
  • end (91-101)
src/product/behavior/BehaviorPriceTypeDefinitionCollection.php (1)
  • end (15-18)
src/product/behavior/BehaviorTariffTypeCollection.php (1)
  • end (15-18)
🪛 GitHub Check: PHP 8.3
src/product/behavior/BehaviorCollectionInterface.php

[failure] 10-10: MissingTemplateParam
src/product/behavior/BehaviorCollectionInterface.php:10:47: MissingTemplateParam: hiqdev\php\billing\product\behavior\BehaviorCollectionInterface has missing template params when extending IteratorAggregate, expecting 2 (see https://psalm.dev/182)

src/product/price/PriceTypeDefinitionCollectionInterface.php

[failure] 10-10: MissingTemplateParam
src/product/price/PriceTypeDefinitionCollectionInterface.php:10:58: MissingTemplateParam: hiqdev\php\billing\product\price\PriceTypeDefinitionCollectionInterface has missing template params when extending IteratorAggregate, expecting 2 (see https://psalm.dev/182)

🪛 GitHub Actions: Psalm Static Analysis
src/product/behavior/BehaviorCollectionInterface.php

[error] 10-10: Psalm error: MissingTemplateParam - hiqdev\php\billing\product\behavior\BehaviorCollectionInterface has missing template params when extending IteratorAggregate, expecting 2 (see https://psalm.dev/182)

🔇 Additional comments (36)
src/product/Domain/Model/Unit/UnitInterface.php (1)

9-10: Method addition is appropriate and consistent with domain model

The addition of the name() method provides a standard way to retrieve the unit name, following the same pattern used in other domain interfaces like TariffTypeInterface. This enhances the interface's usability and maintains consistency across domain objects.

src/product/price/PriceTypeDefinitionCollection.php (1)

59-62: Improves semantic clarity of the relationship

Adding getTariffTypeDefinition() provides a more explicit and intention-revealing method for accessing the parent tariff type definition compared to the existing end() method. This enhancement clarifies the relationship between price type collections and tariff type definitions, making the code more readable and easier to understand.

src/product/TariffTypeDefinitionInterface.php (1)

17-24: Well-documented method enhances domain relationship modeling

The addition of belongToTariffType() with comprehensive PHPDoc provides a clear, type-safe way to check if a tariff type definition belongs to a specific tariff type. This enhances the ability to enforce and verify relationships between domain objects in the billing system.

src/product/price/PriceTypeDefinitionInterface.php (2)

11-11: Appropriate import for new method return type

The import of TariffTypeDefinitionInterface is necessary for the new method's return type and follows proper PHP coding practices.


51-56: Well-documented method establishes clear domain relationship

The new getTariffTypeDefinition() method explicitly defines the relationship between price type definitions and tariff type definitions. The comprehensive PHPDoc clearly explains the purpose, making the code more maintainable and the domain model relationships more transparent.

src/product/invoice/RepresentationCollection.php (2)

8-8: Consistent use of imported Traversable interface.

Good addition of the Traversable import, which aligns with the best practice of using imported classes rather than relying on fully qualified names.


27-29: Improved type safety with generic return type.

The update from RepresentationInterface[] to Traversable<RepresentationInterface> in the PHPDoc and changing the method signature from \Traversable to the imported Traversable interface improves type safety and makes the code more consistent with other collection interfaces in the codebase.

src/product/behavior/BehaviorCollectionInterface.php (2)

8-8: Good addition of imported Traversable interface.

Using the imported Traversable interface rather than the fully qualified name improves readability and consistency.


13-15: Improved type clarity with generic Traversable.

The update from a simple array return type to Traversable<BehaviorInterface> in the PHPDoc improves type safety and makes the code more consistent with other collection interfaces.

src/product/TariffTypeDefinition.php (2)

13-15: Explicit generic type implementation.

Good addition of the PHPDoc that explicitly states the generic type parameter being used when implementing the interface. This improves type safety and code clarity.


37-40: Well-implemented tariff type equality check.

The new belongToTariffType method provides a clear way to check if this definition belongs to a specific tariff type by leveraging the equals method of the tariff type interface. This implementation aligns well with the related interface changes.

src/product/Domain/Model/TariffTypeInterface.php (1)

11-13: Enhanced comparison capabilities.

The addition of equals and equalsName methods provides useful functionality for comparing tariff types both by reference and by name. These methods support the enhanced relationship management between tariff types and price types throughout the codebase.

src/product/price/PriceTypeDefinition.php (3)

17-17: Good addition of the required import.

The import for TariffTypeDefinitionInterface is correctly added to support the new methods being implemented.


203-206: Well-implemented delegation method.

Good implementation of the getTariffTypeDefinition() method that delegates to the parent collection. This maintains a clean hierarchy and follows the principle of single responsibility by letting each level of the hierarchy handle its own concerns.


208-211: Effective implementation of tariff type relationship check.

The belongsToTariffType method provides a convenient way to check if a price type belongs to a specific tariff type, improving usability of the API. The method correctly uses the tariff type definition's equalsName method for comparison.

src/product/price/PriceTypeDefinitionCollectionInterface.php (4)

8-8: Good import of the Traversable interface.

Explicitly importing the Traversable interface instead of using the fully qualified name improves code readability.


13-15: Improved return type documentation and implementation.

The return type of getIterator() has been correctly updated to use Traversable<PriceTypeDefinitionInterface> in the PHPDoc and the method signature now returns Traversable instead of \Traversable, which is more consistent with the imported interface.


19-24: Good addition of the end() method with appropriate PHPDoc.

The end() method provides a fluent interface to return to the parent tariff type definition, following the builder pattern. The PHPDoc correctly specifies the return type with template parameter annotation.


25-32: Well-documented relationship method.

The getTariffTypeDefinition() method with explanatory PHPDoc clarifies the relationship between PriceTypeDefinitionCollection and TariffTypeDefinition, making the code more maintainable and easier to understand.

src/product/BillingRegistryInterface.php (6)

18-19: Improved PHPDoc for priceTypes() method.

The PHPDoc has been enhanced to specify the return type as Generator<PriceTypeDefinitionInterface>, which improves type safety and code understanding.


22-22: Changed return type specification for getTariffTypeDefinitions().

The return type has been changed from Generator to array, which is more appropriate since the method returns a collection of all tariff type definitions at once rather than yielding them one by one.


44-49: Enhanced PHPDoc for getBehaviors method.

The documentation for the getBehaviors method has been improved with a clearer description of its purpose and a more specific return type annotation.


51-51: Added method to find tariff type definition by behavior.

This new method provides a convenient way to locate a tariff type definition based on a behavior instance, enhancing the API's functionality.


53-59: Well-documented method for finding price type definitions by behavior.

The new findPriceTypeDefinitionsByBehavior method with its descriptive PHPDoc improves the API by adding the ability to search for price type definitions based on behavior class, making the system more flexible.


61-61: Repositioned method without signature changes.

The getAggregate method was moved to the end of the interface without changes to its signature, maintaining backward compatibility while organizing the interface methods more logically.

src/product/BillingRegistryService.php (9)

1-24: Well-structured service class implementation.

The new BillingRegistryService class is correctly implemented with proper namespace, imports, and constructor injection. This follows good practices for service classes by accepting the dependency (BillingRegistry) through the constructor.


25-47: Comprehensive implementation of representation retrieval.

The getRepresentationsByType method includes proper validation of the representation class existence and interface implementation before proceeding with the search. The method effectively collects all matching representations from price types.


49-62: Well-implemented quantity formatter creation.

The createQuantityFormatter method correctly converts the string type to a TypeInterface and searches through price types to find and return the appropriate formatter, throwing an exception if not found.


64-67: Simple helper method for type conversion.

The convertStringTypeToType method encapsulates the conversion logic from string to TypeInterface, promoting code reuse and maintainability.


69-98: Thorough implementation of behavior retrieval with validation.

The getBehavior method includes comprehensive validation of the behavior class existence and interface implementation, and efficiently searches through price types to find the matching behavior.


100-111: Useful helper method for finding behaviors in price types.

The findBehaviorInPriceType method extracts the behavior search logic into a reusable method, which improves code organization and readability.


135-146: Well-implemented aggregate retrieval.

The getAggregate method effectively converts the type string, searches for a matching price type, and returns its aggregate or throws an appropriate exception if not found.


148-159: Effective implementation of tariff type definition lookup by behavior.

The findTariffTypeDefinitionByBehavior method efficiently extracts the tariff type from the behavior and searches for a matching tariff type definition, throwing an appropriate exception if not found.


161-168: Efficient generator implementation for finding price types by behavior.

The findPriceTypeDefinitionsByBehavior method uses a generator to yield price type definitions with the specified behavior class, which is memory-efficient for potentially large collections.

src/product/BillingRegistry.php (2)

40-40: Nice substitution of direct property access with accessor

Using the accessor improves encapsulation and keeps future refactors safer.


72-75: Missing import for AggregateInterface

Unless AggregateInterface is in the same namespace, calls will fail with Class "hiqdev\php\billing\product\AggregateInterface" not found.

+use hiqdev\php\billing\product\aggregate\AggregateInterface;

Validate the correct FQN/path and add the import accordingly.

@@ -5,15 +5,29 @@
use hiqdev\php\billing\product\TariffTypeDefinitionInterface;
use hiqdev\php\billing\product\trait\HasLockInterface;
use hiqdev\php\billing\type\TypeInterface;
use Traversable;

interface PriceTypeDefinitionCollectionInterface extends \IteratorAggregate, \Countable, HasLockInterface
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix the missing template parameter in interface definition.

The interface is extending IteratorAggregate without specifying the required template parameters.

#!/bin/bash
# Check how IteratorAggregate is typically used in PHP 8.x
rg -A 3 'implements\s+\\\?IteratorAggregate.+<' --type php

Apply this fix:

-interface PriceTypeDefinitionCollectionInterface extends \IteratorAggregate, \Countable, HasLockInterface
+interface PriceTypeDefinitionCollectionInterface extends \IteratorAggregate<int, PriceTypeDefinitionInterface>, \Countable, HasLockInterface
🧰 Tools
🪛 GitHub Check: PHP 8.3

[failure] 10-10: MissingTemplateParam
src/product/price/PriceTypeDefinitionCollectionInterface.php:10:58: MissingTemplateParam: hiqdev\php\billing\product\price\PriceTypeDefinitionCollectionInterface has missing template params when extending IteratorAggregate, expecting 2 (see https://psalm.dev/182)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/product/price/PriceTypeDefinitionCollectionInterface.php (1)

10-10: Fix the missing template parameter in interface definition.

The interface is extending IteratorAggregate without specifying the required template parameters.

Apply this fix:

-interface PriceTypeDefinitionCollectionInterface extends \IteratorAggregate, \Countable, HasLockInterface
+interface PriceTypeDefinitionCollectionInterface extends \IteratorAggregate<int, PriceTypeDefinitionInterface>, \Countable, HasLockInterface
🧰 Tools
🪛 GitHub Check: PHP 8.3

[failure] 10-10: MissingTemplateParam
src/product/price/PriceTypeDefinitionCollectionInterface.php:10:58: MissingTemplateParam: hiqdev\php\billing\product\price\PriceTypeDefinitionCollectionInterface has missing template params when extending IteratorAggregate, expecting 2 (see https://psalm.dev/182)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a9e3868 and 02f0958.

📒 Files selected for processing (3)
  • src/product/price/PriceTypeDefinition.php (3 hunks)
  • src/product/price/PriceTypeDefinitionCollectionInterface.php (1 hunks)
  • src/product/price/PriceTypeDefinitionInterface.php (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/product/price/PriceTypeDefinitionInterface.php
🧰 Additional context used
🧬 Code Graph Analysis (2)
src/product/price/PriceTypeDefinition.php (4)
src/type/Type.php (1)
  • Type (18-103)
src/product/price/PriceTypeDefinitionInterface.php (4)
  • getTariffTypeDefinition (56-56)
  • belongsToTariffType (58-58)
  • belongsToPriceType (60-60)
  • type (34-34)
src/product/TariffTypeDefinitionInterface.php (1)
  • tariffType (15-15)
src/product/Domain/Model/TariffTypeInterface.php (2)
  • equalsName (13-13)
  • name (7-7)
src/product/price/PriceTypeDefinitionCollectionInterface.php (9)
src/product/behavior/BehaviorCollectionInterface.php (2)
  • getIterator (15-15)
  • end (22-22)
src/product/price/PriceTypeDefinitionCollection.php (3)
  • getIterator (34-37)
  • end (49-52)
  • getTariffTypeDefinition (59-62)
src/product/behavior/BehaviorCollection.php (1)
  • getIterator (19-22)
src/product/price/PriceTypeDefinitionInterface.php (3)
  • type (34-34)
  • end (27-27)
  • getTariffTypeDefinition (56-56)
src/product/price/PriceTypeDefinition.php (3)
  • type (140-143)
  • end (117-121)
  • getTariffTypeDefinition (204-207)
src/product/TariffTypeDefinitionInterface.php (1)
  • end (37-37)
src/product/TariffTypeDefinition.php (1)
  • end (91-101)
src/product/behavior/BehaviorPriceTypeDefinitionCollection.php (1)
  • end (15-18)
src/product/behavior/BehaviorTariffTypeCollection.php (1)
  • end (15-18)
🪛 GitHub Check: PHP 8.3
src/product/price/PriceTypeDefinition.php

[failure] 216-216: UndefinedClass
src/product/price/PriceTypeDefinition.php:216:30: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\Type\TypeSemantics does not exist (see https://psalm.dev/019)

src/product/price/PriceTypeDefinitionCollectionInterface.php

[failure] 10-10: MissingTemplateParam
src/product/price/PriceTypeDefinitionCollectionInterface.php:10:58: MissingTemplateParam: hiqdev\php\billing\product\price\PriceTypeDefinitionCollectionInterface has missing template params when extending IteratorAggregate, expecting 2 (see https://psalm.dev/182)

🔇 Additional comments (5)
src/product/price/PriceTypeDefinition.php (2)

204-207: LGTM! Good method implementation.

The getTariffTypeDefinition() method correctly delegates to the parent collection's corresponding method, establishing a proper relationship hierarchy between price type and tariff type definitions.


209-212: LGTM! Useful relationship check.

The belongsToTariffType method provides a convenient way to determine if a price type belongs to a specific tariff type by name, utilizing the existing equalsName method from the tariff type interface.

src/product/price/PriceTypeDefinitionCollectionInterface.php (3)

8-8: LGTM! Improved type safety.

The explicit import of Traversable and updating the return type annotation for getIterator() with proper generic types enhances type safety and better communicates the interface's behavior.

Also applies to: 13-15


19-23: LGTM! Clear method documentation.

The added PHPDoc for the end() method clearly specifies the return type and includes template parameter information for improved type checking.


25-32: LGTM! Well-documented relationship method.

The new getTariffTypeDefinition() method with comprehensive documentation clearly establishes the relationship between PriceTypeDefinitionCollection and TariffTypeDefinition, making the code more maintainable and self-explanatory.

This addition complements the new method in PriceTypeDefinition that delegates to this method, creating a consistent way to navigate the relationship hierarchy.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/product/BillingRegistry.php (2)

19-22: Bidirectional dependency between BillingRegistry and BillingRegistryService

The BillingRegistry class creates an instance of BillingRegistryService and passes itself to it, forming a bidirectional dependency. While this pattern can work, it may lead to tight coupling that could make testing more difficult.

Consider using dependency injection instead:

- private BillingRegistryService $service;
+ private BillingRegistryServiceInterface $service;

- public function __construct()
+ public function __construct(BillingRegistryServiceInterface $service = null)
  {
-     $this->service = new BillingRegistryService($this);
+     $this->service = $service ?? new BillingRegistryService($this);
  }

This would allow for easier mocking in tests and more flexible configuration.


62-64: Fix typo in PHPDoc tag

There's a typo in the @inheritDoc tag.

- /**
-  * @inerhitDoc
-  */
+ /**
+  * @inheritDoc
+  */
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d993cd0 and 405f064.

📒 Files selected for processing (1)
  • src/product/BillingRegistry.php (3 hunks)
🔇 Additional comments (2)
src/product/BillingRegistry.php (2)

16-83: Good implementation of the Facade pattern

The refactoring to delegate core logic to a dedicated service class is a good architectural decision. This separation of concerns:

  1. Makes the code more maintainable by centralizing complex logic in the service
  2. Allows BillingRegistry to focus on its role as a registry
  3. Improves testability by allowing the service to be potentially mocked

The implementation consistently delegates method calls to the service while preserving the public API.


31-34:

✅ Verification successful

Return type change from Generator to array

The return type of getTariffTypeDefinitions() has been changed from Generator to array. This is a breaking change that might affect code relying on lazy evaluation.


🏁 Script executed:

#!/bin/bash
# Check if there are any clients that might be affected by this return type change
rg -A 2 -B 2 "getTariffTypeDefinitions\(\)" --glob="!src/product/BillingRegistry.php"

Length of output: 1456


Document interface return type update for getTariffTypeDefinitions()

The getTariffTypeDefinitions() method’s signature has been changed from returning a Generator to returning an array. All existing internal consumers (notably in src/product/BillingRegistryService.php) use foreach, which works identically on arrays, so there are no runtime errors introduced by this change.

Please ensure:

  • The PHPDoc blocks on getTariffTypeDefinitions() in src/product/BillingRegistry.php and src/product/BillingRegistryInterface.php are updated to @return TariffTypeDefinitionInterface[].
  • A note is added to your CHANGELOG (or release notes) to record this breaking change at the interface level.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/product/measure/TrafCollectorInterface.php (2)

5-15: Improve documentation to focus on interface contract rather than implementations.

The current DocBlock focuses heavily on specific implementation files rather than clearly defining the contract this interface represents. Consider restructuring to:

  1. Explain what a traffic collector is and its purpose
  2. Define the expected behavior of implementations
  3. Move implementation examples to a separate @see or @example section
/**
- - Websa Metering Service - The counters are in a Websa Metering Service
- Set references to classes in
- {@see vendor/advancedhosters/hiapi-metering-collector/src/Collectors/CdnTrafMaxCollector.php}
- 
- - RCP Traf collector
- {@see vendor/advancedhosters/hiapi-rcp-traf/src/collectors/ServerDuCollector.php}
- 
- - PeriodicDbQuery - Support Time (stored in our DB)
- Pass a DBMS function name that handles this price type measurement
+ * Interface for traffic collection mechanisms used in billing measurements.
+ * 
+ * Traffic collectors are responsible for retrieving traffic usage data from
+ * various sources to be used in billing calculations.
+ *
+ * @see vendor/advancedhosters/hiapi-metering-collector/src/Collectors/CdnTrafMaxCollector.php Websa Metering Service implementation
+ * @see vendor/advancedhosters/hiapi-rcp-traf/src/collectors/ServerDuCollector.php RCP Traf collector implementation
+ * @see PeriodicDbQuery Support Time (stored in our DB)
- 
- This interface is not completed this is only a scratch
 */

20-20: Add documentation for the getClassName() method.

The purpose of the getClassName() method isn't clear. Consider adding a DocBlock to explain why this method is needed and what it should return.

interface TrafCollectorInterface
{
+    /**
+     * Returns the fully qualified class name of the collector implementation.
+     * 
+     * This is used for identification and registry purposes within the billing system.
+     * 
+     * @return string Fully qualified class name
+     */
    public function getClassName(): string;
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 405f064 and be55965.

📒 Files selected for processing (6)
  • src/product/behavior/BehaviorCollectionInterface.php (1 hunks)
  • src/product/invoice/RepresentationCollection.php (2 hunks)
  • src/product/measure/TrafCollectorInterface.php (1 hunks)
  • src/product/price/PriceTypeDefinition.php (4 hunks)
  • src/product/price/PriceTypeDefinitionCollectionInterface.php (1 hunks)
  • src/product/price/PriceTypeDefinitionInterface.php (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/product/behavior/BehaviorCollectionInterface.php
  • src/product/invoice/RepresentationCollection.php
  • src/product/price/PriceTypeDefinitionCollectionInterface.php
  • src/product/price/PriceTypeDefinitionInterface.php
  • src/product/price/PriceTypeDefinition.php

Comment on lines +18 to +21
interface TrafCollectorInterface
{
public function getClassName(): string;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider expanding this minimal interface definition.

While the interface definition is syntactically correct, it currently only contains a single method getClassName() that appears to be for identification purposes. Since this interface is intended to abstract traffic collection mechanisms (as indicated in the AI summary), consider:

  1. Adding methods that actually define traffic collection behavior
  2. Including method docblocks to explain the purpose of each method
  3. Removing the "scratch" status before using in production code
interface TrafCollectorInterface
{
    public function getClassName(): string;
+    
+    /**
+     * Collects traffic data for the specified resource within the given timeframe
+     * 
+     * @param string $resourceId  Identifier of the resource to measure
+     * @param \DateTimeInterface $startTime  Start of the collection period
+     * @param \DateTimeInterface $endTime  End of the collection period
+     * @return mixed  The collected traffic data
+     */
+    public function collectTraffic(string $resourceId, \DateTimeInterface $startTime, \DateTimeInterface $endTime);
}
📝 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.

Suggested change
interface TrafCollectorInterface
{
public function getClassName(): string;
}
interface TrafCollectorInterface
{
public function getClassName(): string;
/**
* Collects traffic data for the specified resource within the given timeframe
*
* @param string $resourceId Identifier of the resource to measure
* @param \DateTimeInterface $startTime Start of the collection period
* @param \DateTimeInterface $endTime End of the collection period
* @return mixed The collected traffic data
*/
public function collectTraffic(string $resourceId, \DateTimeInterface $startTime, \DateTimeInterface $endTime);
}

Comment on lines +16 to +17
* This interface is not completed this is only a scratch
*/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Remove draft status indication before using in production.

The DocBlock explicitly states "This interface is not completed this is only a scratch". Based on the AI summary, this interface is already being integrated into the codebase by replacing specific type hints in the PriceTypeDefinition classes. Draft/incomplete interfaces should be properly completed before being used in production code.

Let's check where this interface is being used:


🏁 Script executed:

#!/bin/bash
# Check where TrafCollectorInterface is being used in the codebase
grep -r "TrafCollectorInterface" --include="*.php" .

Length of output: 603


🏁 Script executed:

#!/bin/bash
# Display the contents of TrafCollectorInterface.php for review
sed -n '1,200p' src/product/measure/TrafCollectorInterface.php

Length of output: 735


Remove draft status in TrafCollectorInterface docblock

The interface is already referenced by PriceTypeDefinition and PriceTypeDefinitionInterface, yet its docblock still marks it as “not completed… scratch.” Before shipping, please remove or replace that draft note with a clear description of the interface’s purpose.

  • File: src/product/measure/TrafCollectorInterface.php, docblock (lines 5–12)
    • Remove the “This interface is not completed this is only a scratch” sentence
    • Update the summary to explain what getClassName() returns and how implementations should behave

Example diff:

 /**
  * - Websa Metering Service - The counters are in a Websa Metering Service
  * Set references to classes in
  * {@see vendor/advancedhosters/hiapi-metering-collector/src/Collectors/CdnTrafMaxCollector.php}
  *
  * - RCP Traf collector
  * {@see vendor/advancedhosters/hiapi-rcp-traf/src/collectors/ServerDuCollector.php}
  *
  * - PeriodicDbQuery - Support Time (stored in our DB)
  * Pass a DBMS function name that handles this price type measurement
- * This interface is not completed this is only a scratch
+ * Returns the fully qualified class name of a traffic collector implementation.
  */
 interface TrafCollectorInterface
 {
     public function getClassName(): string;
 }

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (5)
src/product/BillingRegistryInterface.php (1)

16-18: Add PHPDoc to keep interface self-documenting and consistent

getTariffTypeDefinitions() and addTariffType() miss PHPDoc blocks, while priceTypes() already provides one. Adding them helps IDEs/static-analysis and keeps the public contract explicit.

@@
-    public function getTariffTypeDefinitions(): array;
+    /**
+     * Return **all** tariff-type definitions registered in the system.
+     *
+     * @return TariffTypeDefinitionInterface[]
+     */
+    public function getTariffTypeDefinitions(): array;
@@
-    public function addTariffType(TariffTypeDefinitionInterface $tariffTypeDefinition): void;
+    /**
+     * Register a new tariff-type definition.
+     */
+    public function addTariffType(TariffTypeDefinitionInterface $tariffTypeDefinition): void;
src/product/Application/BillingRegistryServiceInterface.php (1)

5-5: Redundant use Generator; import

Because \Generator is a built-in global class, it’s available without importing. The extra use isn’t harmful but adds noise. You can safely drop it:

-use Generator;
src/product/BillingRegistry.php (2)

21-24: Add return-type PHPDoc for static analysers

Returning a plain array hides the element type. A one-liner keeps tools happy:

-    public function getTariffTypeDefinitions(): array
+    /**
+     * @return TariffTypeDefinitionInterface[]
+     */
+    public function getTariffTypeDefinitions(): array

26-33: Use yield from for a more idiomatic generator

Nestled foreach loops work, but yield from both flattens the code and signals intent:

-        foreach ($this->getTariffTypeDefinitions() as $tariffTypeDefinition) {
-            foreach ($tariffTypeDefinition->withPrices() as $priceTypeDefinition) {
-                yield $priceTypeDefinition;
-            }
-        }
+        foreach ($this->getTariffTypeDefinitions() as $tariffTypeDefinition) {
+            yield from $tariffTypeDefinition->withPrices();
+        }

The behaviour and memory profile stay identical while improving readability.

src/product/Application/BillingRegistryService.php (1)

41-47: Possible duplicate / order-dependent collection

$representations may end up with duplicate objects when several price-type definitions share the same representation instance.
If uniqueness matters (e.g. JSON-serialisation, UI listing), consider using SplObjectStorage or array_unique() keyed by spl_object_id() / class name.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between be55965 and 27bec10.

📒 Files selected for processing (8)
  • src/product/Application/BillingRegistryService.php (1 hunks)
  • src/product/Application/BillingRegistryServiceInterface.php (1 hunks)
  • src/product/BillingRegistry.php (1 hunks)
  • src/product/BillingRegistryInterface.php (1 hunks)
  • tests/unit/product/Domain/Model/DummyTariffType.php (1 hunks)
  • tests/unit/product/Domain/Model/FakeTariffType.php (1 hunks)
  • tests/unit/product/Domain/Model/MockTariffType.php (1 hunks)
  • tests/unit/product/Domain/Model/TariffType.php (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (5)
tests/unit/product/Domain/Model/DummyTariffType.php (1)
tests/unit/product/Domain/Model/TariffType.php (1)
  • TariffType (7-18)
tests/unit/product/Domain/Model/MockTariffType.php (1)
tests/unit/product/Domain/Model/TariffType.php (1)
  • TariffType (7-18)
tests/unit/product/Domain/Model/FakeTariffType.php (1)
tests/unit/product/Domain/Model/TariffType.php (1)
  • TariffType (7-18)
src/product/BillingRegistryInterface.php (1)
src/product/BillingRegistry.php (3)
  • priceTypes (26-33)
  • getTariffTypeDefinitions (21-24)
  • addTariffType (14-19)
tests/unit/product/Domain/Model/TariffType.php (3)
tests/unit/product/Domain/Model/DummyTariffType.php (1)
  • name (7-10)
tests/unit/product/Domain/Model/FakeTariffType.php (1)
  • name (7-10)
tests/unit/product/Domain/Model/MockTariffType.php (1)
  • name (7-10)
🔇 Additional comments (8)
tests/unit/product/Domain/Model/TariffType.php (1)

7-18: Well-designed abstract class for comparison functionality!

This new abstract class efficiently centralizes the equality comparison logic for tariff types:

  • equals() method allows comparison between tariff type instances
  • equalsName() method enables direct string name comparison
  • Both methods use the name() method that concrete classes must implement

This approach follows good OO design principles by:

  1. Eliminating code duplication across implementations
  2. Providing a consistent comparison mechanism
  3. Maintaining a clear separation of concerns
tests/unit/product/Domain/Model/DummyTariffType.php (1)

5-5: Good refactoring to use the abstract class.

The change from directly implementing TariffTypeInterface to extending the abstract TariffType class is a good refactoring approach. This leverages the shared implementation of equals and equalsName methods provided by the abstract class, promoting code reuse and consistency across tariff type implementations.

tests/unit/product/Domain/Model/FakeTariffType.php (1)

5-5: Good inheritance structure change.

Changing from direct interface implementation to extending the abstract TariffType class follows good OOP practices by reusing the common equality comparison logic. This maintains consistent behavior across all tariff type test classes while reducing code duplication.

tests/unit/product/Domain/Model/MockTariffType.php (1)

5-5: Appropriate use of inheritance for shared behavior.

The change to extend the abstract TariffType class rather than directly implementing the interface is appropriate. This allows MockTariffType to inherit the standard implementations of equals and equalsName methods while retaining its specific implementations of name() and label(). This approach ensures consistency in comparison behavior across all tariff type implementations.

src/product/Application/BillingRegistryServiceInterface.php (1)

26-34: Consider documenting (or typing) possible “not found” outcomes

getBehavior() specifies two exception types, but other look-ups (getAggregate, findTariffTypeDefinitionByBehavior, etc.) don’t advertise failure modes. If they can legitimately “miss”, choose one of:

• Return null and reflect it in the return type (?AggregateInterface)
• Throw a domain-specific exception and add @throws to the PHPDoc

Making the contract explicit prevents unexpected TypeErrors or hidden null usage.

src/product/BillingRegistry.php (1)

35-38: Locking only protects definitions, not contained mutability

afterLock() forwards the lock to each TariffTypeDefinition, which is great. Ensure that:

  1. Every definition’s own withPrices() returns price definitions already locked (or immutable).
  2. Down-stream code doesn’t mutate price definitions post-lock.

Otherwise, external callers could still alter objects retrieved via priceTypes(). If that’s already covered, feel free to ignore.

src/product/Application/BillingRegistryService.php (2)

52-62: Missing fallback when createQuantityFormatter() returns null

The loop assumes that the first price-type that “has the type” will always be able to create a formatter.
If an implementation returns null (or throws) the service will bubble the exception instead of probing the remaining definitions.

-                return $priceTypeDefinition->createQuantityFormatter($data);
+                $formatter = $priceTypeDefinition->createQuantityFormatter($data);
+                if ($formatter !== null) {
+                    return $formatter;
+                }

Alternatively, check the interface contract and document the expectation.


132-143: Ambiguous aggregate resolution when multiple price-types share a type

getAggregate() returns the aggregate of the first matching price-type.
If the registry can legally contain several definitions for the same billing type, results become order-dependent.
Consider:

• Clarifying the expectation in PHPDoc, or
• Enforcing uniqueness in BillingRegistryInterface, or
• Returning all aggregates (\Generator) so the caller decides.

Comment on lines +113 to +130
public function getBehaviors(string $behaviorClassWrapper): \Generator
{
foreach ($this->registry->getTariffTypeDefinitions() as $tariffTypeDefinition) {
foreach ($tariffTypeDefinition->withBehaviors() as $behavior) {
if ($behavior instanceof $behaviorClassWrapper) {
yield $behavior;
}
}
}

foreach ($this->registry->priceTypes() as $priceTypeDefinition) {
foreach ($priceTypeDefinition->withBehaviors() as $behavior) {
if ($behavior instanceof $behaviorClassWrapper) {
yield $behavior;
}
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add class existence / interface checks to prevent fatal errors

See previous comment. Before the two nested loops, add:

if (!class_exists($behaviorClassWrapper) ||
    !is_subclass_of($behaviorClassWrapper, BehaviorInterface::class)) {
    throw new InvalidBehaviorException(
        sprintf('Behavior class "%s" is invalid or does not implement %s',
            $behaviorClassWrapper,
            BehaviorInterface::class
        )
    );
}

Comment on lines +71 to +81
if (!class_exists($behaviorClassWrapper)) {
throw new InvalidBehaviorException(
sprintf('Behavior class "%s" does not exist', $behaviorClassWrapper)
);
}

if (!is_subclass_of($behaviorClassWrapper, BehaviorInterface::class)) {
throw new InvalidBehaviorException(
sprintf('Behavior class "%s" does not implement BehaviorInterface', $behaviorClassWrapper)
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Validation logic duplicated & missing in sibling methods

Excellent job validating $behaviorClassWrapper here.
However, the same guard clauses are absent in getBehaviors() and findPriceTypeDefinitionsByBehavior().
Calling those methods with an unknown class string will raise a fatal Error: Class "<name>" not found.

Extract the validation into a small private helper and reuse it.

Comment on lines +158 to +165
public function findPriceTypeDefinitionsByBehavior(string $behaviorClassWrapper): \Generator
{
foreach ($this->registry->priceTypes() as $priceTypeDefinition) {
if ($priceTypeDefinition->hasBehavior($behaviorClassWrapper)) {
yield $priceTypeDefinition;
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Same validation gap as in getBehaviors()

Add the class_exists/is_subclass_of guard here as well to avoid runtime fatals.

Comment on lines +30 to +38
if (!class_exists($representationClass)) {
throw new InvalidRepresentationException("Class '$representationClass' does not exist");
}

if (!is_subclass_of($representationClass, RepresentationInterface::class)) {
throw new InvalidBehaviorException(
sprintf('Representation class "%s" does not implement RepresentationInterface', $representationClass)
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Incorrect exception type for representation-interface validation

When the provided class is not a RepresentationInterface, InvalidBehaviorException is thrown.
That is confusing, because the error is about representation misuse, not behavior misuse, and clients relying on the more specific InvalidRepresentationException will silently miss it.

-            throw new InvalidBehaviorException(
-                sprintf('Representation class "%s" does not implement RepresentationInterface', $representationClass)
-            );
+            throw new InvalidRepresentationException(
+                sprintf('Representation class "%s" does not implement %s', $representationClass, RepresentationInterface::class)
+            );
📝 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.

Suggested change
if (!class_exists($representationClass)) {
throw new InvalidRepresentationException("Class '$representationClass' does not exist");
}
if (!is_subclass_of($representationClass, RepresentationInterface::class)) {
throw new InvalidBehaviorException(
sprintf('Representation class "%s" does not implement RepresentationInterface', $representationClass)
);
}
if (!class_exists($representationClass)) {
throw new InvalidRepresentationException("Class '$representationClass' does not exist");
}
if (!is_subclass_of($representationClass, RepresentationInterface::class)) {
- throw new InvalidBehaviorException(
- sprintf('Representation class "%s" does not implement RepresentationInterface', $representationClass)
- );
+ throw new InvalidRepresentationException(
+ sprintf('Representation class "%s" does not implement %s', $representationClass, RepresentationInterface::class)
+ );
}

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (4)
tests/unit/product/Application/BillingRegistryServiceTest.php (4)

61-96: Test method naming is inconsistent with PHP conventions.

The method name uses snake_case (testGetBehavior_WithMultipleTariffTypeDefinitions) which is inconsistent with PHP's PSR-1/PSR-2 coding standards that recommend camelCase for method names.

Otherwise, the test logic is solid, effectively testing behavior retrieval across multiple tariff type definitions.

-    public function testGetBehavior_WithMultipleTariffTypeDefinitions(): void
+    public function testGetBehaviorWithMultipleTariffTypeDefinitions(): void

71-85: Complex builder chain could be simplified for better readability.

The nested builder chain makes the test setup complex and harder to follow. Consider extracting this configuration into a helper method to improve test readability.

+    private function createTariffTypeDefinitionWithBehaviors(DummyTariffType $tariffType, array $typeAndBehaviors): TariffTypeDefinition
+    {
+        $definition = new TariffTypeDefinition($tariffType);
+        $builder = $definition->withPrices();
+        
+        foreach ($typeAndBehaviors as $item) {
+            $builder = $builder
+                ->priceType($item['type'])
+                ->withBehaviors();
+                
+            foreach ($item['behaviors'] as $behavior) {
+                $builder = $builder->attach($behavior);
+            }
+            
+            $builder = $builder->end()->end();
+        }
+        
+        return $builder->end();
+    }

     public function testGetBehaviorWithMultipleTariffTypeDefinitions(): void
     {
         $tariffType = new DummyTariffType();
-        $tariffTypeDefinition = new TariffTypeDefinition($tariffType);
         $type1 = Type::anyId('type,dummy1');
         $type2 = Type::anyId('type,dummy2');
         $dummyBehavior1 = new TestBehavior('dummy 1');
         $dummyBehavior2 = new TestBehavior('dummy 2');
         $dummyBehavior3 = new FakeBehavior('dummy 3');

-        $tariffTypeDefinition
-            ->withPrices()
-            ->priceType($type1)
-            ->withBehaviors()
-            ->attach($dummyBehavior1)
-            ->end()
-            ->end()
-            ->priceType($type2)
-            ->withBehaviors()
-            ->attach($dummyBehavior2)
-            ->attach($dummyBehavior3)
-            ->end()
-            ->end()
-            ->end();
+        $tariffTypeDefinition = $this->createTariffTypeDefinitionWithBehaviors(
+            $tariffType,
+            [
+                [
+                    'type' => $type1,
+                    'behaviors' => [$dummyBehavior1]
+                ],
+                [
+                    'type' => $type2,
+                    'behaviors' => [$dummyBehavior2, $dummyBehavior3]
+                ]
+            ]
+        );

98-134: Test method naming is inconsistent with PHP conventions.

Similar to the previous test method, this one also uses snake_case which is inconsistent with PHP's PSR-1/PSR-2 coding standards.

The test logic effectively verifies behavior retrieval with multiple price type definitions.

-    public function testGetBehavior_WithMultiplePriceTypeDefinitions(): void
+    public function testGetBehaviorWithMultiplePriceTypeDefinitions(): void

142-146: Address commented-out test.

There's a commented-out test for createQuantityFormatter. Either implement this test if the functionality is ready, remove it if it's not needed, or add a TODO comment explaining why it's commented out and when it will be implemented.

-    //    public function testCreateQuantityFormatterThrowsExceptionWhenNotFound(): void
-//    {
-//        $this->expectException(QuantityFormatterNotFoundException::class);
-//        $this->registryService->createQuantityFormatter('non-existent-type', $this->createMock(FractionQuantityData::class));
-//    }
+    /**
+     * @todo Implement this test when the createQuantityFormatter functionality is ready
+     */
+    // public function testCreateQuantityFormatterThrowsExceptionWhenNotFound(): void
+    // {
+    //     $this->expectException(QuantityFormatterNotFoundException::class);
+    //     $this->registryService->createQuantityFormatter('non-existent-type', $this->createMock(FractionQuantityData::class));
+    // }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 27bec10 and 8eba047.

📒 Files selected for processing (2)
  • tests/unit/product/Application/BillingRegistryServiceTest.php (1 hunks)
  • tests/unit/product/BillingRegistryTest.php (0 hunks)
💤 Files with no reviewable changes (1)
  • tests/unit/product/BillingRegistryTest.php
🧰 Additional context used
🧬 Code Graph Analysis (1)
tests/unit/product/Application/BillingRegistryServiceTest.php (9)
src/product/Application/BillingRegistryService.php (1)
  • BillingRegistryService (22-166)
src/product/behavior/BehaviorNotFoundException.php (1)
  • BehaviorNotFoundException (7-9)
src/product/Exception/AggregateNotFoundException.php (1)
  • AggregateNotFoundException (7-9)
src/product/invoice/InvalidRepresentationException.php (1)
  • InvalidRepresentationException (7-10)
tests/unit/product/behavior/FakeBehavior.php (1)
  • FakeBehavior (5-8)
tests/unit/product/behavior/TestBehavior.php (1)
  • TestBehavior (7-20)
tests/unit/product/Domain/Model/DummyTariffType.php (1)
  • DummyTariffType (5-16)
tests/unit/product/Domain/Model/FakeTariffType.php (1)
  • FakeTariffType (5-16)
src/type/Type.php (2)
  • Type (18-103)
  • anyId (99-102)
🔇 Additional comments (6)
tests/unit/product/Application/BillingRegistryServiceTest.php (6)

18-29: Good setup with proper dependency injection.

The class has a clear setup with proper initialization of the BillingRegistry and BillingRegistryService objects. The dependency is correctly injected into the service following the dependency injection pattern.


30-35: Well-structured test for representation type validation.

This test properly verifies that the service throws an InvalidRepresentationException when passed an invalid class name. The expectation is set correctly before calling the method under test.


36-41: Good exception testing for aggregate retrieval.

The test correctly verifies the exception handling behavior when attempting to retrieve a non-existent aggregate type.


42-59: Comprehensive behavior retrieval test.

The test effectively sets up a tariff type definition with behaviors and verifies that the service correctly retrieves the expected behavior. Good use of assertSame to confirm the exact instance is returned.


65-67: Verify the Type identifiers format.

The type identifiers use a comma separator (type,dummy1 and type,dummy2). Make sure this format is consistent with how your application processes type identifiers elsewhere.


136-140: Effective test for behavior not found scenario.

The test properly verifies that the service throws a BehaviorNotFoundException when attempting to retrieve a non-existent behavior.

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