Skip to content
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

HP-1751 Create configurations for billing types #93

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

Conversation

VadymHrechukha
Copy link
Contributor

@VadymHrechukha VadymHrechukha commented Nov 28, 2024

Summary by CodeRabbit

  • New Features

    • Introduced a robust billing system enhancement for streamlined tariff management and dynamic invoice generation.
    • Added contracts for consistent product naming and labeling, along with expanded pricing definitions and quantity formatting for precise billing.
    • Implemented new classes for managing tariff types, price types, and invoice representations, enhancing the overall structure and functionality of billing processes.
    • Added new interfaces for behavior collections and aggregate handling to improve modularity and extensibility.
    • Introduced new exception handling for quantity formatting and aggregate not found scenarios.
    • Added support for generating invoice descriptions and managing price type definitions.
  • Tests

    • Implemented unit tests to ensure the reliability and accuracy of tariff and billing operations.
  • Refactor

    • Reorganized behavior components to improve code clarity and maintainability.
  • Chores

    • Updated development dependencies in composer.json.

Copy link
Contributor

coderabbitai bot commented Nov 28, 2024

Walkthrough

This pull request expands the billing product module with the introduction of new classes, interfaces, factories, and test suites. Key components include a BillingRegistry for managing tariff types, an InvoiceDescriptionsBuilder for generating invoice descriptions, and various interfaces defining behaviors and pricing structures. Additionally, new exception classes enhance error handling, while method signatures and namespaces have been updated for improved type safety and organization. The changes also introduce factory methods and collections to support billing operations, along with unit tests for validation.

Changes

File(s) Change Summary
src/product/BillingRegistry.php; src/product/BillingRegistryInterface.php; src/product/InvoiceDescriptionsBuilder.php Added BillingRegistry implementation, its interface with a priceTypes() generator, and an InvoiceDescriptionsBuilder class that generates invoice descriptions based on price types.
src/product/ProductInterface.php; src/product/TariffTypeDefinition.php; src/product/TariffTypeDefinitionFactory.php; tests/unit/product/TariffTypeTest.php; src/product/ParentNodeDefinitionInterface.php Introduced a new ProductInterface, TariffTypeDefinition classes with fluent API methods, a factory for creating tariff type definitions, corresponding unit tests, and an updated interface method signature for behavior collections.
src/product/DocumentRepresentationInterface.php; src/product/AggregateInterface.php; src/product/AggregateNotFoundException.php Added new interfaces for document representation and aggregates, alongside an exception class for cases where an aggregate is not found.
src/product/behavior/BehaviorCollection.php; src/product/behavior/BehaviorCollectionInterface.php; src/product/behavior/BehaviorInterface.php; src/product/behavior/BehaviorNotFoundException.php; src/product/behavior/BehaviorTariffTypeCollection.php; src/product/behavior/BehaviourPriceTypeDefinitionCollection.php Restructured behavior-related classes with updated namespaces, changed the collection to implement a new interface, and added specialized behavior collections for tariff and price type definitions.
src/product/Domain/Model/TariffTypeInterface.php; src/product/Domain/Model/Unit/FractionUnitInterface.php; src/product/Domain/Model/Unit/UnitInterface.php Minor modifications (newlines) to domain interfaces for tariff types and units to enforce strict type declarations.
src/product/quantity/QuantityFormatterInterface.php; src/product/quantity/QuantityFormatterNotFoundException.php; src/product/quantity/FractionQuantityData.php; src/product/quantity/QuantityFormatterDefinition.php; src/product/quantity/QuantityFormatterFactory.php Introduced a new interface for quantity formatting, its associated exception, a data class to encapsulate fraction quantity details, a definition class for formatter configuration, and a factory to create formatter instances.
src/product/invoice/InvoiceRepresentationCollection.php; src/product/invoice/RepresentationInterface.php Added InvoiceRepresentationCollection and its interface, allowing attachment, filtering, and iterative operations over representation objects.
src/product/price/PriceTypeDefinition.php; src/product/price/PriceTypeDefinitionCollection.php; src/product/price/PriceTypeInterface.php; src/product/price/PriceTypeStorage.php; src/product/GTypeInterface.php Added comprehensive pricing classes including a PriceTypeDefinition with quantity formatting and behavior support, a collection and storage mechanism for price types, an interface for price type naming, and a new interface for comparing GType instances.
composer.json Added a new development dependency on opis/closure.

Sequence Diagram(s)

sequenceDiagram
    participant Builder as InvoiceDescriptionsBuilder
    participant Registry as BillingRegistry
    participant Price as PriceType
    Builder->>Registry: priceTypes()
    Registry-->>Builder: Generator of Price instances
    loop For each Price instance
        Builder->>Price: representInvoice()
    end
    Builder->>Builder: Return invoice descriptions array
Loading

Suggested reviewers

  • SilverFire

Poem

I hopped through lines of brand new code,
Where tariff types and prices now flowed.
Invoice tales in arrays take flight,
Behaviors and formats gleam so bright.
With every change, my heart does thump —
A joyful hop in our rabbit bump 🐇!

✨ Finishing Touches
  • 📝 Generate Docstrings

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

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 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: 21

🧹 Outside diff range and nitpick comments (15)
src/product/ServerProduct.php (2)

1-6: Add class documentation.

Please add PHPDoc documentation to describe the purpose and responsibility of this class within the billing system. This will help other developers understand how ServerProduct fits into the billing type configurations.

 <?php declare(strict_types=1);

 namespace hiqdev\php\billing\product;

+/**
+ * Represents a server product in the billing system.
+ * This class handles billing configuration for server-related products.
+ */
 class ServerProduct implements ProductInterface

5-11: Consider expanding the server product implementation.

The current implementation seems minimal for a server product in a billing system. Consider adding:

  1. Properties to store server specifications (e.g., CPU, RAM, storage)
  2. Methods to handle pricing tiers based on specifications
  3. Integration with the billing type configurations mentioned in the PR title

This will make the class more useful for real-world billing scenarios.

src/product/InvoiceDescriptionsBuilder.php (2)

7-12: Consider adding validation in the constructor.

While the dependency injection is good, consider validating that the registry has price types to fail fast if the registry is empty or invalid.

 public function __construct(BillingRegistry $registry)
 {
+    if (empty($registry->priceTypes())) {
+        throw new \InvalidArgumentException('Registry must contain price types');
+    }
     $this->registry = $registry;
 }

5-6: Consider defining interfaces for better abstraction.

Since this is part of a billing system that other modules might need to extend:

  1. Consider extracting an interface (e.g., InvoiceDescriptionsBuilderInterface)
  2. Consider making the class final if it's not meant to be extended
interface InvoiceDescriptionsBuilderInterface
{
    public function build(): array;
}

final class InvoiceDescriptionsBuilder implements InvoiceDescriptionsBuilderInterface
{
    // ... existing implementation
}
src/product/PriceTypesCollection.php (3)

1-6: Add class documentation.

Please add a PHPDoc block to describe the class purpose, responsibilities, and usage.

 <?php declare(strict_types=1);

 namespace hiqdev\php\billing\product;

+/**
+ * Collection of price type definitions for a tariff.
+ * Manages the creation and storage of different price types (monthly, overuse)
+ * within the context of a tariff configuration.
+ */
 class PriceTypesCollection

7-7: Enhance property type safety and documentation.

The property type could be more specific to ensure type safety.

-    private array $prices = [];
+    /** @var array<string, PriceTypeDefinition> */
+    private array $prices = [];

5-29: Consider adding helper methods for price type management.

The collection would benefit from additional methods to improve its functionality and maintainability.

Consider adding these methods:

  1. getPriceType(string $type): ?PriceTypeDefinition - Retrieve a specific price type
  2. hasPriceType(string $type): bool - Check if a price type exists
  3. getAllPriceTypes(): array - Get all defined price types
  4. removePriceType(string $type): void - Remove a price type

This would make the collection more flexible and easier to work with in different contexts.

src/product/BillingRegistry.php (3)

5-9: Add PHPDoc documentation for class and properties.

The class and its properties would benefit from comprehensive PHPDoc blocks to improve code maintainability and IDE support.

+/**
+ * Registry for managing tariff types and their associated price types.
+ * Once locked, the registry prevents further modifications to ensure data integrity.
+ */
 class BillingRegistry
 {
+    /**
+     * @var TariffType[] Array of registered tariff types
+     */
     private array $tariffTypes = [];
+
+    /**
+     * @var bool Whether the registry is locked for modifications
+     */
     private bool $locked = false;

19-22: Add method to check lock state.

Consider adding a public method to check if the registry is locked, which would be useful for conditional logic in consuming code.

 public function lock(): void
 {
     $this->locked = true;
 }
+
+/**
+ * @return bool Whether the registry is locked for modifications
+ */
+public function isLocked(): bool
+{
+    return $this->locked;
+}

5-32: Consider architectural improvements for dependency injection and testing.

As a central component of the billing system, consider these architectural improvements:

  1. Make the class implement an interface for better dependency injection and testing
  2. Consider adding a factory method for creating pre-configured instances
  3. Add methods to export the configuration for validation or persistence

Example interface:

interface BillingRegistryInterface
{
    public function addTariffType(TariffType $tariffType): void;
    public function lock(): void;
    public function isLocked(): bool;
    public function priceTypes(): \Generator;
    public function export(): array;
}
src/product/PriceTypeDefinition.php (1)

5-48: Consider architectural improvements

A few architectural suggestions to enhance the robustness of this class:

  1. Consider making the class immutable by cloning in builder methods
  2. Consider extracting an interface to define the contract
  3. Consider adding a build() method that returns a PriceType and keeping collection building separate

This would improve thread safety and separation of concerns.

src/product/TariffType.php (2)

39-42: Remove unnecessary method

The withBehaviors method doesn't serve any purpose as it only returns $this. Consider removing it or implementing actual functionality.


5-56: Consider architectural improvements

Several architectural improvements could make the class more robust:

  1. Define a TariffTypeInterface to make the contract explicit
  2. Add getters for accessing the configured state
  3. Make the class immutable after finalization
  4. Add proper exception classes instead of using generic ones

Would you like me to help with implementing these architectural improvements?

tests/unit/product/TariffTypeTest.php (2)

21-22: Use ::class constant instead of string literal

Using string literals for class names makes the test brittle to refactoring. Consider using the ::class constant for better maintainability.

 $tariffType = new TariffType('server');
-$tariffType->ofProduct('ServerProductClass');
+$tariffType->ofProduct(ServerProduct::class);

67-76: Improve helper method documentation and reusability

The helper method could benefit from:

  1. Complete PHPDoc with parameter and return types
  2. Moving to a shared test trait for reuse across test classes
 /**
  * Helper function to access private properties for testing.
+ * 
+ * @param object $object Object to inspect
+ * @param string $propertyName Name of the private property
+ * @return mixed Value of the private property
  */
 private function getPrivateProperty($object, $propertyName)

Consider creating a TestHelperTrait with this method to avoid duplication across test classes.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 54f3d75 and 818174f.

📒 Files selected for processing (9)
  • src/product/BehaviorInterface.php (1 hunks)
  • src/product/BillingRegistry.php (1 hunks)
  • src/product/InvoiceDescriptionsBuilder.php (1 hunks)
  • src/product/PriceTypeDefinition.php (1 hunks)
  • src/product/PriceTypesCollection.php (1 hunks)
  • src/product/ProductInterface.php (1 hunks)
  • src/product/ServerProduct.php (1 hunks)
  • src/product/TariffType.php (1 hunks)
  • tests/unit/product/TariffTypeTest.php (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • src/product/BehaviorInterface.php
  • src/product/ProductInterface.php
🔇 Additional comments (2)
src/product/InvoiceDescriptionsBuilder.php (1)

1-6: LGTM! Clean and well-structured class declaration.

The namespace is properly defined and the class name clearly indicates its responsibility.

tests/unit/product/TariffTypeTest.php (1)

11-17: LGTM! Well-structured initialization test

The test properly verifies both the name initialization and the prices collection type.

Comment on lines 7 to 10
public function name(): string
{
// TODO: Implement name() method.
}
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

⚠️ Potential issue

Implement the required name() method to fulfill the ProductInterface contract

The verification confirms that:

  • ServerProduct implements ProductInterface
  • name(): string is a required interface method
  • No other implementations exist in the codebase to reference
  • The current TODO implementation will cause a runtime error

The method must be properly implemented to return a string value that identifies the server product.

🔗 Analysis chain

Implement the required name() method.

The current TODO implementation will cause a runtime error as it doesn't return a string value as required by the return type declaration. This method is crucial for product identification in the billing system.

Please implement this method before merging. The implementation should return a meaningful name that identifies the server product in the billing context.

Let's verify if there are any existing implementations we can reference:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other implementations of the name() method in the codebase
ast-grep --pattern 'function name(): string { $$$$ }'

Length of output: 55


Script:

#!/bin/bash
# Let's try a broader search for name() method implementations
rg "function name\(\)" -A 3

# Also search for the interface definition to understand the contract
rg "interface.*Product" -A 10

Length of output: 576

Comment on lines +14 to +22
public function build(): array
{
$descriptions = [];
foreach ($this->registry->priceTypes() as $priceType) {
$descriptions[] = $priceType->representInvoice();
}

return $descriptions;
}
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

Add error handling and consider using array_map.

The build method could benefit from several improvements:

  1. Error handling for potential failures in representInvoice()
  2. Using array_map for a more functional approach
  3. PHPDoc explaining the return format
+/**
+ * Builds an array of invoice descriptions from registered price types.
+ *
+ * @return array<int, mixed> Array of invoice descriptions
+ * @throws \RuntimeException When failed to represent a price type
+ */
 public function build(): array
 {
-    $descriptions = [];
-    foreach ($this->registry->priceTypes() as $priceType) {
-        $descriptions[] = $priceType->representInvoice();
+    try {
+        return array_map(
+            static fn($priceType) => $priceType->representInvoice(),
+            $this->registry->priceTypes()
+        );
+    } catch (\Throwable $e) {
+        throw new \RuntimeException(
+            'Failed to build invoice descriptions: ' . $e->getMessage(),
+            0,
+            $e
+        );
     }
-
-    return $descriptions;
 }
📝 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
public function build(): array
{
$descriptions = [];
foreach ($this->registry->priceTypes() as $priceType) {
$descriptions[] = $priceType->representInvoice();
}
return $descriptions;
}
/**
* Builds an array of invoice descriptions from registered price types.
*
* @return array<int, mixed> Array of invoice descriptions
* @throws \RuntimeException When failed to represent a price type
*/
public function build(): array
{
try {
return array_map(
static fn($priceType) => $priceType->representInvoice(),
$this->registry->priceTypes()
);
} catch (\Throwable $e) {
throw new \RuntimeException(
'Failed to build invoice descriptions: ' . $e->getMessage(),
0,
$e
);
}
}

Comment on lines +17 to +19
foreach ($this->registry->priceTypes() as $priceType) {
$descriptions[] = $priceType->representInvoice();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

Integration verification reveals potential issues

The codebase analysis shows a potential mismatch in the implementation. While InvoiceDescriptionsBuilder calls representInvoice() on price types, the actual implementation shows:

  • BillingRegistry::priceTypes() exists but returns a Generator
  • Price types are managed through PriceTypesCollection and PriceTypeDefinition
  • PriceTypeDefinition has invoiceRepresentation() method, but no representInvoice() method
  • No class or interface was found implementing the required representInvoice() method

This suggests that either:

  1. The representInvoice() method is missing from the implementation
  2. There's a naming mismatch between the expected and actual method names
  3. The implementation might be using a different pattern than what's being called
🔗 Analysis chain

Verify integration with dependent components.

Let's ensure the required methods exist in the dependent classes.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if BillingRegistry and price types have required methods

# Check BillingRegistry for priceTypes method
ast-grep --pattern 'class BillingRegistry {
  $$$
  public function priceTypes() {
    $$$
  }
  $$$
}'

# Check for classes implementing representInvoice method
ast-grep --pattern 'class $_ {
  $$$
  public function representInvoice() {
    $$$
  }
  $$$
}'

Length of output: 204


Script:

#!/bin/bash
# Let's try a different approach using ripgrep to find these methods

# Search for BillingRegistry class and priceTypes method
echo "=== Searching for BillingRegistry and priceTypes ==="
rg "class BillingRegistry" -A 10
rg "function priceTypes"

# Search for representInvoice method
echo -e "\n=== Searching for representInvoice method ==="
rg "function representInvoice"

# Search for any interface that might define representInvoice
echo -e "\n=== Searching for interfaces that might define representInvoice ==="
rg "interface.*Price" -A 10

Length of output: 4025


Script:

#!/bin/bash
# Let's check for TariffType class and its relationship with representInvoice

# Search for TariffType class/interface
echo "=== Searching for TariffType definition ==="
rg "class TariffType|interface TariffType" -A 10

# Search for files that might contain representInvoice in price or tariff related files
echo -e "\n=== Searching for representInvoice in price/tariff related files ==="
fd -e php | grep -i "price\|tariff" | xargs rg "representInvoice"

# Let's also check if there's an interface that might define this method
echo -e "\n=== Searching for interfaces that might define invoice representation ==="
rg "interface.*Invoice" -A 10

Length of output: 1992


Script:

#!/bin/bash
# Let's check PriceTypesCollection and its relationship with invoice representation

# Search for PriceTypesCollection
echo "=== Searching for PriceTypesCollection ==="
rg "class PriceTypesCollection" -A 10

# Search for any invoice-related methods in price types
echo -e "\n=== Searching for invoice-related methods in price types ==="
rg -i "invoice.*function|function.*invoice" -A 3

# Let's also check if there are any invoice-related traits
echo -e "\n=== Searching for invoice-related traits ==="
rg "trait.*Invoice" -A 10

Length of output: 1407

Comment on lines 25 to 28
public function end(): TariffType
{
// Return to the parent TariffType context
}
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

Critical: Implement the end() method.

The end() method is incomplete and will cause a fatal error when called.

The method needs proper implementation to return to the parent TariffType context. Based on the builder pattern suggested by the method name, it likely needs a reference to the parent TariffType:

 class PriceTypesCollection
 {
+    private TariffType $parent;
+
+    public function __construct(TariffType $parent)
+    {
+        $this->parent = $parent;
+    }
+
     // ... other methods ...

     public function end(): TariffType
     {
-        // Return to the parent TariffType context
+        return $this->parent;
     }
 }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 9 to 23
public function monthly(string $type): PriceTypeDefinition
{
$priceType = new PriceTypeDefinition();
$this->prices[$type] = $priceType;

return $priceType;
}

public function overuse(string $type): PriceTypeDefinition
{
$priceType = new PriceTypeDefinition();
$this->prices[$type] = $priceType;

return $priceType;
}
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

Refactor duplicate code and add validation.

The monthly() and overuse() methods contain identical implementation, which violates the DRY principle. Additionally, there's no validation of the input type or handling of duplicate entries.

Consider refactoring like this:

+    private function createPriceType(string $type): PriceTypeDefinition
+    {
+        if (empty($type)) {
+            throw new \InvalidArgumentException('Price type cannot be empty');
+        }
+        
+        if (isset($this->prices[$type])) {
+            throw new \RuntimeException("Price type '$type' already exists");
+        }
+        
+        $priceType = new PriceTypeDefinition();
+        $this->prices[$type] = $priceType;
+        
+        return $priceType;
+    }
+
     public function monthly(string $type): PriceTypeDefinition
     {
-        $priceType = new PriceTypeDefinition();
-        $this->prices[$type] = $priceType;
-
-        return $priceType;
+        return $this->createPriceType($type);
     }

     public function overuse(string $type): PriceTypeDefinition
     {
-        $priceType = new PriceTypeDefinition();
-        $this->prices[$type] = $priceType;
-
-        return $priceType;
+        return $this->createPriceType($type);
     }
📝 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
public function monthly(string $type): PriceTypeDefinition
{
$priceType = new PriceTypeDefinition();
$this->prices[$type] = $priceType;
return $priceType;
}
public function overuse(string $type): PriceTypeDefinition
{
$priceType = new PriceTypeDefinition();
$this->prices[$type] = $priceType;
return $priceType;
}
private function createPriceType(string $type): PriceTypeDefinition
{
if (empty($type)) {
throw new \InvalidArgumentException('Price type cannot be empty');
}
if (isset($this->prices[$type])) {
throw new \RuntimeException("Price type '$type' already exists");
}
$priceType = new PriceTypeDefinition();
$this->prices[$type] = $priceType;
return $priceType;
}
public function monthly(string $type): PriceTypeDefinition
{
return $this->createPriceType($type);
}
public function overuse(string $type): PriceTypeDefinition
{
return $this->createPriceType($type);
}

Comment on lines 15 to 19
public function __construct(string $name)
{
$this->name = $name;
$this->prices = new PriceTypesCollection();
}
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 input validation and handle required properties

The constructor should:

  • Validate that name is not empty
  • Either require productClass in constructor or mark it nullable

Apply this diff:

     public function __construct(string $name)
     {
+        if (empty(trim($name))) {
+            throw new \InvalidArgumentException('Tariff name cannot be empty');
+        }
         $this->name = $name;
         $this->prices = new PriceTypesCollection();
     }

Consider also adding productClass to constructor since it appears to be required:

-    private string $productClass;
+    private ?string $productClass = null;
📝 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
public function __construct(string $name)
{
$this->name = $name;
$this->prices = new PriceTypesCollection();
}
public function __construct(string $name)
{
if (empty(trim($name))) {
throw new \InvalidArgumentException('Tariff name cannot be empty');
}
$this->name = $name;
$this->prices = new PriceTypesCollection();
}

Comment on lines +5 to +7
use hiqdev\php\billing\product\PriceTypesCollection;
use hiqdev\php\billing\product\TariffType;
use PHPUnit\Framework\TestCase;
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 missing imports for behavior-related classes

The test uses OncePerMonthPlanChangeBehavior but it's not imported. Also, it would be beneficial to import BehaviorInterface for proper type hinting.

Add these imports:

 use hiqdev\php\billing\product\PriceTypesCollection;
 use hiqdev\php\billing\product\TariffType;
+use hiqdev\php\billing\product\BehaviorInterface;
+use hiqdev\php\billing\product\OncePerMonthPlanChangeBehavior;
 use PHPUnit\Framework\TestCase;
📝 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
use hiqdev\php\billing\product\PriceTypesCollection;
use hiqdev\php\billing\product\TariffType;
use PHPUnit\Framework\TestCase;
use hiqdev\php\billing\product\PriceTypesCollection;
use hiqdev\php\billing\product\TariffType;
use hiqdev\php\billing\product\BehaviorInterface;
use hiqdev\php\billing\product\OncePerMonthPlanChangeBehavior;
use PHPUnit\Framework\TestCase;

Comment on lines +33 to +35
$tariffType = new TariffType('server');
$behavior = new OncePerMonthPlanChangeBehavior();
$tariffType->attach($behavior);
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

Add type hints for better type safety

The behavior attachment test should verify that the behavior implements BehaviorInterface. Consider adding type hints and assertions.

 $tariffType = new TariffType('server');
 $behavior = new OncePerMonthPlanChangeBehavior();
+$this->assertInstanceOf(BehaviorInterface::class, $behavior);
 $tariffType->attach($behavior);
📝 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
$tariffType = new TariffType('server');
$behavior = new OncePerMonthPlanChangeBehavior();
$tariffType->attach($behavior);
$tariffType = new TariffType('server');
$behavior = new OncePerMonthPlanChangeBehavior();
$this->assertInstanceOf(BehaviorInterface::class, $behavior);
$tariffType->attach($behavior);

Comment on lines +50 to +52
$priceType = $prices->monthly('support_time');
$priceType->unit('hour')->description('Monthly fee for support time');
$priceType->end();
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

Add assertions for price type configuration

The test configures the price type but doesn't verify if the unit and description were set correctly.

 $priceType = $prices->monthly('support_time');
 $priceType->unit('hour')->description('Monthly fee for support time');
 $priceType->end();
+
+$this->assertEquals('hour', $this->getPrivateProperty($priceType, 'unit'));
+$this->assertEquals('Monthly fee for support time', $this->getPrivateProperty($priceType, 'description'));
📝 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
$priceType = $prices->monthly('support_time');
$priceType->unit('hour')->description('Monthly fee for support time');
$priceType->end();
$priceType = $prices->monthly('support_time');
$priceType->unit('hour')->description('Monthly fee for support time');
$priceType->end();
$this->assertEquals('hour', $this->getPrivateProperty($priceType, 'unit'));
$this->assertEquals('Monthly fee for support time', $this->getPrivateProperty($priceType, 'description'));

Comment on lines +57 to +65
public function testEndLocksTariffType(): void
{
$tariffType = new TariffType('server');
$tariffType->end();

// Assuming TariffType has a `locked` private property
$isLocked = $this->getPrivateProperty($tariffType, 'locked');
$this->assertTrue($isLocked, 'TariffType should be locked after calling end().');
}
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

Add test cases for locked state behavior

The test verifies that the tariff type is locked but doesn't test that operations after locking throw exceptions. Consider adding test cases for operations attempted after locking.

 $tariffType = new TariffType('server');
 $tariffType->end();

-// Assuming TariffType has a `locked` private property
 $isLocked = $this->getPrivateProperty($tariffType, 'locked');
 $this->assertTrue($isLocked, 'TariffType should be locked after calling end().');
+
+$this->expectException(\RuntimeException::class);
+$tariffType->ofProduct(ServerProduct::class);

Committable suggestion skipped: line range outside the PR's diff.

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: 1

🧹 Outside diff range and nitpick comments (4)
tests/unit/product/InvoiceDescriptionsBuilderTest.php (4)

19-22: Consider extracting hardcoded values to constants or data providers.

The test contains hardcoded values in closures ($100, $50). This makes the test less maintainable and harder to update if these values need to change.

Consider refactoring like this:

+ private const MONTHLY_SUPPORT_FEE = 100;
+ private const OVERUSE_SUPPORT_FEE = 50;

- ->invoiceRepresentation(function () {
-     return 'Invoice for support_time (monthly): $100';
- })
+ ->invoiceRepresentation(function () {
+     return sprintf('Invoice for support_time (monthly): $%d', self::MONTHLY_SUPPORT_FEE);
+ })

Also applies to: 27-30


11-35: Consider breaking down the test setup.

The test setup is quite complex with multiple configurations. Consider breaking it down into smaller, focused test methods or using data providers.

You could split this into separate test methods:

  • testMonthlyInvoiceDescription()
  • testOveruseInvoiceDescription()

38-40: Add assertions for registry state.

The test should verify that the registry is properly configured and locked.

Consider adding these assertions:

 $billingRegistry->addTariffType($serverTariffType);
 $billingRegistry->lock();
+$this->assertTrue($billingRegistry->isLocked(), 'Registry should be locked');
+$this->assertCount(1, $billingRegistry->getTariffTypes(), 'Registry should contain exactly one tariff type');

47-60: Consider adding more robust assertions.

While the current assertions verify the basic functionality, consider adding:

  • Null/empty checks
  • Structure validation instead of exact string matches
  • Edge case handling

Example additions:

+$this->assertNotEmpty($invoiceDescriptions, 'Invoice descriptions should not be empty');
+$this->assertMatchesRegularExpression(
+    '/Invoice for support_time \(monthly\): \$\d+/',
+    $invoiceDescriptions[0],
+    'Monthly invoice should match expected format'
+);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 818174f and 7aa8fcc.

📒 Files selected for processing (1)
  • tests/unit/product/InvoiceDescriptionsBuilderTest.php (1 hunks)
🔇 Additional comments (2)
tests/unit/product/InvoiceDescriptionsBuilderTest.php (2)

1-8: LGTM! Class declaration follows best practices.

The class is properly set up with strict types, correct namespace, and proper TestCase extension.


14-14: Verify the external class reference.

The class \hiapi\legacy\lib\billing\price\suggester\device\ServerPricesSuggester is referenced but might not be available in the test environment.

Comment on lines 9 to 68
public function testInvoiceDescriptionsBuilderWithRealClasses()
{
// Create TariffType with real prices and behaviors
$serverTariffType = (new TariffType('server'))
->ofProduct(ServerProduct::class)
->setPricesSuggester(\hiapi\legacy\lib\billing\price\suggester\device\ServerPricesSuggester::class)
->withPrices()
->monthly('support_time')
->unit('hour')
->description('Monthly fee for support time')
->quantityFormatter(MonthlyQuantityFormatter::class)
->invoiceRepresentation(function () {
return 'Invoice for support_time (monthly): $100';
})
->end()
->overuse('support_time')
->unit('hour')
->description('Support time overuse')
->quantityFormatter(HourBasedOveruseQuantityFormatter::class)
->invoiceRepresentation(function () {
return 'Invoice for support_time (overuse): $50';
})
->end()
->end() // Returns control to TariffType
->withBehaviors()
->attach(new OncePerMonthPlanChangeBehavior())
->end();

// Create BillingRegistry and add the TariffType
$billingRegistry = new BillingRegistry();
$billingRegistry->addTariffType($serverTariffType);
$billingRegistry->lock();

// Build invoice descriptions
$builder = new InvoiceDescriptionsBuilder($billingRegistry);
$invoiceDescriptions = $builder->build();

// Verify the results
$this->assertIsArray($invoiceDescriptions, 'build() should return an array of invoice descriptions.');
$this->assertCount(2, $invoiceDescriptions, 'There should be 2 invoice descriptions generated.');

$this->assertSame(
'Invoice for support_time (monthly): $100',
$invoiceDescriptions[0],
'The first description should match the expected monthly invoice description.'
);

$this->assertSame(
'Invoice for support_time (overuse): $50',
$invoiceDescriptions[1],
'The second description should match the expected overuse invoice description.'
);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

Based on the implementation findings, I can now provide the final response:

Additional test scenarios needed for InvoiceDescriptionsBuilder

The current test coverage is insufficient. Based on the implementation, add test cases for:

  • Attempting to build descriptions with an unlocked registry (verify proper handling)
  • Empty registry (no tariff types added)
  • Registry with multiple tariff types
  • Registry throwing RuntimeException when attempting modifications after being locked

The implementation shows that BillingRegistry has important validation logic that should be covered in the tests, particularly around the locking mechanism and empty states.

🔗 Analysis chain

Verify test coverage for InvoiceDescriptionsBuilder.

The class only tests the happy path with valid inputs. Consider adding tests for:

  • Invalid configurations
  • Empty registry
  • Unlocked registry

Let me gather more information about the test coverage by checking the actual implementation of InvoiceDescriptionsBuilder and its usage.


Let me check the actual implementation to understand what scenarios need testing.


Let me check the BillingRegistry implementation since it's a dependency of InvoiceDescriptionsBuilder.


Let me check the BillingRegistry implementation directly.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for other test files related to InvoiceDescriptionsBuilder
fd -e php -g "*InvoiceDescriptionsBuilder*Test.php"

# Check for test methods in this file
ast-grep --pattern 'function test[A-Za-z]*\(\)'

Length of output: 157


Script:

#!/bin/bash
# Find the implementation file
fd -e php InvoiceDescriptionsBuilder.php

# Check for usage of InvoiceDescriptionsBuilder in the codebase
rg "InvoiceDescriptionsBuilder" -l

# Look for the implementation to understand what scenarios need testing
ast-grep --pattern 'class InvoiceDescriptionsBuilder'

Length of output: 273


Script:

#!/bin/bash
# Check the implementation of InvoiceDescriptionsBuilder
cat src/product/InvoiceDescriptionsBuilder.php

# Look for any error handling or validation in the implementation
rg -A 5 "throw|assert" src/product/InvoiceDescriptionsBuilder.php

Length of output: 615


Script:

#!/bin/bash
# Find and check BillingRegistry implementation
fd -e php BillingRegistry.php

# Look for validation or error handling in BillingRegistry
rg -A 5 "throw|assert|Exception" --type php BillingRegistry

Length of output: 181


Script:

#!/bin/bash
# Check BillingRegistry implementation
cat src/product/BillingRegistry.php

# Look for any test files related to BillingRegistry
fd -e php -g "*BillingRegistry*Test.php"

Length of output: 805

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

🧹 Outside diff range and nitpick comments (3)
src/product/PriceTypeDefinition.php (1)

43-47: Improve the end() method implementation

The current implementation has several architectural issues:

  1. It creates a new empty collection instead of returning to the parent collection
  2. No validation of required properties
  3. The state isn't properly locked after finalization

Consider these architectural improvements:

  1. Add a reference to the parent collection
  2. Validate all required properties before finalization
  3. Implement state locking to prevent modifications after end() is called
  4. Return the parent collection for continued builder chain

Example implementation structure:

class PriceTypeDefinition
{
    private PriceTypesCollection $parent;
    private bool $isFinalized = false;

    public function __construct(PriceTypesCollection $parent)
    {
        $this->parent = $parent;
    }

    private function ensureNotFinalized(): void
    {
        if ($this->isFinalized) {
            throw new \RuntimeException('Cannot modify finalized price type definition');
        }
    }

    public function end(): PriceTypesCollection
    {
        $this->validate();
        $this->isFinalized = true;
        return $this->parent;
    }

    private function validate(): void
    {
        // Validate all required properties
    }
}
src/product/TariffType.php (1)

41-44: Implement withBehaviors method

The method currently returns self without any implementation. Consider adding behavior validation or initialization logic.

tests/unit/product/InvoiceDescriptionsBuilderTest.php (1)

27-29: Simplify representation logic

The document representation setup is complex and might be better extracted into a separate helper method or fixture.

Consider creating a helper method to set up document representations to improve test readability.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 7aa8fcc and d8d3535.

📒 Files selected for processing (6)
  • src/product/BillingRegistry.php (1 hunks)
  • src/product/BillingRegistryInterface.php (1 hunks)
  • src/product/DocumentRepresentationInterface.php (1 hunks)
  • src/product/PriceTypeDefinition.php (1 hunks)
  • src/product/TariffType.php (1 hunks)
  • tests/unit/product/InvoiceDescriptionsBuilderTest.php (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • src/product/DocumentRepresentationInterface.php
  • src/product/BillingRegistryInterface.php
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/product/BillingRegistry.php
🧰 Additional context used
🪛 GitHub Check: PHP 8.3
src/product/TariffType.php

[failure] 11-11: UndefinedClass
src/product/TariffType.php:11:13: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\product\Product does not exist (see https://psalm.dev/019)


[failure] 23-23: UndefinedClass
src/product/TariffType.php:23:31: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\product\Product does not exist (see https://psalm.dev/019)

🔇 Additional comments (11)
src/product/PriceTypeDefinition.php (3)

5-14: Add class-level documentation and property initialization

The class lacks PHPDoc documentation and property initialization, which could lead to undefined property access.


15-41: Add input validation and method documentation

The builder methods need input validation and PHPDoc documentation. The formatter and representation classes should be validated.


49-52: Clarify the purpose of documentRepresentation method

The documentRepresentation method appears to be a no-op as it simply returns $this without any state changes. If this is a placeholder for future implementation, it should be documented.

Let's check if this method is used in the codebase:

Consider adding documentation to clarify the method's purpose:

+    /**
+     * Placeholder for document representation configuration.
+     * TODO: Implement document representation logic
+     *
+     * @return self
+     */
     public function documentRepresentation(): self
     {
         return $this;
     }
src/product/TariffType.php (7)

30-34: ⚠️ Potential issue

Implement price suggester functionality

The method contains only a placeholder comment. This could lead to unexpected behavior.

Would you like me to help implement the price suggester validation and storage functionality?


46-51: 🛠️ Refactor suggestion

Add validation for behavior attachment

The method should validate behavior uniqueness and compatibility.

     public function attach(BehaviorInterface $behavior): self
     {
+        $behaviorClass = get_class($behavior);
+        foreach ($this->behaviors as $existingBehavior) {
+            if ($behaviorClass === get_class($existingBehavior)) {
+                throw new \InvalidArgumentException("Behavior of type '$behaviorClass' is already attached");
+            }
+        }
         $this->behaviors[] = $behavior;
 
         return $this;
     }

53-57: ⚠️ Potential issue

Implement state validation and locking

The end method needs proper implementation for validation and state locking.

-    public function end(): self
+    public function finalize(): self
     {
-        // Validate the TariffType and lock its state
+        if ($this->product === null) {
+            throw new \RuntimeException('Product must be set before finalizing');
+        }
+        if ($this->prices->isEmpty()) {
+            throw new \RuntimeException('At least one price type must be defined');
+        }
+        $this->locked = true;
         return $this;
     }

15-15: 🛠️ Refactor suggestion

Add type hint for behaviors array

The behaviors array should have a proper type hint to ensure type safety.

-    private array $behaviors = [];
+    /** @var BehaviorInterface[] */
+    private array $behaviors = [];

Likely invalid or redundant comment.


17-21: 🛠️ Refactor suggestion

Add name validation in constructor

The constructor should validate that the name is not empty.

     public function __construct(string $name)
     {
+        if (empty(trim($name))) {
+            throw new \InvalidArgumentException('Tariff name cannot be empty');
+        }
         $this->name = $name;
         $this->prices = new PriceTypesCollection();
     }

Likely invalid or redundant comment.


36-39: 🛠️ Refactor suggestion

Add validation in withPrices method

The method should validate that the product is set before allowing price manipulation.

     public function withPrices(): PriceTypesCollection
     {
+        if ($this->product === null) {
+            throw new \RuntimeException('Product must be set before configuring prices');
+        }
         return $this->prices;
     }

Likely invalid or redundant comment.


23-28: 🛠️ Refactor suggestion

Add product validation in ofProduct method

The method should validate that the product is not null.

     public function ofProduct(Product $product): self
     {
+        if ($product === null) {
+            throw new \InvalidArgumentException('Product cannot be null');
+        }
         $this->product = $product;
 
         return $this;
     }

Likely invalid or redundant comment.

🧰 Tools
🪛 GitHub Check: PHP 8.3

[failure] 23-23: UndefinedClass
src/product/TariffType.php:23:31: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\product\Product does not exist (see https://psalm.dev/019)

tests/unit/product/InvoiceDescriptionsBuilderTest.php (1)

12-68: 🛠️ Refactor suggestion

Enhance test coverage with additional scenarios

The current test only covers the happy path. Consider adding test cases for:

  • Invalid configurations
  • Empty registry
  • Unlocked registry
  • Registry with multiple tariff types

Would you like me to help generate additional test cases?

// Create TariffType with real prices and behaviors
$serverTariffType = (new TariffType('server'))
->ofProduct(ServerProduct::class)
->setPricesSuggester(\hiapi\legacy\lib\billing\price\suggester\device\ServerPricesSuggester::class)
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

Legacy class ServerPricesSuggester appears to be missing

The class \hiapi\legacy\lib\billing\price\suggester\device\ServerPricesSuggester is referenced in the test but doesn't exist in the codebase. This could lead to test failures. You should either:

  • Move the actual class implementation to the specified legacy path
  • Update the test to use a mock or a different existing price suggester
  • Update the namespace if the class has been moved to a new location
🔗 Analysis chain

Verify class existence in test

The test uses ServerPricesSuggester from a legacy path. Consider moving this to a more maintainable location or mocking it for tests.


Let me check if this class actually exists in the legacy path or if it's just referenced in the test.


Let me try one more search to find any references or implementations of this class in the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if the legacy suggester class exists
rg -l "ServerPricesSuggester" 

Length of output: 83


Script:

#!/bin/bash
# Check for the actual class file in the legacy path
fd -e php ServerPricesSuggester

# Also search for any class definition
ast-grep --pattern 'class ServerPricesSuggester'

Length of output: 84


Script:

#!/bin/bash
# Search for any file containing the full namespace path
rg -l "hiapi.legacy.lib.billing.price.suggester.device.ServerPricesSuggester"

# Search with different path separators
rg -l "hiapi/legacy/lib/billing/price/suggester/device/ServerPricesSuggester"

# Get context around the usage in the test file
rg -C 5 "ServerPricesSuggester" tests/unit/product/InvoiceDescriptionsBuilderTest.php

Length of output: 885

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

🧹 Outside diff range and nitpick comments (2)
src/product/PriceTypesCollection.php (1)

7-7: Initialize prices array with type hint.

Add a type hint to the prices array for better type safety and code readability.

-    private array $prices = [];
+    /** @var array<string, PriceTypeDefinition> */
+    private array $prices = [];
src/product/PriceTypeDefinition.php (1)

9-15: Initialize properties with default values.

Properties should be initialized to prevent undefined property access.

-    private string $unit;
+    private string $unit = '';

-    private string $description;
+    private string $description = '';

-    private string $quantityFormatter;
+    private string $quantityFormatter = '';

-    private string $invoiceRepresentation;
+    private string $invoiceRepresentation = '';
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between d8d3535 and cbc66f2.

📒 Files selected for processing (3)
  • src/product/PriceTypeDefinition.php (1 hunks)
  • src/product/PriceTypesCollection.php (1 hunks)
  • src/product/TariffType.php (1 hunks)
🧰 Additional context used
🪛 GitHub Check: PHP 8.3
src/product/TariffType.php

[failure] 11-11: UndefinedClass
src/product/TariffType.php:11:13: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\product\Product does not exist (see https://psalm.dev/019)


[failure] 23-23: UndefinedClass
src/product/TariffType.php:23:31: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\product\Product does not exist (see https://psalm.dev/019)

src/product/PriceTypeDefinition.php

[failure] 55-55: UndefinedClass
src/product/PriceTypeDefinition.php:55:47: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\invoice\InvoiceRepresentationCollection does not exist (see https://psalm.dev/019)


[failure] 57-57: UndefinedClass
src/product/PriceTypeDefinition.php:57:20: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\invoice\InvoiceRepresentationCollection does not exist (see https://psalm.dev/019)


[failure] 60-60: UndefinedClass
src/product/PriceTypeDefinition.php:60:34: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\measure\RcpTrafCollector does not exist (see https://psalm.dev/019)

🔇 Additional comments (4)
src/product/PriceTypesCollection.php (1)

21-24: LGTM!

The helper method is well-designed, encapsulating the creation logic and maintaining the parent-child relationship.

src/product/TariffType.php (2)

46-51: 🛠️ Refactor suggestion

Add validation for behavior attachment.

The method should validate behavior uniqueness and compatibility.

     public function attach(BehaviorInterface $behavior): self
     {
+        $behaviorClass = get_class($behavior);
+        foreach ($this->behaviors as $existingBehavior) {
+            if ($behaviorClass === get_class($existingBehavior)) {
+                throw new \InvalidArgumentException("Behavior of type '$behaviorClass' is already attached");
+            }
+        }
         $this->behaviors[] = $behavior;
 
         return $this;
     }

Likely invalid or redundant comment.


53-57: ⚠️ Potential issue

Implement tariff validation and state locking.

The end method needs proper validation of required properties and state locking mechanism.

     public function end(): self
     {
-        // Validate the TariffType and lock its state
+        if (!isset($this->product)) {
+            throw new \RuntimeException('Product must be set before finalizing');
+        }
+        if (empty($this->prices)) {
+            throw new \RuntimeException('At least one price type must be defined');
+        }
         return $this;
     }

Likely invalid or redundant comment.

src/product/PriceTypeDefinition.php (1)

49-53: ⚠️ Potential issue

Add validation in end method.

The end method should validate that all required properties are set before returning to parent.

     public function end(): PriceTypesCollection
     {
-        // Validate the PriceType and lock its state
+        $requiredProps = ['unit', 'description', 'quantityFormatter', 'invoiceRepresentation'];
+        foreach ($requiredProps as $prop) {
+            if (empty($this->$prop)) {
+                throw new \RuntimeException("$prop is required but not set");
+            }
+        }
         return $this->parent;
     }

Likely invalid or redundant comment.

Comment on lines 13 to 19
public function monthly(string $type): PriceTypeDefinition
{
$priceType = $this->createPriceTypeDefinition();
$this->prices[$type] = $priceType;

return $priceType;
}
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

Add validation for duplicate price types.

The monthly and overuse methods should validate that the price type doesn't already exist to prevent overwriting.

     public function monthly(string $type): PriceTypeDefinition
     {
+        if (isset($this->prices[$type])) {
+            throw new \InvalidArgumentException("Price type '$type' already exists");
+        }
         $priceType = $this->createPriceTypeDefinition();
         $this->prices[$type] = $priceType;
 
         return $priceType;
     }

Apply the same validation to the overuse method.

Also applies to: 26-32

Comment on lines 30 to 34
public function setPricesSuggester(string $suggesterClass): self
{
// Validate or store the suggester class
return $this;
}
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

Implement price suggester functionality.

The method contains only a placeholder comment. This could lead to unexpected behavior.

Would you like me to help implement the price suggester validation and storage functionality?


namespace hiqdev\php\billing\product;

use hiqdev\billing\registry\product\Product;
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

⚠️ Potential issue

The Product class import path is incorrect

The import statement is using hiqdev\billing\registry\product\Product, but based on the codebase analysis, the actual namespace for product-related classes is hiqdev\php\billing\product. The Product class itself is not found, but there is a ProductInterface and ServerProduct in this namespace.

  • Change the import in src/product/TariffType.php from:
    use hiqdev\billing\registry\product\Product;
    to either:
    use hiqdev\php\billing\product\ProductInterface;
    or
    use hiqdev\php\billing\product\ServerProduct;
    depending on which one is needed in the context.
🔗 Analysis chain

Fix undefined Product class import.

The static analysis indicates that the Product class doesn't exist in the specified namespace.

Also applies to: 11-11


Let me gather more information about the Product class and its namespace.


Let me try one more search to find any potential Product class definition in vendor directories or related repositories.


Let me check the actual namespace of the found Product-related files.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for Product class definition
rg -t php "class Product"

# Search for Product usage
rg -t php "use.*Product"

Length of output: 231


Script:

#!/bin/bash
# Search for the complete namespace path
fd . -e php -x grep -l "namespace.*billing\\registry\\product"

# Search for any references to Product class usage
rg -t php "Product::" -A 2
rg -t php "instanceof Product" -A 2

Length of output: 474


Script:

#!/bin/bash
# Search for composer.json to check dependencies
cat composer.json

# Search for any PHP files containing Product in their name
fd -e php -g '*Product*.php'

Length of output: 3040


Script:

#!/bin/bash
# Check the namespace declarations in the found files
cat src/product/ProductInterface.php
cat src/product/ServerProduct.php

Length of output: 423

Comment on lines 55 to 98
public function documentRepresentation(): InvoiceRepresentationCollection
{
return new InvoiceRepresentationCollection($this);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

⚠️ Potential issue

Missing required dependency for invoice and measure classes

The classes InvoiceRepresentationCollection and RcpTrafCollector are referenced from the hiqdev\billing\registry namespace, but there's no corresponding dependency in composer.json that would provide these classes. This appears to be a missing dependency issue rather than undefined classes.

  • Add the required dependency that provides the hiqdev\billing\registry namespace to composer.json
  • The dependency is likely a separate package from the same vendor (hiqdev)
🔗 Analysis chain

Fix undefined classes and add method documentation.

The static analysis indicates that InvoiceRepresentationCollection and RcpTrafCollector classes don't exist in the specified namespaces.

Also applies to: 60-63

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for the undefined classes
rg -t php "class (InvoiceRepresentationCollection|RcpTrafCollector)"

# Search for their usage
rg -t php "use.*(InvoiceRepresentationCollection|RcpTrafCollector)"

Length of output: 245


Script:

#!/bin/bash
# Check if the class exists in the vendor directory
fd -t f "InvoiceRepresentationCollection.php" 

# Check for any references to the namespace
rg -t php "hiqdev\\\\billing\\\\registry\\\\invoice"

# Check for RcpTrafCollector references
rg -t php "RcpTrafCollector"

Length of output: 594


Script:

#!/bin/bash
# Check for the measure namespace and RcpTrafCollector in vendor or source
fd -t f "RcpTrafCollector.php"

# Look for other classes in the measure namespace
rg -t php "hiqdev\\\\billing\\\\registry\\\\measure"

# Check composer.json for relevant dependencies
cat composer.json

Length of output: 3159

🧰 Tools
🪛 GitHub Check: PHP 8.3

[failure] 55-55: UndefinedClass
src/product/PriceTypeDefinition.php:55:47: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\invoice\InvoiceRepresentationCollection does not exist (see https://psalm.dev/019)


[failure] 57-57: UndefinedClass
src/product/PriceTypeDefinition.php:57:20: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\invoice\InvoiceRepresentationCollection does not exist (see https://psalm.dev/019)

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

🧹 Outside diff range and nitpick comments (2)
src/product/PriceTypeDefinition.php (2)

11-17: Initialize properties with default values.

Properties should be initialized to prevent undefined property access.

-    private Unit $unit;
+    private ?Unit $unit = null;

-    private string $description;
+    private string $description = '';

-    private string $quantityFormatter;
+    private string $quantityFormatter = '';
🧰 Tools
🪛 GitHub Check: PHP 8.3

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


[failure] 17-17: UndefinedClass
src/product/PriceTypeDefinition.php:17:13: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\invoice\InvoiceRepresentationCollection does not exist (see https://psalm.dev/019)


45-50: Remove or implement commented code.

The commented out invoiceRepresentation method should either be implemented or removed.

If the method is needed, implement it. Otherwise, remove the commented code to maintain cleanliness.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between cbc66f2 and 1e3d171.

📒 Files selected for processing (3)
  • src/product/BillingRegistry.php (1 hunks)
  • src/product/PriceTypeDefinition.php (1 hunks)
  • src/product/PriceTypesCollection.php (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/product/BillingRegistry.php
🧰 Additional context used
🪛 GitHub Check: PHP 8.3
src/product/PriceTypeDefinition.php

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


[failure] 17-17: UndefinedClass
src/product/PriceTypeDefinition.php:17:13: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\invoice\InvoiceRepresentationCollection does not exist (see https://psalm.dev/019)


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


[failure] 21-21: UndefinedClass
src/product/PriceTypeDefinition.php:21:44: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\invoice\InvoiceRepresentationCollection does not exist (see https://psalm.dev/019)


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


[failure] 58-58: UndefinedClass
src/product/PriceTypeDefinition.php:58:47: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\invoice\InvoiceRepresentationCollection does not exist (see https://psalm.dev/019)


[failure] 63-63: UndefinedClass
src/product/PriceTypeDefinition.php:63:34: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\measure\RcpTrafCollector does not exist (see https://psalm.dev/019)


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

src/product/PriceTypesCollection.php

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


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

🔇 Additional comments (3)
src/product/PriceTypeDefinition.php (3)

5-7: ⚠️ Potential issue

Add missing dependency for billing registry classes.

The static analysis indicates missing classes from the hiqdev\billing\registry namespace.

Add the required dependency to your composer.json:

{
    "require": {
+        "hiqdev/billing-registry": "^1.0"
    }
}

Likely invalid or redundant comment.


52-56: 🛠️ Refactor suggestion

Add validation in end() method.

The end() method should validate that all required properties are set before returning.

     public function end(): PriceTypesCollection
     {
-        // Validate the PriceType and lock its state
+        if ($this->unit === null) {
+            throw new \RuntimeException('Unit must be set');
+        }
+        if (empty($this->description)) {
+            throw new \RuntimeException('Description must be set');
+        }
+        if (empty($this->quantityFormatter)) {
+            throw new \RuntimeException('Quantity formatter must be set');
+        }
         return $this->parent;
     }

Likely invalid or redundant comment.


63-66: Add implementation for measuredWith method.

The method currently returns without any implementation.

Would you like me to help implement this method based on the usage patterns in the codebase?

🧰 Tools
🪛 GitHub Check: PHP 8.3

[failure] 63-63: UndefinedClass
src/product/PriceTypeDefinition.php:63:34: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\measure\RcpTrafCollector does not exist (see https://psalm.dev/019)

Comment on lines 15 to 21
public function monthly(Type $type): PriceTypeDefinition
{
$priceType = $this->createPriceTypeDefinition($type);
$this->prices[$type->toTypeName()] = $priceType;

return $priceType;
}
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

Refactor duplicate code in monthly() and overuse() methods.

The methods contain identical implementation which violates the DRY principle.

Consider extracting the common logic:

+    private function addPriceType(Type $type): PriceTypeDefinition 
+    {
+        if (isset($this->prices[$type->toTypeName()])) {
+            throw new \InvalidArgumentException("Price type '{$type->toTypeName()}' already exists");
+        }
+        $priceType = $this->createPriceTypeDefinition($type);
+        $this->prices[$type->toTypeName()] = $priceType;
+        return $priceType;
+    }
+
     public function monthly(Type $type): PriceTypeDefinition
     {
-        $priceType = $this->createPriceTypeDefinition($type);
-        $this->prices[$type->toTypeName()] = $priceType;
-        return $priceType;
+        return $this->addPriceType($type);
     }

     public function overuse(Type $type): PriceTypeDefinition
     {
-        $priceType = $this->createPriceTypeDefinition($type);
-        $this->prices[$type->toTypeName()] = $priceType;
-        return $priceType;
+        return $this->addPriceType($type);
     }

Also applies to: 28-34

🧰 Tools
🪛 GitHub Check: PHP 8.3

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

Comment on lines 36 to 39
public function end(): TariffType
{
return $this->parent;
}
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

Add validation in end() method.

The end() method should validate that at least one price type has been added before returning to the parent context.

     public function end(): TariffType
     {
+        if (empty($this->prices)) {
+            throw new \RuntimeException('At least one price type must be defined');
+        }
         return $this->parent;
     }
📝 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
public function end(): TariffType
{
return $this->parent;
}
public function end(): TariffType
{
if (empty($this->prices)) {
throw new \RuntimeException('At least one price type must be defined');
}
return $this->parent;
}

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

🧹 Outside diff range and nitpick comments (1)
src/product/PriceTypeDefinition.php (1)

45-50: Remove commented-out code.

The commented-out invoiceRepresentation method should be either implemented or removed. Commented-out code can lead to confusion and maintenance issues.

-//    public function invoiceRepresentation(string $representationClass): self
-//    {
-//        $this->invoiceRepresentation = $representationClass;
-//
-//        return $this;
-//    }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 1e3d171 and d678b94.

📒 Files selected for processing (3)
  • src/product/BillingRegistryInterface.php (1 hunks)
  • src/product/PriceTypeDefinition.php (1 hunks)
  • src/product/PriceTypesCollection.php (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/product/BillingRegistryInterface.php
🧰 Additional context used
🪛 GitHub Check: PHP 8.3
src/product/PriceTypeDefinition.php

[failure] 17-17: UndefinedClass
src/product/PriceTypeDefinition.php:17:13: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\invoice\InvoiceRepresentationCollection does not exist (see https://psalm.dev/019)


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


[failure] 21-21: UndefinedClass
src/product/PriceTypeDefinition.php:21:44: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\invoice\InvoiceRepresentationCollection does not exist (see https://psalm.dev/019)


[failure] 58-58: UndefinedClass
src/product/PriceTypeDefinition.php:58:47: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\invoice\InvoiceRepresentationCollection does not exist (see https://psalm.dev/019)


[failure] 63-63: UndefinedClass
src/product/PriceTypeDefinition.php:63:34: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\measure\RcpTrafCollector does not exist (see https://psalm.dev/019)


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

src/product/PriceTypesCollection.php

[failure] 15-15: UndefinedClass
src/product/PriceTypesCollection.php:15:29: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\product\PriceType does not exist (see https://psalm.dev/019)


[failure] 23-23: UndefinedClass
src/product/PriceTypesCollection.php:23:48: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\product\PriceType does not exist (see https://psalm.dev/019)


[failure] 28-28: UndefinedClass
src/product/PriceTypesCollection.php:28:29: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\product\PriceType does not exist (see https://psalm.dev/019)

🔇 Additional comments (6)
src/product/PriceTypesCollection.php (4)

1-13: LGTM! Well-structured class initialization.

The class follows good practices with strict typing, proper namespace organization, and constructor property promotion.


23-26: LGTM! Well-designed helper method.

The helper method follows good practices with proper scoping and single responsibility.

🧰 Tools
🪛 GitHub Check: PHP 8.3

[failure] 23-23: UndefinedClass
src/product/PriceTypesCollection.php:23:48: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\product\PriceType does not exist (see https://psalm.dev/019)


15-21: 🛠️ Refactor suggestion

Refactor duplicate code and add validation.

The implementation contains duplicate code with the overuse method and lacks validation for duplicate price types.

🧰 Tools
🪛 GitHub Check: PHP 8.3

[failure] 15-15: UndefinedClass
src/product/PriceTypesCollection.php:15:29: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\product\PriceType does not exist (see https://psalm.dev/019)


36-39: 🛠️ Refactor suggestion

Add validation in end() method.

The method should validate the collection state before returning to the parent context.

src/product/PriceTypeDefinition.php (2)

24-43: 🛠️ Refactor suggestion

Add input validation and method documentation.

The builder methods need input validation and proper documentation.


5-18: ⚠️ Potential issue

Add missing dependencies to composer.json.

The following classes are referenced but appear to be missing from dependencies:

  • hiqdev\billing\registry\invoice\InvoiceRepresentationCollection
  • hiqdev\billing\registry\product\PriceType
  • hiqdev\billing\registry\measure\RcpTrafCollector

Add the required package that provides these classes to your composer.json:

{
    "require": {
+        "hiqdev/billing-registry": "^1.0"
    }
}

Likely invalid or redundant comment.

🧰 Tools
🪛 GitHub Check: PHP 8.3

[failure] 17-17: UndefinedClass
src/product/PriceTypeDefinition.php:17:13: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\invoice\InvoiceRepresentationCollection does not exist (see https://psalm.dev/019)

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

🧹 Outside diff range and nitpick comments (2)
src/product/PriceTypesCollection.php (2)

24-34: Optimize getAllPrices method.

The nested loops can be replaced with array_merge and array_values for better performance.

 private function getAllPrices(): array
 {
-    $allPrices = [];
-    foreach ($this->pricesGroupedByPriceType as $prices) {
-        foreach ($prices as $price) {
-            $allPrices[] = $price;
-        }
-    }
-
-    return $allPrices;
+    return array_merge(...array_values($this->pricesGroupedByPriceType));
 }

36-43: Refactor duplicate code in price type methods.

The monthly, overuse, feature, domain, certificate, and discount methods share identical implementation patterns.

+    private function createAndAddPriceType(GType $gType, PriceType $type): PriceTypeDefinition
+    {
+        $priceType = $this->createPriceTypeDefinition($gType, $type);
+        $this->addPriceTypeDefinition($type, $priceType);
+        return $priceType;
+    }
+
     public function monthly(PriceType $type): PriceTypeDefinition
     {
-        $priceType = $this->createPriceTypeDefinition(GType::monthly, $type);
-        $this->addPriceTypeDefinition($type, $priceType);
-        return $priceType;
+        return $this->createAndAddPriceType(GType::monthly, $type);
     }

Apply similar changes to other price type methods.

Also applies to: 55-62, 69-76, 78-85, 87-94, 96-103

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between d678b94 and f7e6961.

📒 Files selected for processing (4)
  • src/product/BillingRegistry.php (1 hunks)
  • src/product/BillingRegistryInterface.php (1 hunks)
  • src/product/PriceTypeDefinition.php (1 hunks)
  • src/product/PriceTypesCollection.php (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/product/BillingRegistryInterface.php
🔇 Additional comments (2)
src/product/BillingRegistry.php (1)

27-34: 🛠️ Refactor suggestion

Add error handling for tariff type iteration.

The priceTypes method should handle potential errors from withPrices() calls.

 public function priceTypes(): \Generator
 {
     foreach ($this->tariffTypes as $tariffType) {
-        foreach ($tariffType->withPrices() as $priceTypeDefinition) {
-            yield $priceTypeDefinition;
+        try {
+            foreach ($tariffType->withPrices() as $priceTypeDefinition) {
+                yield $priceTypeDefinition;
+            }
+        } catch (\Throwable $e) {
+            throw new \RuntimeException(
+                "Failed to retrieve prices for tariff type",
+                0,
+                $e
+            );
         }
     }
 }

Likely invalid or redundant comment.

src/product/PriceTypeDefinition.php (1)

49-53: 🛠️ Refactor suggestion

Add validation in end() method.

The end() method should validate that all required properties are set before returning.

 public function end(): PriceTypesCollection
 {
-    // Validate the PriceType and lock its state
+    $requiredProps = ['unit', 'description', 'quantityFormatter'];
+    foreach ($requiredProps as $prop) {
+        if (!isset($this->$prop)) {
+            throw new \RuntimeException("$prop is required but not set");
+        }
+    }
     return $this->parent;
 }

Likely invalid or redundant comment.

Comment on lines +40 to +57
public function getRepresentationsByType(string $representationClass): array
{
$representations = [];
foreach ($this->priceTypes() as $priceTypeDefinition) {
foreach ($priceTypeDefinition->documentRepresentation() as $representation) {
if ($representation instanceof $representationClass) {
$representations[] = $representation;
}
}
}

return $representations;
}
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

Add validation for representation class existence.

The getRepresentationsByType method should validate that the provided class exists and implements RepresentationInterface.

 public function getRepresentationsByType(string $representationClass): array
 {
+    if (!class_exists($representationClass)) {
+        throw new \InvalidArgumentException("Class '$representationClass' does not exist");
+    }
+    if (!is_subclass_of($representationClass, RepresentationInterface::class)) {
+        throw new \InvalidArgumentException("Class '$representationClass' must implement RepresentationInterface");
+    }
     $representations = [];
     foreach ($this->priceTypes() as $priceTypeDefinition) {
         foreach ($priceTypeDefinition->documentRepresentation() as $representation) {

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 45 to 48
private function addPriceTypeDefinition(PriceType $type, PriceTypeDefinition $priceTypeDefinition): void
{
$this->pricesGroupedByPriceType[$type->name][] = $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

Add validation in addPriceTypeDefinition method.

The method should validate the price type name and prevent duplicate entries.

 private function addPriceTypeDefinition(PriceType $type, PriceTypeDefinition $priceTypeDefinition): void
 {
+    if (empty($type->name)) {
+        throw new \InvalidArgumentException('Price type name cannot be empty');
+    }
     $this->pricesGroupedByPriceType[$type->name][] = $priceTypeDefinition;
 }
📝 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
private function addPriceTypeDefinition(PriceType $type, PriceTypeDefinition $priceTypeDefinition): void
{
$this->pricesGroupedByPriceType[$type->name][] = $priceTypeDefinition;
}
private function addPriceTypeDefinition(PriceType $type, PriceTypeDefinition $priceTypeDefinition): void
{
if (empty($type->name)) {
throw new \InvalidArgumentException('Price type name cannot be empty');
}
$this->pricesGroupedByPriceType[$type->name][] = $priceTypeDefinition;
}

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 (4)
src/product/PriceTypeDefinition.php (3)

18-19: Potential uninitialized property usage.
Although PHP enforces type hints, consider initializing the private property $unit with a default or in the constructor to avoid the risk of referencing it before the setter is called.


48-53: Validate description input if needed.
If an empty description is meaningless in your context, consider throwing an exception or logging a warning.


60-66: Check if $formatterClass exists.
The TODO comment suggests verifying if $formatterClass exists. Implement it to prevent runtime errors from invalid classes.

src/product/BillingRegistry.php (1)

31-38: Consider adding error handling or fallback logic.
If a tariff type's withPrices() call throws or returns non-iterable data, it may cause unexpected behavior. You could wrap the iteration in a try-catch to safeguard.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 7ab9d97 and 3f8e530.

📒 Files selected for processing (4)
  • src/product/BillingRegistry.php (1 hunks)
  • src/product/PriceTypeDefinition.php (1 hunks)
  • src/product/PriceTypesCollection.php (1 hunks)
  • src/quantity/QuantityFormatterInterface.php (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/product/PriceTypesCollection.php
🔇 Additional comments (3)
src/product/PriceTypeDefinition.php (2)

26-34: Consider adding validation for GType, PriceType, or parent correctness.
In situations where the constructor receives invalid objects (e.g., null or inconsistent references), you might want to enforce stronger checks or gracefully handle those errors, preventing runtime exceptions later.


89-92: Implementation is incomplete.
The measuredWith method simply returns $this. Ensure that hooking RcpTrafCollector or other measure logic is actually performed if intended.

src/quantity/QuantityFormatterInterface.php (1)

1-28: All methods appear well-defined.
The interface methods are concise and logical, covering textual formatting, DB storage format, and client-friendly quantity values. Great job!

Comment on lines 41 to 52
public function unit(string $unit): self
{
$this->unit = Unit::create($unit);

return $this;
}
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

Add input validation for the unit.
If $unit can be empty or invalid, a runtime error might occur in Unit::create(). Add input validation to ensure $unit is not empty and is recognized by Unit::create().

Comment on lines 78 to 93
public function end(): PriceTypesCollection
{
// Validate the PriceType and lock its state
return $this->parent;
}
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

Add final validation to the end() method.
It might be beneficial to validate that required properties (e.g., $unit, $description, and $quantityFormatterDefinition) are all set and valid before returning to the parent.

Comment on lines 58 to 72
public function createQuantityFormatter(
GType $gType,
PriceType $priceType,
FractionQuantityData $data,
): array {
foreach ($this->priceTypes() as $priceTypeDefinition) {
if ($priceTypeDefinition->gType() === $gType && $priceTypeDefinition->priceType() === $priceType) {
return $priceTypeDefinition->createQuantityFormatter($data);
}
}

throw new QuantityFormatterNotFoundException('Quantity formatter not found');
}
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

Potential mismatch in method names.
Line 64 calls $priceTypeDefinition->priceType(), but in PriceTypeDefinition, the method is type(). This discrepancy may cause errors.

- if ($priceTypeDefinition->gType() === $gType && $priceTypeDefinition->priceType() === $priceType) {
+ if ($priceTypeDefinition->gType() === $gType && $priceTypeDefinition->type() === $priceType) {

Committable suggestion skipped: line range outside the PR's diff.

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 (3)
src/product/PriceTypeDefinition.php (2)

16-17: Add class-level documentation for clarity.
This class defines a core entity for building price types but lacks a PHPDoc block describing its purpose, usage, and examples.


63-69: Address the TODO for the formatter class check.
Currently, there is a TODO comment but no actual validation. Ensure the class exists and implements the required interface.

Would you like me to generate a quick fix for this check?

src/product/BillingRegistry.php (1)

16-23: Validate duplicates in addTariffType.
If TariffType is identified by something like getId(), consider preventing duplicates.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 3f8e530 and 6548d55.

📒 Files selected for processing (2)
  • src/product/BillingRegistry.php (1 hunks)
  • src/product/PriceTypeDefinition.php (1 hunks)
🧰 Additional context used
🪛 GitHub Check: PHP 8.3
src/product/PriceTypeDefinition.php

[failure] 22-22: UndefinedClass
src/product/PriceTypeDefinition.php:22:13: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\quantity\formatter\QuantityFormatterDefinition does not exist (see https://psalm.dev/019)


[failure] 24-24: UndefinedClass
src/product/PriceTypeDefinition.php:24:13: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\invoice\InvoiceRepresentationCollection does not exist (see https://psalm.dev/019)


[failure] 26-26: UndefinedClass
src/product/PriceTypeDefinition.php:26:13: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\behavior\BehaviorCollection does not exist (see https://psalm.dev/019)


[failure] 31-31: UndefinedClass
src/product/PriceTypeDefinition.php:31:9: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\quantity\formatter\QuantityFormatterFactory does not exist (see https://psalm.dev/019)


[failure] 33-33: UndefinedClass
src/product/PriceTypeDefinition.php:33:40: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\invoice\InvoiceRepresentationCollection does not exist (see https://psalm.dev/019)


[failure] 34-34: UndefinedClass
src/product/PriceTypeDefinition.php:34:41: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\behavior\BehaviorCollection does not exist (see https://psalm.dev/019)


[failure] 60-60: UndefinedDocblockClass
src/product/PriceTypeDefinition.php:60:15: UndefinedDocblockClass: Docblock-defined class, interface or enum named hiqdev\billing\registry\unit\FractionUnit does not exist (see https://psalm.dev/200)


[failure] 72-72: UndefinedClass
src/product/PriceTypeDefinition.php:72:9: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\quantity\FractionQuantityData does not exist (see https://psalm.dev/019)

src/product/BillingRegistry.php

[failure] 41-41: UndefinedDocblockClass
src/product/BillingRegistry.php:41:16: UndefinedDocblockClass: Docblock-defined class, interface or enum named hiqdev\billing\registry\invoice\RepresentationInterface does not exist (see https://psalm.dev/200)


[failure] 59-59: UndefinedClass
src/product/BillingRegistry.php:59:9: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\quantity\FractionQuantityData does not exist (see https://psalm.dev/019)

🔇 Additional comments (3)
src/product/PriceTypeDefinition.php (3)

18-24: Initialize properties to avoid undefined usage.
Properties such as $unit, $description, etc., can be initialized with default values (e.g., empty strings) to prevent undefined usage.

🧰 Tools
🪛 GitHub Check: PHP 8.3

[failure] 22-22: UndefinedClass
src/product/PriceTypeDefinition.php:22:13: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\quantity\formatter\QuantityFormatterDefinition does not exist (see https://psalm.dev/019)


[failure] 24-24: UndefinedClass
src/product/PriceTypeDefinition.php:24:13: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\invoice\InvoiceRepresentationCollection does not exist (see https://psalm.dev/019)


44-49: Add input validation for unit.
If $unit can be empty or invalid, a runtime error might occur in Unit::create($unit). Validate that $unit is not empty and recognized by Unit::create.


81-85: Add final validation to end() method.
Before returning $this->parent, verify that you've set required properties (unit, description, and quantity formatter) to valid values.

Comment on lines 57 to 72
public function createQuantityFormatter(
string $type,
FractionQuantityData $data,
): array {
$type = Type::anyId($type);

foreach ($this->priceTypes() as $priceTypeDefinition) {
if ($priceTypeDefinition->hasType($type)) {
return $priceTypeDefinition->createQuantityFormatter($data);
}
}

throw new QuantityFormatterNotFoundException('Quantity formatter not found');
}
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

Enhance error context in createQuantityFormatter.
When quantity formatters are not found, you throw a generic exception. Consider adding details about which $type was not found to aid troubleshooting.

- throw new QuantityFormatterNotFoundException('Quantity formatter not found');
+ throw new QuantityFormatterNotFoundException(
+     sprintf("Quantity formatter not found for type '%s'.", $type->getId())
+ );
📝 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
public function createQuantityFormatter(
string $type,
FractionQuantityData $data,
): array {
$type = Type::anyId($type);
foreach ($this->priceTypes() as $priceTypeDefinition) {
if ($priceTypeDefinition->hasType($type)) {
return $priceTypeDefinition->createQuantityFormatter($data);
}
}
throw new QuantityFormatterNotFoundException('Quantity formatter not found');
}
public function createQuantityFormatter(
string $type,
FractionQuantityData $data,
): array {
$type = Type::anyId($type);
foreach ($this->priceTypes() as $priceTypeDefinition) {
if ($priceTypeDefinition->hasType($type)) {
return $priceTypeDefinition->createQuantityFormatter($data);
}
}
throw new QuantityFormatterNotFoundException(
sprintf("Quantity formatter not found for type '%s'.", $type->getId())
);
}
🧰 Tools
🪛 GitHub Check: PHP 8.3

[failure] 59-59: UndefinedClass
src/product/BillingRegistry.php:59:9: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\quantity\FractionQuantityData does not exist (see https://psalm.dev/019)

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/TariffType.php (1)

5-5: ⚠️ Potential issue

Incorrect namespace import for Product
It appears that the class "Product" doesn't exist in the "hiqdev\billing\registry\product" namespace, leading to a potential autoloading or class-not-found error. Consider referencing the correct Product class or interface in "hiqdev\php\billing\product" (e.g., "ProductInterface" or "ServerProduct").

Apply this diff for a potential fix (switching to ProductInterface as an example):

-use hiqdev\billing\registry\product\Product;
+use hiqdev\php\billing\product\ProductInterface as Product;
🧹 Nitpick comments (4)
src/product/TariffType.php (1)

33-37: Add implementation or remove placeholder
The "setPricesSuggester" method currently contains only a comment, leaving the purpose unfulfilled. You may want to either implement the logic (e.g., store or validate the suggester class and tie it into pricing logic) or remove it if it's not required.

src/product/ConsumptionDefinition.php (1)

19-22: Empty groups() method
The "groups()" method is currently a placeholder. If you plan to implement grouping logic for consumption columns, consider adding relevant parameters, logic, or documentation. Otherwise, remove the method to avoid confusion.

src/product/ConsumptionColumn.php (2)

11-16: Encapsulate logic behind meaningful method naming
The method "convertible()" sets "$isConvertible" to true. Though this is functional, you might consider using clearer naming (e.g., "markAsConvertible()") or adding inline documentation to clarify the context and ramifications.


23-28: Extend consistent naming
Similarly, "overMax()" sets "$isOverMax" to true. In combination with "convertible()", both methods could benefit from more descriptive names or docblocks to govern usage.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 6548d55 and a1c0436.

📒 Files selected for processing (3)
  • src/product/ConsumptionColumn.php (1 hunks)
  • src/product/ConsumptionDefinition.php (1 hunks)
  • src/product/TariffType.php (1 hunks)
🧰 Additional context used
🪛 GitHub Check: PHP 8.3
src/product/ConsumptionDefinition.php

[failure] 7-7: UndefinedClass
src/product/ConsumptionDefinition.php:7:13: UndefinedClass: Class, interface or enum named hiqdev\php\billing\product\ConsumptionColumnCollection does not exist (see https://psalm.dev/019)


[failure] 11-11: UndefinedClass
src/product/ConsumptionDefinition.php:11:39: UndefinedClass: Class, interface or enum named hiqdev\php\billing\product\ConsumptionColumnCollection does not exist (see https://psalm.dev/019)


[failure] 14-14: UndefinedClass
src/product/ConsumptionDefinition.php:14:32: UndefinedClass: Class, interface or enum named hiqdev\php\billing\product\ConsumptionColumnCollection does not exist (see https://psalm.dev/019)

🔇 Additional comments (1)
src/product/TariffType.php (1)

26-31: 🛠️ Refactor suggestion

Validate the product class
When a product is assigned (via the "ofProduct" method), consider validating that the given class or object is correct. At minimum, ensure that it indeed conforms to your business logic or an expected interface to prevent accidental misuse.

 public function ofProduct(Product $product): self
 {
+    // e.g., validate or check interface
+    // if (!$product instanceof ProductInterface) {
+    //     throw new \InvalidArgumentException('Provided product must implement ProductInterface');
+    // }
     $this->product = $product;

     return $this;
 }

Likely invalid or redundant comment.

Comment on lines 56 to 59
public function end(): self
{
// Validate the TariffType and lock its state
return $this;
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

Finalize or lock TariffType upon "end"
The "end()" method includes a comment about validating the TariffType and locking its state, but lacks any actual implementation. Consider adding the necessary steps to prevent further modification after this point, or rename it to better describe its purpose (e.g., "finalize" or "build").


class ConsumptionDefinition
{
private ConsumptionColumnCollection $columnCollection;
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

Undefined ConsumptionColumnCollection class
Static analysis reports that "ConsumptionColumnCollection" is not found in the "hiqdev\php\billing\product" namespace. Make sure you have created and properly namespaced this class or import it from the correct location to avoid fatal errors.

Would you like assistance in defining or locating "ConsumptionColumnCollection"?

🧰 Tools
🪛 GitHub Check: PHP 8.3

[failure] 7-7: UndefinedClass
src/product/ConsumptionDefinition.php:7:13: UndefinedClass: Class, interface or enum named hiqdev\php\billing\product\ConsumptionColumnCollection does not exist (see https://psalm.dev/019)

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: 6

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

7-7: ⚠️ Potential issue

Undefined ConsumptionColumnCollection class

The ConsumptionColumnCollection class is not found in the current namespace.

Please ensure the class is properly imported:

+use hiqdev\php\billing\product\ConsumptionColumnCollection;
🧹 Nitpick comments (5)
src/product/ConsumptionDefinition.php (2)

5-9: Add class documentation

Please add PHPDoc to describe the class purpose and its role in the billing system. This will help other developers understand how this class fits into the overall architecture.

+/**
+ * ConsumptionDefinition manages the configuration of consumption-based billing
+ * through collections of columns and groups.
+ *
+ * @see ConsumptionColumnCollection
+ * @see ConsumptionGroupCollection
+ */
 class ConsumptionDefinition

10-14: Consider validating parent TariffType in constructor

The constructor accepts a TariffType without any validation. Consider adding validation to ensure the parent is in a valid state.

 public function __construct(private readonly TariffType $parent)
 {
+    if (!$parent->isValid()) {
+        throw new \InvalidArgumentException('Invalid TariffType provided');
+    }
     $this->columnCollection = new ConsumptionColumnCollection($this);
     $this->groupCollection = new ConsumptionGroupCollection($this);
 }
src/product/ConsumptionGroupCollection.php (1)

22-29: Consider adding validation in group() method

The group() method creates and stores groups without any validation or limits.

 public function group(): ConsumptionGroup
 {
+    if (count($this->groups) >= 100) {
+        throw new \RuntimeException('Maximum number of groups exceeded');
+    }
     $group = new ConsumptionGroup($this);
     $this->groups[] = $group;
     return $group;
 }
src/product/ConsumptionGroup.php (1)

25-30: Add validation in add() method

The add() method accepts PriceType objects without any validation.

 public function add(PriceType $priceType): self
 {
+    if ($priceType->isExpired()) {
+        throw new \InvalidArgumentException('Cannot add expired price type');
+    }
     $this->group[] = $priceType;
     return $this;
 }
🧰 Tools
🪛 GitHub Check: PHP 8.3

[failure] 25-25: UndefinedClass
src/product/ConsumptionGroup.php:25:25: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\product\PriceType does not exist (see https://psalm.dev/019)

src/product/ConsumptionColumn.php (1)

9-18: Add property documentation

Consider adding PHPDoc comments to explain the purpose and impact of $isConvertible and $isOverMax flags.

+    /**
+     * Indicates if the consumption can be converted to different units
+     */
     private bool $isConvertible = false;

+    /**
+     * Indicates if the consumption exceeds maximum allowed value
+     */
     private bool $isOverMax = false;
🧰 Tools
🪛 GitHub Check: PHP 8.3

[failure] 15-15: UndefinedClass
src/product/ConsumptionColumn.php:15:9: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\product\PriceType does not exist (see https://psalm.dev/019)

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between a1c0436 and be007f7.

📒 Files selected for processing (7)
  • src/product/BillingRegistry.php (1 hunks)
  • src/product/ConsumptionColumn.php (1 hunks)
  • src/product/ConsumptionColumnCollection.php (1 hunks)
  • src/product/ConsumptionDefinition.php (1 hunks)
  • src/product/ConsumptionGroup.php (1 hunks)
  • src/product/ConsumptionGroupCollection.php (1 hunks)
  • src/product/PriceTypeDefinition.php (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/product/PriceTypeDefinition.php
🧰 Additional context used
🪛 GitHub Check: PHP 8.3
src/product/ConsumptionGroup.php

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


[failure] 18-18: UndefinedDocblockClass
src/product/ConsumptionGroup.php:18:16: UndefinedDocblockClass: Docblock-defined class, interface or enum named hiqdev\billing\registry\product\PriceType does not exist (see https://psalm.dev/200)


[failure] 25-25: UndefinedClass
src/product/ConsumptionGroup.php:25:25: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\product\PriceType does not exist (see https://psalm.dev/019)

src/product/ConsumptionColumn.php

[failure] 15-15: UndefinedClass
src/product/ConsumptionColumn.php:15:9: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\product\PriceType does not exist (see https://psalm.dev/019)


[failure] 44-44: UndefinedClass
src/product/ConsumptionColumn.php:44:34: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\product\PriceType does not exist (see https://psalm.dev/019)

src/product/ConsumptionColumnCollection.php

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


[failure] 24-24: UndefinedClass
src/product/ConsumptionColumnCollection.php:24:28: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\product\PriceType does not exist (see https://psalm.dev/019)

src/product/ConsumptionGroupCollection.php

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

src/product/BillingRegistry.php

[failure] 41-41: UndefinedDocblockClass
src/product/BillingRegistry.php:41:16: UndefinedDocblockClass: Docblock-defined class, interface or enum named hiqdev\billing\registry\invoice\RepresentationInterface does not exist (see https://psalm.dev/200)


[failure] 59-59: UndefinedClass
src/product/BillingRegistry.php:59:9: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\quantity\FractionQuantityData does not exist (see https://psalm.dev/019)

🔇 Additional comments (10)
src/product/BillingRegistry.php (5)

16-23: Add validation for duplicate tariff types.

The current implementation allows duplicate tariff types to be added, which could lead to data integrity issues.


25-28: LGTM! The lock mechanism is simple and effective.

The one-way locking mechanism prevents modifications after initialization.


30-37: Add error handling and improve return type specificity.

The method should handle potential errors from withPrices() and provide a more specific return type hint.


43-55: Add validation for representation class existence.

The method should validate that the provided class exists and implements RepresentationInterface.


57-70: Enhance error context in createQuantityFormatter.

When quantity formatters are not found, you throw a generic exception. Consider adding details about which $type was not found to aid troubleshooting.

🧰 Tools
🪛 GitHub Check: PHP 8.3

[failure] 59-59: UndefinedClass
src/product/BillingRegistry.php:59:9: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\quantity\FractionQuantityData does not exist (see https://psalm.dev/019)

src/product/ConsumptionColumnCollection.php (3)

9-14: LGTM!

Good use of constructor property promotion and proper type declarations.


16-22: LGTM!

Proper implementation of the IteratorAggregate interface with clear return type documentation.


24-36: Verify PriceType class dependency

The code references PriceType from hiqdev\billing\registry\product namespace, but static analysis indicates this class might not exist.

If the class doesn't exist, consider creating it or updating the import path.

🧰 Tools
🪛 GitHub Check: PHP 8.3

[failure] 24-24: UndefinedClass
src/product/ConsumptionColumnCollection.php:24:28: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\product\PriceType does not exist (see https://psalm.dev/019)

src/product/ConsumptionColumn.php (2)

20-42: LGTM!

Good implementation of fluent interface pattern with clear state management methods.


44-52: LGTM!

Clean implementation of accessor methods. Note that the PriceType dependency verification was requested in the previous file's review.

🧰 Tools
🪛 GitHub Check: PHP 8.3

[failure] 44-44: UndefinedClass
src/product/ConsumptionColumn.php:44:34: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\product\PriceType does not exist (see https://psalm.dev/019)

Comment on lines 72 to 132
public function getConsumptionColumns(): \Generator
{
foreach ($this->tariffTypes as $tariffType) {
yield $tariffType->consumption()->columns();
}
}

public function getConsumptionGroups(): \Generator
{
foreach ($this->tariffTypes as $tariffType) {
yield $tariffType->consumption()->groups();
}
}
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

Add error handling for consumption methods.

Both methods assume that consumption() will always return a valid object. Consider adding null checks and error handling.

 public function getConsumptionColumns(): \Generator
 {
     foreach ($this->tariffTypes as $tariffType) {
-        yield $tariffType->consumption()->columns();
+        $consumption = $tariffType->consumption();
+        if ($consumption === null) {
+            throw new \RuntimeException(
+                sprintf("No consumption data available for tariff type '%s'", $tariffType->getId())
+            );
+        }
+        yield $consumption->columns();
     }
 }

 public function getConsumptionGroups(): \Generator
 {
     foreach ($this->tariffTypes as $tariffType) {
-        yield $tariffType->consumption()->groups();
+        $consumption = $tariffType->consumption();
+        if ($consumption === null) {
+            throw new \RuntimeException(
+                sprintf("No consumption data available for tariff type '%s'", $tariffType->getId())
+            );
+        }
+        yield $consumption->groups();
     }
 }
📝 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
public function getConsumptionColumns(): \Generator
{
foreach ($this->tariffTypes as $tariffType) {
yield $tariffType->consumption()->columns();
}
}
public function getConsumptionGroups(): \Generator
{
foreach ($this->tariffTypes as $tariffType) {
yield $tariffType->consumption()->groups();
}
}
public function getConsumptionColumns(): \Generator
{
foreach ($this->tariffTypes as $tariffType) {
$consumption = $tariffType->consumption();
if ($consumption === null) {
throw new \RuntimeException(
sprintf("No consumption data available for tariff type '%s'", $tariffType->getId())
);
}
yield $consumption->columns();
}
}
public function getConsumptionGroups(): \Generator
{
foreach ($this->tariffTypes as $tariffType) {
$consumption = $tariffType->consumption();
if ($consumption === null) {
throw new \RuntimeException(
sprintf("No consumption data available for tariff type '%s'", $tariffType->getId())
);
}
yield $consumption->groups();
}
}

Comment on lines 5 to 6
class ConsumptionGroupCollection implements \IteratorAggregate
{
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 IteratorAggregate implementation and docblock return type

There are two issues with the iterator implementation:

  1. Missing type parameters for IteratorAggregate
  2. Incorrect return type in docblock (ConsumptionColumn[] vs ConsumptionGroup[])
-class ConsumptionGroupCollection implements \IteratorAggregate
+class ConsumptionGroupCollection implements \IteratorAggregate<int, ConsumptionGroup>
 {
     /** @var ConsumptionGroup[] */
     private array $groups = [];

     /**
-     * @return ConsumptionColumn[]
+     * @return \Traversable<int, ConsumptionGroup>
      */
     public function getIterator(): \Traversable
     {
         return new \ArrayIterator($this->groups);
     }

Also applies to: 14-20

🧰 Tools
🪛 GitHub Check: PHP 8.3

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

Comment on lines 7 to 9
class ConsumptionGroup implements \IteratorAggregate
{
private array $group;
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 IteratorAggregate implementation and initialize group array

The class is missing type parameters for IteratorAggregate and the group array is not initialized.

-class ConsumptionGroup implements \IteratorAggregate
+class ConsumptionGroup implements \IteratorAggregate<int, PriceType>
 {
-    private array $group;
+    /** @var PriceType[] */
+    private array $group = [];
📝 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
class ConsumptionGroup implements \IteratorAggregate
{
private array $group;
class ConsumptionGroup implements \IteratorAggregate<int, PriceType>
{
/** @var PriceType[] */
private array $group = [];
🧰 Tools
🪛 GitHub Check: PHP 8.3

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

Comment on lines 5 to 6
use hiqdev\billing\registry\product\PriceType;

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 import and use statement for PriceType

The PriceType class is not found in the current namespace.

 use hiqdev\billing\registry\product\PriceType;
+use hiqdev\php\billing\product\PriceType;
📝 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
use hiqdev\billing\registry\product\PriceType;
use hiqdev\php\billing\product\PriceType;

Comment on lines 7 to 8
class ConsumptionColumnCollection implements \IteratorAggregate
{
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

Add type parameter to IteratorAggregate interface

The class implements IteratorAggregate without specifying the type parameter. In PHP 8.3, it's recommended to explicitly declare the type:

-class ConsumptionColumnCollection implements \IteratorAggregate
+class ConsumptionColumnCollection implements \IteratorAggregate<ConsumptionColumn>

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 GitHub Check: PHP 8.3

[failure] 7-7: MissingTemplateParam
src/product/ConsumptionColumnCollection.php:7:46: MissingTemplateParam: hiqdev\php\billing\product\ConsumptionColumnCollection 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: 1

🧹 Nitpick comments (3)
src/product/BehaviorInterface.php (1)

5-8: Consider renaming the method to be more descriptive

The method name class() is ambiguous and could potentially conflict with PHP's class keyword. Consider using a more descriptive name that clearly indicates the method's purpose, such as getClassName() or getBehaviorClass().

 interface BehaviorInterface
 {
-    public function class(): string;
+    public function getBehaviorClass(): string;
 }
src/product/BillingRegistry.php (2)

57-70: Enhance error context in createQuantityFormatter

The error message should include details about which type was not found.

-        throw new QuantityFormatterNotFoundException('Quantity formatter not found');
+        throw new QuantityFormatterNotFoundException(
+            sprintf("Quantity formatter not found for type '%s'", $type)
+        );
🧰 Tools
🪛 GitHub Check: PHP 8.3

[failure] 59-59: UndefinedClass
src/product/BillingRegistry.php:59:9: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\quantity\FractionQuantityData does not exist (see https://psalm.dev/019)


86-95: Add class validation to behavior method

Similar to getRepresentationsByType, validate the behavior class before processing.

 public function behavior(string $behaviorClassWrapper): \Generator
 {
+    if (!class_exists($behaviorClassWrapper)) {
+        throw new \InvalidArgumentException("Behavior class '$behaviorClassWrapper' does not exist");
+    }
+    if (!is_subclass_of($behaviorClassWrapper, BehaviorInterface::class)) {
+        throw new \InvalidArgumentException(
+            "Class '$behaviorClassWrapper' must implement BehaviorInterface"
+        );
+    }
     foreach ($this->priceTypes() as $priceTypeDefinition) {
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between be007f7 and ab1f2f5.

📒 Files selected for processing (2)
  • src/product/BehaviorInterface.php (1 hunks)
  • src/product/BillingRegistry.php (1 hunks)
🧰 Additional context used
🪛 GitHub Check: PHP 8.3
src/product/BillingRegistry.php

[failure] 41-41: UndefinedDocblockClass
src/product/BillingRegistry.php:41:16: UndefinedDocblockClass: Docblock-defined class, interface or enum named hiqdev\billing\registry\invoice\RepresentationInterface does not exist (see https://psalm.dev/200)


[failure] 59-59: UndefinedClass
src/product/BillingRegistry.php:59:9: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\quantity\FractionQuantityData does not exist (see https://psalm.dev/019)

🔇 Additional comments (4)
src/product/BillingRegistry.php (4)

16-23: Add validation for duplicate tariff types

The method should prevent duplicate tariff types to maintain data integrity.


30-37: 🛠️ Refactor suggestion

Add error handling to priceTypes method

The method should handle potential errors from withPrices() calls.

 public function priceTypes(): \Generator
 {
     foreach ($this->tariffTypes as $tariffType) {
-        foreach ($tariffType->withPrices() as $priceTypeDefinition) {
-            yield $priceTypeDefinition;
+        try {
+            foreach ($tariffType->withPrices() as $priceTypeDefinition) {
+                yield $priceTypeDefinition;
+            }
+        } catch (\Throwable $e) {
+            throw new \RuntimeException(
+                "Failed to retrieve prices for tariff type: " . $tariffType->getId(),
+                0,
+                $e
+            );
         }
     }
 }

Likely invalid or redundant comment.


72-84: 🛠️ Refactor suggestion

Add error handling for consumption methods

Both methods need null checks and error handling for the consumption calls.

 public function getConsumptionColumns(): \Generator
 {
     foreach ($this->tariffTypes as $tariffType) {
-        yield $tariffType->consumption()->columns();
+        $consumption = $tariffType->consumption();
+        if ($consumption === null) {
+            throw new \RuntimeException(
+                sprintf("No consumption data available for tariff type '%s'", $tariffType->getId())
+            );
+        }
+        yield $consumption->columns();
     }
 }

Likely invalid or redundant comment.


43-55: 🛠️ Refactor suggestion

Add class validation to getRepresentationsByType method

The method should validate that the provided class exists and implements the correct interface.

 public function getRepresentationsByType(string $representationClass): array
 {
+    if (!class_exists($representationClass)) {
+        throw new \InvalidArgumentException("Class '$representationClass' does not exist");
+    }
+    if (!is_subclass_of($representationClass, DocumentRepresentationInterface::class)) {
+        throw new \InvalidArgumentException(
+            "Class '$representationClass' must implement DocumentRepresentationInterface"
+        );
+    }
     $representations = [];

Likely invalid or redundant comment.

Comment on lines 5 to 9
use hiqdev\billing\registry\invoice\RepresentationInterface;
use hiqdev\billing\registry\quantity\formatter\QuantityFormatterNotFoundException;
use hiqdev\billing\registry\quantity\FractionQuantityData;
use hiqdev\php\billing\type\Type;
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

Update import paths to match the codebase structure

The import paths for several classes don't match the codebase structure:

-use hiqdev\billing\registry\invoice\RepresentationInterface;
-use hiqdev\billing\registry\quantity\formatter\QuantityFormatterNotFoundException;
-use hiqdev\billing\registry\quantity\FractionQuantityData;
+use hiqdev\php\billing\product\DocumentRepresentationInterface;
+use hiqdev\php\billing\product\formatter\QuantityFormatterNotFoundException;
+use hiqdev\php\billing\product\quantity\FractionQuantityData;
 use hiqdev\php\billing\type\Type;
📝 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
use hiqdev\billing\registry\invoice\RepresentationInterface;
use hiqdev\billing\registry\quantity\formatter\QuantityFormatterNotFoundException;
use hiqdev\billing\registry\quantity\FractionQuantityData;
use hiqdev\php\billing\type\Type;
use hiqdev\php\billing\product\DocumentRepresentationInterface;
use hiqdev\php\billing\product\formatter\QuantityFormatterNotFoundException;
use hiqdev\php\billing\product\quantity\FractionQuantityData;
use hiqdev\php\billing\type\Type;

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: 1

♻️ Duplicate comments (2)
src/product/PriceTypeDefinition.php (2)

88-92: ⚠️ Potential issue

Add validation in end() method

The end() method should validate that all required properties are set before returning.

     public function end(): PriceTypesCollection
     {
-        // Validate the PriceType and lock its state
+        if ($this->unit === null) {
+            throw new \RuntimeException('Unit is required but not set');
+        }
+        if (empty($this->description)) {
+            throw new \RuntimeException('Description is required but not set');
+        }
+        if ($this->quantityFormatterDefinition === null) {
+            throw new \RuntimeException('Quantity formatter is required but not set');
+        }
         return $this->parent;
     }

99-102: ⚠️ Potential issue

Remove or implement measuredWith method

The method appears to be a no-op, returning $this without any implementation.

Either implement the method's logic or remove it if it's not needed. If it's intended for future use, document it as such:

+    /**
+     * @todo Implement measurement logic
+     * This method is intended to configure measurement collection using RcpTrafCollector
+     * but is currently not implemented.
+     */
     public function measuredWith(\hiqdev\billing\registry\measure\RcpTrafCollector $param): self
     {
+        throw new \RuntimeException('Method not implemented');
-        return $this;
     }
🧹 Nitpick comments (3)
src/product/BehaviorInterface.php (1)

5-10: Enhance interface documentation

While marker interfaces are valid design patterns, the documentation could be more descriptive about:

  • The purpose and benefits of marking a class as a product behavior
  • Examples of concrete behaviors that might implement this interface
  • How the interface integrates with the billing system
 /**
- * Empty interface for mark product behavior
+ * Marker interface for product behaviors in the billing system.
+ *
+ * Classes implementing this interface represent specific billing behaviors
+ * that can be attached to products. Examples might include:
+ * - Recurring billing behavior
+ * - Usage-based billing behavior
+ * - Tiered pricing behavior
+ *
+ * These behaviors can be registered with products through the BehaviorCollection.
  */
src/product/PriceTypeDefinition.php (2)

41-44: Document or remove empty hook method

The init() method is marked as a hook but lacks documentation explaining its purpose and when it should be overridden.

+    /**
+     * Hook method called after constructor initialization.
+     * Override this method in child classes to perform additional initialization.
+     */
     protected function init(): void
     {
-        // Hook
     }

124-130: Fix documentation language and typos

The PHPDoc contains typos and is written in a non-English language.

     /**
-     * це параметер визначає агрегатну функцію яка застосовується для щоденно записаних ресурсів щоб визнизначти
-     * місячне споживання за яке потрібно пробілити клієнта
+     * This parameter defines the aggregate function that is applied to daily recorded resources
+     * to determine the monthly consumption for which the client needs to be billed.
      *
      * @param Aggregate $aggregate
      * @return self
      */
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between ab1f2f5 and dd5e730.

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

5-16: ⚠️ Potential issue

Add missing dependency in composer.json

The class has multiple dependencies from the hiqdev\billing\registry namespace, but there's no corresponding package dependency declared.

Add the required dependency to composer.json:

 {
     "require": {
+        "hiqdev/billing-registry": "^1.0"
     }
 }

Likely invalid or redundant comment.


19-29: 🛠️ Refactor suggestion

Initialize properties to prevent undefined property access

Properties should be initialized with default values or marked as nullable to prevent undefined property access.

-    private UnitInterface $unit;
+    private ?UnitInterface $unit = null;
 
-    private string $description;
+    private string $description = '';
 
-    private QuantityFormatterDefinition $quantityFormatterDefinition;
+    private ?QuantityFormatterDefinition $quantityFormatterDefinition = null;
 
     private InvoiceRepresentationCollection $invoiceCollection;
 
     private BehaviorCollection $behaviorCollection;
 
-    private Aggregate $aggregate;
+    private ?Aggregate $aggregate = null;

Likely invalid or redundant comment.

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 (5)
src/product/BillingRegistry.php (5)

5-8: ⚠️ Potential issue

Update import paths to match the codebase structure

The import paths for several classes don't match the project's namespace structure. They should be updated to use the hiqdev\php\billing\product namespace.

-use hiqdev\billing\registry\behavior\BehaviorNotFoundException;
-use hiqdev\billing\registry\invoice\RepresentationInterface;
-use hiqdev\billing\registry\quantity\formatter\QuantityFormatterNotFoundException;
-use hiqdev\billing\registry\quantity\FractionQuantityData;
+use hiqdev\php\billing\product\behavior\BehaviorNotFoundException;
+use hiqdev\php\billing\product\invoice\RepresentationInterface;
+use hiqdev\php\billing\product\quantity\formatter\QuantityFormatterNotFoundException;
+use hiqdev\php\billing\product\quantity\FractionQuantityData;

18-25: 🛠️ Refactor suggestion

Enhance validation in addTariffType method

While the basic locked state validation is good, consider these improvements:

  1. Add validation for duplicate tariff types (as previously suggested)
  2. Add type validation to ensure TariffType class exists
 public function addTariffType(TariffType $tariffType): void
 {
     if ($this->locked) {
         throw new \RuntimeException("BillingRegistry is locked and cannot be modified.");
     }
 
+    if (!class_exists(TariffType::class)) {
+        throw new \RuntimeException("TariffType class is not defined.");
+    }
+
+    foreach ($this->tariffTypes as $existingTariff) {
+        if ($existingTariff->getId() === $tariffType->getId()) {
+            throw new \RuntimeException("Tariff type with ID '{$tariffType->getId()}' already exists.");
+        }
+    }
+
     $this->tariffTypes[] = $tariffType;
 }

32-39: 🛠️ Refactor suggestion

Add error handling and improve documentation

The method should handle potential errors from withPrices() and include proper return type documentation.

+/**
+ * @return \Generator<PriceTypeDefinition>
+ * @throws \RuntimeException When price retrieval fails
+ */
 public function priceTypes(): \Generator
 {
     foreach ($this->tariffTypes as $tariffType) {
+        try {
             foreach ($tariffType->withPrices() as $priceTypeDefinition) {
                 yield $priceTypeDefinition;
             }
+        } catch (\Throwable $e) {
+            throw new \RuntimeException(
+                "Failed to retrieve prices for tariff type: " . $tariffType->getId(),
+                0,
+                $e
+            );
+        }
     }
 }

45-57: 🛠️ Refactor suggestion

Add validation for representation class

The method should validate the existence and interface implementation of the provided class.

 public function getRepresentationsByType(string $representationClass): array
 {
+    if (!class_exists($representationClass)) {
+        throw new \InvalidArgumentException("Class '$representationClass' does not exist");
+    }
+    if (!is_subclass_of($representationClass, RepresentationInterface::class)) {
+        throw new \InvalidArgumentException("Class '$representationClass' must implement RepresentationInterface");
+    }
+
     $representations = [];
     foreach ($this->priceTypes() as $priceTypeDefinition) {
         foreach ($priceTypeDefinition->documentRepresentation() as $representation) {

79-91: 🛠️ Refactor suggestion

Add error handling for consumption methods

Both methods need null checks and proper error handling.

+/**
+ * @return \Generator<ConsumptionColumn>
+ * @throws \RuntimeException When consumption data is not available
+ */
 public function getConsumptionColumns(): \Generator
 {
     foreach ($this->tariffTypes as $tariffType) {
-        yield $tariffType->consumption()->columns();
+        $consumption = $tariffType->consumption();
+        if ($consumption === null) {
+            throw new \RuntimeException(
+                sprintf("No consumption data available for tariff type '%s'", $tariffType->getId())
+            );
+        }
+        yield $consumption->columns();
     }
 }

+/**
+ * @return \Generator<ConsumptionGroup>
+ * @throws \RuntimeException When consumption data is not available
+ */
 public function getConsumptionGroups(): \Generator
 {
     foreach ($this->tariffTypes as $tariffType) {
-        yield $tariffType->consumption()->groups();
+        $consumption = $tariffType->consumption();
+        if ($consumption === null) {
+            throw new \RuntimeException(
+                sprintf("No consumption data available for tariff type '%s'", $tariffType->getId())
+            );
+        }
+        yield $consumption->groups();
     }
 }
🧹 Nitpick comments (1)
src/product/BillingRegistry.php (1)

12-13: Add class-level documentation and consider extracting constants

The class would benefit from comprehensive documentation and constant extraction for error messages.

+/**
+ * Registry for managing billing-related entities such as tariff types, price types,
+ * and their associated behaviors.
+ *
+ * This class implements the Registry pattern to maintain a collection of tariff types
+ * and provide access to their various aspects such as price types, consumption data,
+ * and behaviors.
+ *
+ * @implements BillingRegistryInterface
+ */
 class BillingRegistry implements BillingRegistryInterface
 {
+    private const ERROR_REGISTRY_LOCKED = "BillingRegistry is locked and cannot be modified.";
+    private const ERROR_DUPLICATE_TARIFF = "Tariff type with ID '%s' already exists.";
+    private const ERROR_NO_CONSUMPTION = "No consumption data available for tariff type '%s'";
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between dd5e730 and 73bf433.

📒 Files selected for processing (1)
  • src/product/BillingRegistry.php (1 hunks)
🧰 Additional context used
🪛 GitHub Check: PHP 8.3
src/product/BillingRegistry.php

[failure] 43-43: UndefinedDocblockClass
src/product/BillingRegistry.php:43:16: UndefinedDocblockClass: Docblock-defined class, interface or enum named hiqdev\billing\registry\invoice\RepresentationInterface does not exist (see https://psalm.dev/200)


[failure] 61-61: UndefinedClass
src/product/BillingRegistry.php:61:9: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\quantity\FractionQuantityData does not exist (see https://psalm.dev/019)


[failure] 107-107: UndefinedClass
src/product/BillingRegistry.php:107:19: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\behavior\BehaviorNotFoundException does not exist (see https://psalm.dev/019)

Comment on lines 93 to 132
public function behavior(string $type, string $behaviorClassWrapper): BehaviorInterface
{
$type = $this->convertStringTypeToType($type);

foreach ($this->priceTypes() as $priceTypeDefinition) {
if ($priceTypeDefinition->hasType($type)) {
foreach ($priceTypeDefinition->withBehaviors() as $behavior) {
if ($behavior instanceof $behaviorClassWrapper) {
return $behavior;
}
}
}
}

throw new BehaviorNotFoundException('Behavior class not found');
}
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

Improve behavior method documentation and error handling

The method needs better documentation, input validation, and more informative error messages.

+/**
+ * Retrieves a behavior of specified type and class.
+ *
+ * @param string $type The type identifier
+ * @param string $behaviorClassWrapper The behavior class to match
+ * @return BehaviorInterface The matched behavior
+ * @throws BehaviorNotFoundException When no matching behavior is found
+ * @throws \InvalidArgumentException When input parameters are invalid
+ */
 public function behavior(string $type, string $behaviorClassWrapper): BehaviorInterface
 {
+    if (empty($type)) {
+        throw new \InvalidArgumentException('Type cannot be empty');
+    }
+    if (!class_exists($behaviorClassWrapper)) {
+        throw new \InvalidArgumentException("Behavior class '$behaviorClassWrapper' does not exist");
+    }
+    if (!is_subclass_of($behaviorClassWrapper, BehaviorInterface::class)) {
+        throw new \InvalidArgumentException("Class '$behaviorClassWrapper' must implement BehaviorInterface");
+    }
+
     $type = $this->convertStringTypeToType($type);
 
     foreach ($this->priceTypes() as $priceTypeDefinition) {
         if ($priceTypeDefinition->hasType($type)) {
             foreach ($priceTypeDefinition->withBehaviors() as $behavior) {
                 if ($behavior instanceof $behaviorClassWrapper) {
                     return $behavior;
                 }
             }
         }
     }
 
-    throw new BehaviorNotFoundException('Behavior class not found');
+    throw new BehaviorNotFoundException(
+        sprintf("Behavior of class '%s' not found for type '%s'", $behaviorClassWrapper, $type)
+    );
 }
📝 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
public function behavior(string $type, string $behaviorClassWrapper): BehaviorInterface
{
$type = $this->convertStringTypeToType($type);
foreach ($this->priceTypes() as $priceTypeDefinition) {
if ($priceTypeDefinition->hasType($type)) {
foreach ($priceTypeDefinition->withBehaviors() as $behavior) {
if ($behavior instanceof $behaviorClassWrapper) {
return $behavior;
}
}
}
}
throw new BehaviorNotFoundException('Behavior class not found');
}
/**
* Retrieves a behavior of specified type and class.
*
* @param string $type The type identifier
* @param string $behaviorClassWrapper The behavior class to match
* @return BehaviorInterface The matched behavior
* @throws BehaviorNotFoundException When no matching behavior is found
* @throws \InvalidArgumentException When input parameters are invalid
*/
public function behavior(string $type, string $behaviorClassWrapper): BehaviorInterface
{
if (empty($type)) {
throw new \InvalidArgumentException('Type cannot be empty');
}
if (!class_exists($behaviorClassWrapper)) {
throw new \InvalidArgumentException("Behavior class '$behaviorClassWrapper' does not exist");
}
if (!is_subclass_of($behaviorClassWrapper, BehaviorInterface::class)) {
throw new \InvalidArgumentException("Class '$behaviorClassWrapper' must implement BehaviorInterface");
}
$type = $this->convertStringTypeToType($type);
foreach ($this->priceTypes() as $priceTypeDefinition) {
if ($priceTypeDefinition->hasType($type)) {
foreach ($priceTypeDefinition->withBehaviors() as $behavior) {
if ($behavior instanceof $behaviorClassWrapper) {
return $behavior;
}
}
}
}
throw new BehaviorNotFoundException(
sprintf("Behavior of class '%s' not found for type '%s'", $behaviorClassWrapper, $type)
);
}
🧰 Tools
🪛 GitHub Check: PHP 8.3

[failure] 107-107: UndefinedClass
src/product/BillingRegistry.php:107:19: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\behavior\BehaviorNotFoundException does not exist (see https://psalm.dev/019)

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: 1

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

13-15: Add validation for tariff types array

Consider adding runtime validation to ensure tariff type uniqueness and validity.

Add a method to validate tariff types:

+    /**
+     * @param TariffType $tariffType
+     * @throws \InvalidArgumentException If tariff type is invalid
+     */
+    private function validateTariffType(TariffType $tariffType): void
+    {
+        foreach ($this->tariffTypes as $existingTariff) {
+            if ($existingTariff->getId() === $tariffType->getId()) {
+                throw new \InvalidArgumentException(
+                    sprintf('Tariff type with ID "%s" already exists', $tariffType->getId())
+                );
+            }
+        }
+    }

73-76: Add input validation to convertStringTypeToType method

The method should validate the input string before conversion.

Apply this diff to add validation:

 private function convertStringTypeToType(string $type): TypeInterface
 {
+    if (empty($type)) {
+        throw new \InvalidArgumentException('Type string cannot be empty');
+    }
     return Type::anyId($type);
 }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 73bf433 and 4c4d0a5.

📒 Files selected for processing (2)
  • src/product/BillingRegistry.php (1 hunks)
  • src/product/TariffType.php (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/product/TariffType.php
🧰 Additional context used
🪛 GitHub Check: PHP 8.3
src/product/BillingRegistry.php

[failure] 42-42: UndefinedDocblockClass
src/product/BillingRegistry.php:42:16: UndefinedDocblockClass: Docblock-defined class, interface or enum named hiqdev\billing\registry\invoice\RepresentationInterface does not exist (see https://psalm.dev/200)


[failure] 60-60: UndefinedClass
src/product/BillingRegistry.php:60:9: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\quantity\FractionQuantityData does not exist (see https://psalm.dev/019)

🔇 Additional comments (5)
src/product/BillingRegistry.php (5)

58-71: 🛠️ Refactor suggestion

Improve createQuantityFormatter method validation and error handling

The method needs input validation and more descriptive error messages.

Apply this diff to improve the method:

+    /**
+     * @throws QuantityFormatterNotFoundException
+     * @throws \InvalidArgumentException
+     */
     public function createQuantityFormatter(
         string $type,
         FractionQuantityData $data,
     ): array {
+        if (empty($type)) {
+            throw new \InvalidArgumentException('Type cannot be empty');
+        }
+
         $type = $this->convertStringTypeToType($type);
 
         foreach ($this->priceTypes() as $priceTypeDefinition) {
             if ($priceTypeDefinition->hasType($type)) {
                 return $priceTypeDefinition->createQuantityFormatter($data);
             }
         }
 
-        throw new QuantityFormatterNotFoundException('Quantity formatter not found');
+        throw new QuantityFormatterNotFoundException(
+            sprintf('Quantity formatter not found for type "%s"', $type)
+        );
     }

Likely invalid or redundant comment.

🧰 Tools
🪛 GitHub Check: PHP 8.3

[failure] 60-60: UndefinedClass
src/product/BillingRegistry.php:60:9: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\quantity\FractionQuantityData does not exist (see https://psalm.dev/019)


5-8: ⚠️ Potential issue

Fix incorrect import paths

The import paths for several classes don't match the project structure. They should be updated to use the correct namespace.

Apply this diff to fix the import paths:

-use hiqdev\billing\registry\invoice\RepresentationInterface;
-use hiqdev\billing\registry\quantity\formatter\QuantityFormatterNotFoundException;
-use hiqdev\billing\registry\quantity\FractionQuantityData;
+use hiqdev\php\billing\invoice\RepresentationInterface;
+use hiqdev\php\billing\quantity\formatter\QuantityFormatterNotFoundException;
+use hiqdev\php\billing\quantity\FractionQuantityData;

Likely invalid or redundant comment.


31-38: 🛠️ Refactor suggestion

Add error handling for withPrices calls

The method should handle potential errors when retrieving prices from tariff types.

Apply this diff to add error handling:

 public function priceTypes(): \Generator
 {
     foreach ($this->tariffTypes as $tariffType) {
-        foreach ($tariffType->withPrices() as $priceTypeDefinition) {
-            yield $priceTypeDefinition;
+        try {
+            foreach ($tariffType->withPrices() as $priceTypeDefinition) {
+                yield $priceTypeDefinition;
+            }
+        } catch (\Throwable $e) {
+            throw new \RuntimeException(
+                sprintf('Failed to retrieve prices from tariff type "%s": %s',
+                    $tariffType->getId(),
+                    $e->getMessage()
+                ),
+                0,
+                $e
+            );
         }
     }
 }

Likely invalid or redundant comment.


44-56: 🛠️ Refactor suggestion

Validate representation class parameter

The method should validate that the provided class exists and implements RepresentationInterface.

Apply this diff to add validation:

 public function getRepresentationsByType(string $representationClass): array
 {
+    if (!class_exists($representationClass)) {
+        throw new \InvalidArgumentException(
+            sprintf('Representation class "%s" does not exist', $representationClass)
+        );
+    }
+    if (!is_subclass_of($representationClass, RepresentationInterface::class)) {
+        throw new \InvalidArgumentException(
+            sprintf('Class "%s" must implement RepresentationInterface', $representationClass)
+        );
+    }
+
     $representations = [];
     foreach ($this->priceTypes() as $priceTypeDefinition) {

Likely invalid or redundant comment.


17-24: 🛠️ Refactor suggestion

Enhance tariff type validation in addTariffType method

The method should validate the tariff type before adding it to the registry.

Apply this diff to improve validation:

 public function addTariffType(TariffType $tariffType): void
 {
     if ($this->locked) {
         throw new \RuntimeException("BillingRegistry is locked and cannot be modified.");
     }
 
+    $this->validateTariffType($tariffType);
     $this->tariffTypes[] = $tariffType;
 }

Likely invalid or redundant comment.

Comment on lines 78 to 132
public function getBehavior(string $type, string $behaviorClassWrapper): ?BehaviorInterface
{
$type = $this->convertStringTypeToType($type);

foreach ($this->priceTypes() as $priceTypeDefinition) {
if ($priceTypeDefinition->hasType($type)) {
foreach ($priceTypeDefinition->withBehaviors() as $behavior) {
if ($behavior instanceof $behaviorClassWrapper) {
return $behavior;
}
}
}
}

return null;
}
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

Validate behavior class parameter

The method should validate that the provided class exists and implements BehaviorInterface.

Apply this diff to add validation:

+    /**
+     * @throws \InvalidArgumentException
+     */
     public function getBehavior(string $type, string $behaviorClassWrapper): ?BehaviorInterface
     {
+        if (!class_exists($behaviorClassWrapper)) {
+            throw new \InvalidArgumentException(
+                sprintf('Behavior class "%s" does not exist', $behaviorClassWrapper)
+            );
+        }
+        if (!is_subclass_of($behaviorClassWrapper, BehaviorInterface::class)) {
+            throw new \InvalidArgumentException(
+                sprintf('Class "%s" must implement BehaviorInterface', $behaviorClassWrapper)
+            );
+        }
+
         $type = $this->convertStringTypeToType($type);
📝 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
public function getBehavior(string $type, string $behaviorClassWrapper): ?BehaviorInterface
{
$type = $this->convertStringTypeToType($type);
foreach ($this->priceTypes() as $priceTypeDefinition) {
if ($priceTypeDefinition->hasType($type)) {
foreach ($priceTypeDefinition->withBehaviors() as $behavior) {
if ($behavior instanceof $behaviorClassWrapper) {
return $behavior;
}
}
}
}
return null;
}
/**
* @throws \InvalidArgumentException
*/
public function getBehavior(string $type, string $behaviorClassWrapper): ?BehaviorInterface
{
if (!class_exists($behaviorClassWrapper)) {
throw new \InvalidArgumentException(
sprintf('Behavior class "%s" does not exist', $behaviorClassWrapper)
);
}
if (!is_subclass_of($behaviorClassWrapper, BehaviorInterface::class)) {
throw new \InvalidArgumentException(
sprintf('Class "%s" must implement BehaviorInterface', $behaviorClassWrapper)
);
}
$type = $this->convertStringTypeToType($type);
foreach ($this->priceTypes() as $priceTypeDefinition) {
if ($priceTypeDefinition->hasType($type)) {
foreach ($priceTypeDefinition->withBehaviors() as $behavior) {
if ($behavior instanceof $behaviorClassWrapper) {
return $behavior;
}
}
}
}
return null;
}

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

♻️ Duplicate comments (5)
src/product/PriceTypeDefinition.php (5)

5-12: ⚠️ Potential issue

Add missing dependency for registry namespace

The class depends on multiple classes from the hiqdev\billing\registry namespace, but there's no corresponding dependency in composer.json.

Add the required dependency to composer.json that provides the hiqdev\billing\registry namespace.


48-53: ⚠️ Potential issue

Add input validation for the unit

The unit value should be validated before passing to Unit::create().

If $unit can be empty or invalid, a runtime error might occur in Unit::create(). Add input validation to ensure $unit is not empty and is recognized by Unit::create().


72-78: ⚠️ Potential issue

Implement class existence validation

The TODO comment indicates missing validation for the formatter class.

Add validation to ensure the formatter class exists and implements the correct interface.


90-94: ⚠️ Potential issue

Add validation in end() method

The end() method should validate that all required properties are set before returning.

Add validation to ensure required properties (unit, description, quantityFormatterDefinition) are set before returning to the parent.


101-104: ⚠️ Potential issue

Implement the measuredWith logic

The method currently returns $this without implementing any logic.

Either implement the measurement logic or remove the method if it's not needed.

🧹 Nitpick comments (12)
src/product/PriceTypeDefinitionCollection.php (3)

10-16: Use explicit array typing for clarity
The private array property $pricesGroupedByPriceType could be more self-documenting if you annotate it with @var array<string, PriceTypeDefinition[]>, helping IDEs and static analyzers understand its structure.

 /**
  * @var array<string, PriceTypeDefinition[]>
  */
 private array $pricesGroupedByPriceType = [];

18-36: Consider implementing Countable
Your class implements \IteratorAggregate to allow iteration over the list of prices. If you also implement \Countable, you may conveniently provide the number of price definitions.


38-108: Reduce code duplication for similar methods
All methods (e.g., monthly, overuse, domain, etc.) differ only by GType. Consider creating a single private helper that accepts a GType and PriceType and returns a PriceTypeDefinition, to streamline the code.

-public function monthly(PriceType $type): PriceTypeDefinition
-{
-    $priceType = $this->createPriceTypeDefinition(GType::monthly, $type, $this->parent->tariffType());
-    $this->addPriceTypeDefinition($type, $priceType);
-    return $priceType;
-}
...
+private function definePrice(GType $gType, PriceType $type): PriceTypeDefinition
+{
+    $priceType = $this->createPriceTypeDefinition($gType, $type, $this->parent->tariffType());
+    $this->addPriceTypeDefinition($type, $priceType);
+    return $priceType;
+}
+
+public function monthly(PriceType $type): PriceTypeDefinition
+{
+    return $this->definePrice(GType::monthly, $type);
+}
src/product/BehaviorCollection.php (2)

7-7: Annotate IteratorAggregate with a generic
Psalm or PhpStan may complain about missing generic params. Consider adding template annotations, e.g. IteratorAggregate<BehaviorInterface>, to satisfy stricter static analyzers.

-class BehaviorCollection implements \IteratorAggregate
+/**
+ * @implements \IteratorAggregate<int, BehaviorInterface>
+ */
+class BehaviorCollection implements \IteratorAggregate
🧰 Tools
🪛 GitHub Check: PHP 8.3

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


24-31: Validate BehaviorInterface implementations
Before attaching a behavior, consider checking if it indeed matches your expected interface shape (e.g., ensuring mandatory methods exist). This can help catch misconfigurations earlier.

src/product/TariffTypeDefinition.php (2)

1-21: Document how $product is utilized
Although $product is stored, you may want to include brief doc comments about its role within the definition (e.g., whether it’s used for referencing metadata or external logic).


23-56: Extend method-level documentation
Some public methods like end() and setPricesSuggester() lack details on their purpose and expected usage. Adding doc comments can clarify behavior and make the API more discoverable.

src/product/BillingRegistry.php (3)

17-24: Consider preventing duplicate tariff types
When adding a new tariff type, you may want to ensure uniqueness. If one with the same identity exists, throwing an exception guards against accidental overwrites.

🧰 Tools
🪛 GitHub Check: PHP 8.3

[failure] 17-17: MissingDependency
src/product/BillingRegistry.php:17:35: MissingDependency: hiqdev\php\billing\product\TariffTypeDefinition depends on class or interface hiqdev\php\billing\product\parentnodedefinitioninterface that does not exist (see https://psalm.dev/157)


40-56: Short-circuit by returning early
In getRepresentationsByType, consider short-circuiting once you find enough representations or if you anticipate large loops. This can improve performance in big registries.

🧰 Tools
🪛 GitHub Check: PHP 8.3

[failure] 42-42: UndefinedDocblockClass
src/product/BillingRegistry.php:42:16: UndefinedDocblockClass: Docblock-defined class, interface or enum named hiqdev\billing\registry\invoice\RepresentationInterface does not exist (see https://psalm.dev/200)


58-71: Provide clearer error context
When throwing QuantityFormatterNotFoundException, consider including the requested $type, making debugging easier.

-throw new QuantityFormatterNotFoundException('Quantity formatter not found');
+throw new QuantityFormatterNotFoundException(
+    sprintf("Quantity formatter not found for type '%s'", (string) $type)
+);
🧰 Tools
🪛 GitHub Check: PHP 8.3

[failure] 60-60: UndefinedClass
src/product/BillingRegistry.php:60:9: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\quantity\FractionQuantityData does not exist (see https://psalm.dev/019)

src/product/PriceTypeDefinition.php (2)

43-46: Document or implement the init hook

The empty init() method suggests it's a hook for initialization, but its purpose is not documented.

Add PHPDoc explaining the purpose of this hook method and when it should be overridden:

+    /**
+     * Hook method for additional initialization in derived classes.
+     * Override this method to perform custom initialization logic.
+     */
     protected function init(): void
     {
         // Hook
     }

127-129: Translate comments to English

The PHPDoc comments are written in Ukrainian and should be translated to English for consistency.

Replace the Ukrainian comments with English translation:

-     * це параметер визначає агрегатну функцію яка застосовується для щоденно записаних ресурсів щоб визнизначти
-     * місячне споживання за яке потрібно пробілити клієнта
+     * This parameter defines the aggregate function that is applied to daily recorded resources
+     * to determine the monthly consumption for which the client needs to be billed
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 4c4d0a5 and b414de3.

📒 Files selected for processing (6)
  • src/product/BehaviorCollection.php (1 hunks)
  • src/product/BehaviorInterface.php (1 hunks)
  • src/product/BillingRegistry.php (1 hunks)
  • src/product/PriceTypeDefinition.php (1 hunks)
  • src/product/PriceTypeDefinitionCollection.php (1 hunks)
  • src/product/TariffTypeDefinition.php (1 hunks)
🧰 Additional context used
🪛 GitHub Check: PHP 8.3
src/product/BehaviorInterface.php

[failure] 12-12: UndefinedClass
src/product/BehaviorInterface.php:12:35: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\domain\TariffType does not exist (see https://psalm.dev/019)


[failure] 14-14: UndefinedClass
src/product/BehaviorInterface.php:14:38: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\domain\TariffType does not exist (see https://psalm.dev/019)

src/product/BehaviorCollection.php

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


[failure] 12-12: UndefinedClass
src/product/BehaviorCollection.php:12:33: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\domain\TariffType does not exist (see https://psalm.dev/019)

src/product/BillingRegistry.php

[failure] 14-14: MissingDependency
src/product/BillingRegistry.php:14:5: MissingDependency: hiqdev\php\billing\product\TariffTypeDefinition depends on class or interface hiqdev\php\billing\product\parentnodedefinitioninterface that does not exist (see https://psalm.dev/157)


[failure] 14-14: MissingDependency
src/product/BillingRegistry.php:14:13: MissingDependency: hiqdev\php\billing\product\TariffTypeDefinition depends on class or interface hiqdev\php\billing\product\parentnodedefinitioninterface that does not exist (see https://psalm.dev/157)


[failure] 17-17: MissingDependency
src/product/BillingRegistry.php:17:35: MissingDependency: hiqdev\php\billing\product\TariffTypeDefinition depends on class or interface hiqdev\php\billing\product\parentnodedefinitioninterface that does not exist (see https://psalm.dev/157)


[failure] 34-34: MissingDependency
src/product/BillingRegistry.php:34:22: MissingDependency: hiqdev\php\billing\product\TariffTypeDefinition depends on class or interface hiqdev\php\billing\product\parentnodedefinitioninterface that does not exist (see https://psalm.dev/157)


[failure] 42-42: UndefinedDocblockClass
src/product/BillingRegistry.php:42:16: UndefinedDocblockClass: Docblock-defined class, interface or enum named hiqdev\billing\registry\invoice\RepresentationInterface does not exist (see https://psalm.dev/200)


[failure] 60-60: UndefinedClass
src/product/BillingRegistry.php:60:9: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\quantity\FractionQuantityData does not exist (see https://psalm.dev/019)

🔇 Additional comments (3)
src/product/BehaviorInterface.php (1)

7-15: Ensure correct namespace usage
Static analysis indicates that hiqdev\billing\registry\domain\TariffType might be missing or incorrectly referenced. Verify that the namespace path to TariffType is valid or adjust your use statement.

🧰 Tools
🪛 GitHub Check: PHP 8.3

[failure] 12-12: UndefinedClass
src/product/BehaviorInterface.php:12:35: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\domain\TariffType does not exist (see https://psalm.dev/019)


[failure] 14-14: UndefinedClass
src/product/BehaviorInterface.php:14:38: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\domain\TariffType does not exist (see https://psalm.dev/019)

src/product/BillingRegistry.php (1)

78-93: Clarify fallback behavior
If getBehavior returns null when no match is found, it may be beneficial to log or throw an exception, depending on usage. This ensures calling code is aware if the behavior truly doesn't exist.

src/product/PriceTypeDefinition.php (1)

20-31: 🛠️ Refactor suggestion

Initialize properties to prevent undefined access

Properties should be initialized with default values to prevent undefined property access.

Apply this diff:

-    private UnitInterface $unit;
+    private UnitInterface $unit;
 
-    private string $description;
+    private string $description = '';
 
-    private QuantityFormatterDefinition $quantityFormatterDefinition;
+    private ?QuantityFormatterDefinition $quantityFormatterDefinition = null;
 
-    private InvoiceRepresentationCollection $invoiceCollection;
+    private InvoiceRepresentationCollection $invoiceCollection;
 
-    private PriceTypeDefinitionBehaviourCollection $behaviorCollection;
+    private PriceTypeDefinitionBehaviourCollection $behaviorCollection;
 
-    private Aggregate $aggregate;
+    private ?Aggregate $aggregate = null;

Likely invalid or redundant comment.

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: 1

🧹 Nitpick comments (3)
src/product/BehaviorNotFoundException.php (1)

7-9: Add a docblock to clarify usage context

A short docblock explaining when or why this exception is thrown would make the code more self-documenting. For instance, note that it is thrown when a requested behavior class is not registered or cannot be located.

src/product/BillingRegistry.php (2)

78-93: Align naming conventions for “Behavior” vs. “Behaviour”
The exception message uses British spelling, while the class is named in American spelling. For consistency, update the message or rename the class. Also, since you always throw an exception on failure, returning a nullable ?BehaviorInterface might be misleading.

- throw new BehaviorNotFoundException('Behaviour was not found');
+ throw new BehaviorNotFoundException('Behavior was not found');

95-112: Add docblock and consider deduplication
Given that getBehaviors iterates over both tariff types and price types, it may yield the same behavior multiple times if it is declared at multiple levels. If that’s undesirable, consider filtering or deduplicating. Also, add a docblock for clarity:

 /**
+ * Retrieves a generator of all behaviors matching the specified class,
+ * searching in both tariff types and price types.
+ *
+ * @param string $behaviorClassWrapper
+ * @return \Generator<BehaviorInterface>
+ */
 public function getBehaviors(string $behaviorClassWrapper): \Generator
 {
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between b414de3 and b6893c5.

📒 Files selected for processing (2)
  • src/product/BehaviorNotFoundException.php (1 hunks)
  • src/product/BillingRegistry.php (1 hunks)
🧰 Additional context used
🪛 GitHub Check: PHP 8.3
src/product/BillingRegistry.php

[failure] 14-14: MissingDependency
src/product/BillingRegistry.php:14:5: MissingDependency: hiqdev\php\billing\product\TariffTypeDefinition depends on class or interface hiqdev\php\billing\product\parentnodedefinitioninterface that does not exist (see https://psalm.dev/157)


[failure] 14-14: MissingDependency
src/product/BillingRegistry.php:14:13: MissingDependency: hiqdev\php\billing\product\TariffTypeDefinition depends on class or interface hiqdev\php\billing\product\parentnodedefinitioninterface that does not exist (see https://psalm.dev/157)


[failure] 17-17: MissingDependency
src/product/BillingRegistry.php:17:35: MissingDependency: hiqdev\php\billing\product\TariffTypeDefinition depends on class or interface hiqdev\php\billing\product\parentnodedefinitioninterface that does not exist (see https://psalm.dev/157)


[failure] 34-34: MissingDependency
src/product/BillingRegistry.php:34:22: MissingDependency: hiqdev\php\billing\product\TariffTypeDefinition depends on class or interface hiqdev\php\billing\product\parentnodedefinitioninterface that does not exist (see https://psalm.dev/157)


[failure] 42-42: UndefinedDocblockClass
src/product/BillingRegistry.php:42:16: UndefinedDocblockClass: Docblock-defined class, interface or enum named hiqdev\billing\registry\invoice\RepresentationInterface does not exist (see https://psalm.dev/200)


[failure] 60-60: UndefinedClass
src/product/BillingRegistry.php:60:9: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\quantity\FractionQuantityData does not exist (see https://psalm.dev/019)

🔇 Additional comments (6)
src/product/BillingRegistry.php (6)

17-24: Prevent duplicate tariff-type entries
This comment is identical to a past suggestion about adding validation for duplicates. Consider checking for an existing tariff type with the same ID before adding a new one.

🧰 Tools
🪛 GitHub Check: PHP 8.3

[failure] 17-17: MissingDependency
src/product/BillingRegistry.php:17:35: MissingDependency: hiqdev\php\billing\product\TariffTypeDefinition depends on class or interface hiqdev\php\billing\product\parentnodedefinitioninterface that does not exist (see https://psalm.dev/157)


26-30: Locking mechanism confirmed
The locking mechanism is straightforward and effectively prevents further modifications.


31-38: Error handling for price retrieval
The method inherits any errors arising from each tariffType->withPrices(). A previous review recommended adding error handling and clarifying the return type. Consider reintroducing or adopting that approach if applicable.

🧰 Tools
🪛 GitHub Check: PHP 8.3

[failure] 34-34: MissingDependency
src/product/BillingRegistry.php:34:22: MissingDependency: hiqdev\php\billing\product\TariffTypeDefinition depends on class or interface hiqdev\php\billing\product\parentnodedefinitioninterface that does not exist (see https://psalm.dev/157)


40-56: Docblock references an unknown interface?
Static analysis flags RepresentationInterface in the docblock as undefined. Confirm that this interface is available and loaded in the codebase.

🧰 Tools
🪛 GitHub Check: PHP 8.3

[failure] 42-42: UndefinedDocblockClass
src/product/BillingRegistry.php:42:16: UndefinedDocblockClass: Docblock-defined class, interface or enum named hiqdev\billing\registry\invoice\RepresentationInterface does not exist (see https://psalm.dev/200)


73-76: Conversion function usage
This private method simply delegates to Type::anyId. That’s acceptable, but if your domain logic requires advanced string-to-type conversion later, consider adding validation or caching for performance.


58-71: 🛠️ Refactor suggestion

Contextualize the exception message
When throwing a QuantityFormatterNotFoundException, include the given $type to aid debugging:

-throw new QuantityFormatterNotFoundException('Quantity formatter not found');
+throw new QuantityFormatterNotFoundException(
+    sprintf("Quantity formatter not found for type '%s'", $type->getId())
+);

Likely invalid or redundant comment.

🧰 Tools
🪛 GitHub Check: PHP 8.3

[failure] 60-60: UndefinedClass
src/product/BillingRegistry.php:60:9: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\quantity\FractionQuantityData does not exist (see https://psalm.dev/019)

Comment on lines 5 to 10
use hiqdev\billing\registry\invoice\RepresentationInterface;
use hiqdev\billing\registry\quantity\formatter\QuantityFormatterNotFoundException;
use hiqdev\billing\registry\quantity\FractionQuantityData;
use hiqdev\php\billing\type\Type;
use hiqdev\php\billing\type\TypeInterface;
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

Incorrect namespace paths in imports

The imports are using incorrect namespace paths. Based on the codebase analysis:

  • DocumentRepresentationInterface is actually in hiqdev\php\billing\product namespace, not hiqdev\billing\registry\invoice
  • The other classes are imported from hiqdev\billing\registry namespace, but this dependency is not listed in composer.json

The imports should be updated to use the correct namespaces. Here are the specific issues:

  • use hiqdev\billing\registry\invoice\RepresentationInterface should be use hiqdev\php\billing\product\DocumentRepresentationInterface
  • use hiqdev\billing\registry\quantity\formatter\QuantityFormatterNotFoundException and use hiqdev\billing\registry\quantity\FractionQuantityData appear to be from an external package that needs to be added as a dependency
🔗 Analysis chain

Verify import references or rename them if necessary

Static analysis flags these imports as undefined modules. Confirm that the hiqdev\billing\registry\invoice\RepresentationInterface, hiqdev\billing\registry\quantity\formatter\QuantityFormatterNotFoundException, and hiqdev\billing\registry\quantity\FractionQuantityData classes exist in the project or rename them accordingly.

Use the following script to locate all references and verify their existence:


Let me try a different approach to verify these imports by searching for any references to these classes in the codebase.


Let me check the composer.json and the actual implementations of these interfaces/classes to verify their correct namespaces.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Looking for the definitions
ast-grep --pattern $'class RepresentationInterface { $$$ }'
ast-grep --pattern $'class QuantityFormatterNotFoundException { $$$ }'
ast-grep --pattern $'class FractionQuantityData { $$$ }'

Length of output: 190


Script:

#!/bin/bash
# Search for any references to these classes in the codebase
rg -l "RepresentationInterface" --type php
rg -l "QuantityFormatterNotFoundException" --type php
rg -l "FractionQuantityData" --type php

# Check if there are any vendor directories that might contain these dependencies
fd vendor -t d

# Look for composer.json to check dependencies
fd composer.json

Length of output: 366


Script:

#!/bin/bash
# Check composer.json for dependencies
cat composer.json

# Check the actual implementations
cat src/product/DocumentRepresentationInterface.php
cat src/product/PriceTypeDefinition.php

# Look for any namespace declarations in these files
rg -A 1 "namespace" src/product/DocumentRepresentationInterface.php src/product/PriceTypeDefinition.php

Length of output: 7504

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 (1)
src/product/ParentNodeDefinitionInterface.php (1)

5-8: Consider adding a docblock and clarifying the method purpose.
It would be beneficial to document the contract of withBehaviors() for improved maintainability and clarity, especially regarding what type of BehaviorCollection gets returned and any side effects. If BehaviorCollection resides in a different namespace, ensure that it’s properly imported.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between b6893c5 and f66e578.

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

1-1: Good use of strict typing.
This ensures type safety across the file, preventing unexpected type coercion issues.

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 (3)
src/product/PriceTypeDefinition.php (3)

5-16: ⚠️ Potential issue

Add missing billing-registry dependency

Multiple classes from the hiqdev\billing\registry namespace are not found. This is a dependency issue.

Add the required dependency to your composer.json as mentioned in the previous comment.


74-75: ⚠️ Potential issue

Implement class existence validation

The TODO comment indicates missing validation for the formatter class.

Implement the class existence check as suggested in the previous review comments.


101-104: ⚠️ Potential issue

Implement measuredWith method logic

The measuredWith method is currently a no-op, returning $this without any implementation.

Either implement the measurement logic or remove the method if it's not needed, as mentioned in previous review comments.

🧹 Nitpick comments (2)
src/product/BehaviorInterface.php (1)

7-15: Add method-level documentation

The interface methods lack PHPDoc documentation explaining their purpose and parameters.

Apply this diff:

 interface BehaviorInterface
 {
+    /**
+     * Sets the tariff type for this behavior.
+     *
+     * @param TariffType $tariffTypeName The tariff type to set
+     */
     public function setTariffType(TariffType $tariffTypeName): void;

+    /**
+     * Gets the current tariff type.
+     *
+     * @return TariffType The current tariff type
+     */
     public function getTariffType(): TariffType;
 }
🧰 Tools
🪛 GitHub Check: PHP 8.3

[failure] 12-12: UndefinedClass
src/product/BehaviorInterface.php:12:35: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\Domain\Model\TariffType does not exist (see https://psalm.dev/019)


[failure] 14-14: UndefinedClass
src/product/BehaviorInterface.php:14:38: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\Domain\Model\TariffType does not exist (see https://psalm.dev/019)

src/product/BehaviorCollection.php (1)

7-7: Add generic type hints for IteratorAggregate

The class implements IteratorAggregate but lacks the generic type parameter.

Apply this diff:

-class BehaviorCollection implements \IteratorAggregate
+class BehaviorCollection implements \IteratorAggregate<int, BehaviorInterface>
🧰 Tools
🪛 GitHub Check: PHP 8.3

[failure] 7-7: MissingTemplateParam
src/product/BehaviorCollection.php:7:37: MissingTemplateParam: hiqdev\php\billing\product\BehaviorCollection 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 f66e578 and 63f5327.

📒 Files selected for processing (5)
  • src/product/BehaviorCollection.php (1 hunks)
  • src/product/BehaviorInterface.php (1 hunks)
  • src/product/PriceTypeDefinition.php (1 hunks)
  • src/product/PriceTypeDefinitionCollection.php (1 hunks)
  • src/product/TariffTypeDefinition.php (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/product/TariffTypeDefinition.php
  • src/product/PriceTypeDefinitionCollection.php
🧰 Additional context used
🪛 GitHub Check: PHP 8.3
src/product/PriceTypeDefinition.php

[failure] 24-24: UndefinedClass
src/product/PriceTypeDefinition.php:24:13: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\quantity\formatter\QuantityFormatterDefinition does not exist (see https://psalm.dev/019)


[failure] 26-26: UndefinedClass
src/product/PriceTypeDefinition.php:26:13: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\invoice\InvoiceRepresentationCollection does not exist (see https://psalm.dev/019)


[failure] 28-28: UndefinedClass
src/product/PriceTypeDefinition.php:28:13: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\behavior\PriceTypeDefinitionBehaviourCollection does not exist (see https://psalm.dev/019)

src/product/BehaviorCollection.php

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


[failure] 12-12: UndefinedClass
src/product/BehaviorCollection.php:12:33: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\Domain\Model\TariffType does not exist (see https://psalm.dev/019)

src/product/BehaviorInterface.php

[failure] 12-12: UndefinedClass
src/product/BehaviorInterface.php:12:35: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\Domain\Model\TariffType does not exist (see https://psalm.dev/019)


[failure] 14-14: UndefinedClass
src/product/BehaviorInterface.php:14:38: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\Domain\Model\TariffType does not exist (see https://psalm.dev/019)

🔇 Additional comments (1)
src/product/BehaviorCollection.php (1)

24-31: Method implementation looks good

The attach method correctly sets the tariff type and maintains method chaining.


namespace hiqdev\php\billing\product;

use hiqdev\billing\registry\Domain\Model\TariffType;
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 missing dependency for TariffType class

The TariffType class from hiqdev\billing\registry\Domain\Model namespace is not found. This appears to be a missing dependency issue.

Add the required dependency to your composer.json:

{
    "require": {
        "hiqdev/billing-registry": "^1.0"
    }
}

Comment on lines 127 to 139
* це параметер визначає агрегатну функцію яка застосовується для щоденно записаних ресурсів щоб визнизначти
* місячне споживання за яке потрібно пробілити клієнта
*
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

Translate documentation to English

The PHPDoc comment is written in a non-English language. For better maintainability and collaboration, documentation should be in English.

Apply this diff:

-     * це параметер визначає агрегатну функцію яка застосовується для щоденно записаних ресурсів щоб визнизначти
-     * місячне споживання за яке потрібно пробілити клієнта
+     * This parameter defines the aggregate function that is applied to daily recorded resources
+     * to determine the monthly consumption for which the client needs to be billed
📝 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
* це параметер визначає агрегатну функцію яка застосовується для щоденно записаних ресурсів щоб визнизначти
* місячне споживання за яке потрібно пробілити клієнта
*
* This parameter defines the aggregate function that is applied to daily recorded resources
* to determine the monthly consumption for which the client needs to be billed
*

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: 1

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 63f5327 and 49989bf.

📒 Files selected for processing (2)
  • src/product/BillingRegistry.php (1 hunks)
  • src/product/PriceTypeDefinition.php (1 hunks)
🧰 Additional context used
🪛 GitHub Check: PHP 8.3
src/product/BillingRegistry.php

[failure] 42-42: UndefinedDocblockClass
src/product/BillingRegistry.php:42:16: UndefinedDocblockClass: Docblock-defined class, interface or enum named hiqdev\billing\registry\invoice\RepresentationInterface does not exist (see https://psalm.dev/200)


[failure] 60-60: UndefinedClass
src/product/BillingRegistry.php:60:9: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\quantity\FractionQuantityData does not exist (see https://psalm.dev/019)


[failure] 98-98: UndefinedClass
src/product/BillingRegistry.php:98:22: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\behavior\TariffTypeBehaviorCollection does not exist (see https://psalm.dev/019)

src/product/PriceTypeDefinition.php

[failure] 24-24: UndefinedClass
src/product/PriceTypeDefinition.php:24:13: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\quantity\formatter\QuantityFormatterDefinition does not exist (see https://psalm.dev/019)


[failure] 26-26: UndefinedClass
src/product/PriceTypeDefinition.php:26:13: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\invoice\InvoiceRepresentationCollection does not exist (see https://psalm.dev/019)


[failure] 28-28: UndefinedClass
src/product/PriceTypeDefinition.php:28:13: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\behavior\PriceTypeDefinitionBehaviourCollection does not exist (see https://psalm.dev/019)

🔇 Additional comments (11)
src/product/BillingRegistry.php (8)

11-15: LGTM! Properties are well-defined.

The class declaration and properties are properly typed and documented.


26-29: LGTM! Simple and effective implementation.

The lock method correctly prevents further modifications to the registry.


31-38: 🛠️ Refactor suggestion

Add error handling and improve return type specificity.

The method should handle potential errors from withPrices() and provide a more specific return type hint.

-public function priceTypes(): \Generator
+/**
+ * @return \Generator<PriceTypeDefinition>
+ * @throws \RuntimeException When price retrieval fails
+ */
+public function priceTypes(): \Generator
 {
     foreach ($this->tariffTypes as $tariffType) {
-        foreach ($tariffType->withPrices() as $priceTypeDefinition) {
-            yield $priceTypeDefinition;
+        try {
+            foreach ($tariffType->withPrices() as $priceTypeDefinition) {
+                yield $priceTypeDefinition;
+            }
+        } catch (\Throwable $e) {
+            throw new \RuntimeException(
+                sprintf("Failed to retrieve prices for tariff type '%s'", $tariffType->getId()),
+                0,
+                $e
+            );
         }
     }
 }

Likely invalid or redundant comment.


17-24: 🛠️ Refactor suggestion

Add validation for duplicate tariff types.

While the lock check is good, the method should also prevent duplicate tariff types to maintain data integrity.

 public function addTariffType(TariffTypeDefinition $tariffType): void
 {
     if ($this->locked) {
         throw new \RuntimeException("BillingRegistry is locked and cannot be modified.");
     }
 
+    foreach ($this->tariffTypes as $existingTariff) {
+        if ($existingTariff->getId() === $tariffType->getId()) {
+            throw new \RuntimeException(
+                sprintf("Tariff type with ID '%s' already exists.", $tariffType->getId())
+            );
+        }
+    }
+
     $this->tariffTypes[] = $tariffType;
 }

Likely invalid or redundant comment.


58-71: 🛠️ Refactor suggestion

Improve error handling and input validation.

The method needs better error messages and input validation.

 public function createQuantityFormatter(
     string $type,
     FractionQuantityData $data,
 ): array {
+    if (empty($type)) {
+        throw new \InvalidArgumentException('Type cannot be empty');
+    }
+
     $type = $this->convertStringTypeToType($type);
 
     foreach ($this->priceTypes() as $priceTypeDefinition) {
         if ($priceTypeDefinition->hasType($type)) {
             return $priceTypeDefinition->createQuantityFormatter($data);
         }
     }
 
-    throw new QuantityFormatterNotFoundException('Quantity formatter not found');
+    throw new QuantityFormatterNotFoundException(
+        sprintf("Quantity formatter not found for type '%s'", $type)
+    );
 }

Likely invalid or redundant comment.

🧰 Tools
🪛 GitHub Check: PHP 8.3

[failure] 60-60: UndefinedClass
src/product/BillingRegistry.php:60:9: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\quantity\FractionQuantityData does not exist (see https://psalm.dev/019)


40-56: 🛠️ Refactor suggestion

Add validation for representation class existence.

The method should validate that the provided class exists and implements RepresentationInterface.

 public function getRepresentationsByType(string $representationClass): array
 {
+    if (!class_exists($representationClass)) {
+        throw new \InvalidArgumentException(
+            sprintf("Class '%s' does not exist", $representationClass)
+        );
+    }
+    if (!is_subclass_of($representationClass, RepresentationInterface::class)) {
+        throw new \InvalidArgumentException(
+            sprintf("Class '%s' must implement RepresentationInterface", $representationClass)
+        );
+    }
+
     $representations = [];
     foreach ($this->priceTypes() as $priceTypeDefinition) {
         foreach ($priceTypeDefinition->documentRepresentation() as $representation) {

Likely invalid or redundant comment.

🧰 Tools
🪛 GitHub Check: PHP 8.3

[failure] 42-42: UndefinedDocblockClass
src/product/BillingRegistry.php:42:16: UndefinedDocblockClass: Docblock-defined class, interface or enum named hiqdev\billing\registry\invoice\RepresentationInterface does not exist (see https://psalm.dev/200)


5-8: ⚠️ Potential issue

Add missing dependency for registry classes.

The code imports several classes from the hiqdev\billing\registry namespace, but this dependency appears to be missing from the project. This will cause compilation failures.

Add the required dependency to your composer.json:

 {
     "require": {
+        "hiqdev/billing-registry": "^1.0"
     }
 }

Likely invalid or redundant comment.


78-112: 🛠️ Refactor suggestion

Add behavior class validation and improve error messages.

The methods should validate the behavior class wrapper and provide more informative error messages.

 public function getBehavior(string $type, string $behaviorClassWrapper): BehaviorInterface
 {
+    if (!class_exists($behaviorClassWrapper)) {
+        throw new \InvalidArgumentException(
+            sprintf("Behavior class '%s' does not exist", $behaviorClassWrapper)
+        );
+    }
+    if (!is_subclass_of($behaviorClassWrapper, BehaviorInterface::class)) {
+        throw new \InvalidArgumentException(
+            sprintf("Class '%s' must implement BehaviorInterface", $behaviorClassWrapper)
+        );
+    }
+
     $type = $this->convertStringTypeToType($type);
 
     foreach ($this->priceTypes() as $priceTypeDefinition) {
         if ($priceTypeDefinition->hasType($type)) {
             foreach ($priceTypeDefinition->withBehaviors() as $behavior) {
                 if ($behavior instanceof $behaviorClassWrapper) {
                     return $behavior;
                 }
             }
         }
     }
 
-    throw new BehaviorNotFoundException('Behaviour was not found');
+    throw new BehaviorNotFoundException(
+        sprintf("Behavior of class '%s' not found for type '%s'", $behaviorClassWrapper, $type)
+    );
 }

Likely invalid or redundant comment.

🧰 Tools
🪛 GitHub Check: PHP 8.3

[failure] 98-98: UndefinedClass
src/product/BillingRegistry.php:98:22: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\behavior\TariffTypeBehaviorCollection does not exist (see https://psalm.dev/019)

src/product/PriceTypeDefinition.php (3)

127-129: 🛠️ Refactor suggestion

Translate documentation to English.

Documentation should be in English for better maintainability and collaboration.

-    /**
-     * це параметер визначає агрегатну функцію яка застосовується для щоденно записаних ресурсів щоб визнизначти
-     * місячне споживання за яке потрібно пробілити клієнта
+    /**
+     * This parameter defines the aggregate function that is applied to daily recorded resources
+     * to determine the monthly consumption for which the client needs to be billed

Likely invalid or redundant comment.


73-78: 🛠️ Refactor suggestion

Implement TODO: Add class existence validation.

The TODO comment indicates missing validation for the formatter class existence.

     public function quantityFormatter(string $formatterClass, $fractionUnit = null): self
     {
-        // TODO: check if formatterClass exists
+        if (!class_exists($formatterClass)) {
+            throw new \InvalidArgumentException(
+                sprintf('Formatter class "%s" does not exist', $formatterClass)
+            );
+        }
+        if (!is_subclass_of($formatterClass, QuantityFormatterInterface::class)) {
+            throw new \InvalidArgumentException(
+                sprintf('Class "%s" must implement QuantityFormatterInterface', $formatterClass)
+            );
+        }
         $this->quantityFormatterDefinition = new QuantityFormatterDefinition($formatterClass, $fractionUnit);
 
         return $this;
     }

Likely invalid or redundant comment.


90-94: 🛠️ Refactor suggestion

Add validation in end() method.

The end() method should validate that all required properties are set before returning.

     public function end(): PriceTypeDefinitionCollection
     {
-        // Validate the PriceType and lock its state
+        $requiredProps = ['unit', 'description', 'quantityFormatterDefinition'];
+        foreach ($requiredProps as $prop) {
+            if (!isset($this->$prop)) {
+                throw new \RuntimeException(sprintf("Required property '%s' is not set", $prop));
+            }
+        }
         return $this->parent;
     }

Likely invalid or redundant comment.

Comment on lines 101 to 103
public function measuredWith(\hiqdev\billing\registry\measure\RcpTrafCollector $param): self
{
return $this;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

🛠️ Refactor suggestion

Remove unused measuredWith method

The measuredWith method in PriceTypeDefinition.php is not used anywhere in the codebase, and its implementation is a no-op that just returns $this. Since both the method and its parameter type RcpTrafCollector are only referenced in this single location, it's safe to remove this unused method.

🔗 Analysis chain

Implement measuredWith method or remove it.

The measuredWith method is currently a no-op. Either implement the measurement logic or remove the method if it's not needed.

Let me check if this method is used elsewhere:


Let me try to find any usage of this method or the collector class it depends on.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for uses of measuredWith method
rg "->measuredWith\(" --type php

Length of output: 439


Script:

#!/bin/bash
# Search for uses of measuredWith method with proper escaping
rg "measuredWith" --type php

# Also check for any references to RcpTrafCollector class
rg "RcpTrafCollector" --type php

Length of output: 327

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/BillingRegistry.php (2)

13-24: Enhance type safety and data validation

The tariff types storage implementation could be improved:

  1. The property docblock could be more specific
  2. Consider validating for duplicate tariff types before adding

Apply these improvements:

-    /** @var TariffTypeDefinition[] */
+    /** @var array<int|string, TariffTypeDefinition> */
     private array $tariffTypes = [];

     public function addTariffType(TariffTypeDefinition $tariffType): void
     {
         if ($this->locked) {
             throw new \RuntimeException("BillingRegistry is locked and cannot be modified.");
         }
+
+        foreach ($this->tariffTypes as $existingTariff) {
+            if ($existingTariff->getId() === $tariffType->getId()) {
+                throw new \RuntimeException(
+                    sprintf('Tariff type with ID "%s" already exists', $tariffType->getId())
+                );
+            }
+        }

         $this->tariffTypes[] = $tariffType;
     }

73-76: Add input validation and documentation

The type conversion helper needs input validation and better documentation.

Apply these improvements:

+    /**
+     * Converts a string type identifier to a TypeInterface instance.
+     *
+     * @param string $type The type identifier
+     * @return TypeInterface
+     * @throws \InvalidArgumentException When type is empty
+     */
     private function convertStringTypeToType(string $type): TypeInterface
     {
+        if (empty($type)) {
+            throw new \InvalidArgumentException('Type cannot be empty');
+        }
+
         return Type::anyId($type);
     }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 49989bf and 1280f4c.

📒 Files selected for processing (1)
  • src/product/BillingRegistry.php (1 hunks)
🧰 Additional context used
🪛 GitHub Check: PHP 8.3
src/product/BillingRegistry.php

[failure] 42-42: UndefinedDocblockClass
src/product/BillingRegistry.php:42:16: UndefinedDocblockClass: Docblock-defined class, interface or enum named hiqdev\billing\registry\invoice\RepresentationInterface does not exist (see https://psalm.dev/200)


[failure] 60-60: UndefinedClass
src/product/BillingRegistry.php:60:9: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\quantity\FractionQuantityData does not exist (see https://psalm.dev/019)


[failure] 104-104: UndefinedClass
src/product/BillingRegistry.php:104:22: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\behavior\TariffTypeBehaviorCollection does not exist (see https://psalm.dev/019)

🔇 Additional comments (3)
src/product/BillingRegistry.php (3)

5-8: ⚠️ Potential issue

Fix incorrect import paths

The imports from hiqdev\billing\registry namespace are incorrect. These classes should be imported from the hiqdev\php\billing namespace according to the project structure.

Apply this diff to fix the imports:

-use hiqdev\billing\registry\invoice\RepresentationInterface;
-use hiqdev\billing\registry\quantity\formatter\QuantityFormatterNotFoundException;
-use hiqdev\billing\registry\quantity\FractionQuantityData;
+use hiqdev\php\billing\product\DocumentRepresentationInterface;
+use hiqdev\php\billing\product\formatter\QuantityFormatterNotFoundException;
+use hiqdev\php\billing\product\quantity\FractionQuantityData;

Likely invalid or redundant comment.


31-38: 🛠️ Refactor suggestion

Add error handling and improve return type documentation

The price types generator needs better error handling and documentation.

Apply these improvements:

+    /**
+     * @return \Generator<int, PriceTypeDefinition>
+     * @throws \RuntimeException When price retrieval fails
+     */
     public function priceTypes(): \Generator
     {
         foreach ($this->tariffTypes as $tariffType) {
+            try {
                 foreach ($tariffType->withPrices() as $priceTypeDefinition) {
                     yield $priceTypeDefinition;
                 }
+            } catch (\Throwable $e) {
+                throw new \RuntimeException(
+                    sprintf('Failed to retrieve prices for tariff type: %s', $tariffType->getId()),
+                    0,
+                    $e
+                );
+            }
         }
     }

Likely invalid or redundant comment.


58-71: 🛠️ Refactor suggestion

Improve error handling and documentation

The quantity formatter creation needs better input validation, error messages, and documentation.

Apply these improvements:

+    /**
+     * @param string $type The type identifier
+     * @param FractionQuantityData $data The quantity data
+     * @return array{formatter: callable, params: array} The formatter configuration
+     * @throws QuantityFormatterNotFoundException When no formatter is found
+     * @throws \InvalidArgumentException When type is empty
+     */
     public function createQuantityFormatter(
         string $type,
         FractionQuantityData $data,
     ): array {
+        if (empty($type)) {
+            throw new \InvalidArgumentException('Type cannot be empty');
+        }
+
         $type = $this->convertStringTypeToType($type);

         foreach ($this->priceTypes() as $priceTypeDefinition) {
             if ($priceTypeDefinition->hasType($type)) {
                 return $priceTypeDefinition->createQuantityFormatter($data);
             }
         }

-        throw new QuantityFormatterNotFoundException('Quantity formatter not found');
+        throw new QuantityFormatterNotFoundException(
+            sprintf('Quantity formatter not found for type "%s"', $type)
+        );
     }

Likely invalid or redundant comment.

🧰 Tools
🪛 GitHub Check: PHP 8.3

[failure] 60-60: UndefinedClass
src/product/BillingRegistry.php:60:9: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\quantity\FractionQuantityData does not exist (see https://psalm.dev/019)

Comment on lines +40 to +57
/**
* @param string $representationClass
* @return RepresentationInterface[]
*/
public function getRepresentationsByType(string $representationClass): array
{
$representations = [];
foreach ($this->priceTypes() as $priceTypeDefinition) {
foreach ($priceTypeDefinition->documentRepresentation() as $representation) {
if ($representation instanceof $representationClass) {
$representations[] = $representation;
}
}
}

return $representations;
}
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

Add input validation and fix return type hint

The method needs validation for the representation class and updated return type documentation.

Apply these improvements:

     /**
      * @param string $representationClass
-     * @return RepresentationInterface[]
+     * @return DocumentRepresentationInterface[]
+     * @throws \InvalidArgumentException When representation class is invalid
      */
     public function getRepresentationsByType(string $representationClass): array
     {
+        if (!class_exists($representationClass)) {
+            throw new \InvalidArgumentException(
+                sprintf('Class "%s" does not exist', $representationClass)
+            );
+        }
+        if (!is_subclass_of($representationClass, DocumentRepresentationInterface::class)) {
+            throw new \InvalidArgumentException(
+                sprintf('Class "%s" must implement DocumentRepresentationInterface', $representationClass)
+            );
+        }
+
         $representations = [];
📝 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
/**
* @param string $representationClass
* @return RepresentationInterface[]
*/
public function getRepresentationsByType(string $representationClass): array
{
$representations = [];
foreach ($this->priceTypes() as $priceTypeDefinition) {
foreach ($priceTypeDefinition->documentRepresentation() as $representation) {
if ($representation instanceof $representationClass) {
$representations[] = $representation;
}
}
}
return $representations;
}
/**
* @param string $representationClass
* @return DocumentRepresentationInterface[]
* @throws \InvalidArgumentException When representation class is invalid
*/
public function getRepresentationsByType(string $representationClass): array
{
if (!class_exists($representationClass)) {
throw new \InvalidArgumentException(
sprintf('Class "%s" does not exist', $representationClass)
);
}
if (!is_subclass_of($representationClass, DocumentRepresentationInterface::class)) {
throw new \InvalidArgumentException(
sprintf('Class "%s" must implement DocumentRepresentationInterface', $representationClass)
);
}
$representations = [];
foreach ($this->priceTypes() as $priceTypeDefinition) {
foreach ($priceTypeDefinition->documentRepresentation() as $representation) {
if ($representation instanceof $representationClass) {
$representations[] = $representation;
}
}
}
return $representations;
}
🧰 Tools
🪛 GitHub Check: PHP 8.3

[failure] 42-42: UndefinedDocblockClass
src/product/BillingRegistry.php:42:16: UndefinedDocblockClass: Docblock-defined class, interface or enum named hiqdev\billing\registry\invoice\RepresentationInterface does not exist (see https://psalm.dev/200)

Comment on lines +78 to +119
/**
* @param string $type - full type like 'overuse,lb_capacity_unit'
* @param string $behaviorClassWrapper
* @return BehaviorInterface
* @throws BehaviorNotFoundException
*/
public function getBehavior(string $type, string $behaviorClassWrapper): BehaviorInterface
{
$type = $this->convertStringTypeToType($type);

foreach ($this->priceTypes() as $priceTypeDefinition) {
if ($priceTypeDefinition->hasType($type)) {
foreach ($priceTypeDefinition->withBehaviors() as $behavior) {
if ($behavior instanceof $behaviorClassWrapper) {
return $behavior;
}
}
}
}

throw new BehaviorNotFoundException('Behaviour was not found');
}

public function getBehaviors(string $behaviorClassWrapper): \Generator
{
foreach ($this->tariffTypes as $tariffType) {
foreach ($tariffType->withBehaviors() as $behavior) {
if ($behavior instanceof $behaviorClassWrapper) {
yield $behavior;
}
}
}

foreach ($this->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.

🛠️ Refactor suggestion

Refactor behavior management for better maintainability

The behavior management implementation needs several improvements:

  1. Input validation
  2. Better error messages
  3. Code deduplication
  4. Better documentation

Apply these improvements:

+    /**
+     * @param string $behaviorClassWrapper The behavior class to match
+     * @return \Generator<int, BehaviorInterface>
+     * @throws \InvalidArgumentException When behavior class is invalid
+     */
+    private function findBehaviorsInCollection(iterable $collection, string $behaviorClassWrapper): \Generator
+    {
+        foreach ($collection as $behavior) {
+            if ($behavior instanceof $behaviorClassWrapper) {
+                yield $behavior;
+            }
+        }
+    }

     /**
      * @param string $type - full type like 'overuse,lb_capacity_unit'
      * @param string $behaviorClassWrapper
      * @return BehaviorInterface
      * @throws BehaviorNotFoundException
+     * @throws \InvalidArgumentException When parameters are invalid
      */
     public function getBehavior(string $type, string $behaviorClassWrapper): BehaviorInterface
     {
+        if (empty($type)) {
+            throw new \InvalidArgumentException('Type cannot be empty');
+        }
+        if (!class_exists($behaviorClassWrapper)) {
+            throw new \InvalidArgumentException(
+                sprintf('Behavior class "%s" does not exist', $behaviorClassWrapper)
+            );
+        }
+
         $type = $this->convertStringTypeToType($type);

         foreach ($this->priceTypes() as $priceTypeDefinition) {
             if ($priceTypeDefinition->hasType($type)) {
-                foreach ($priceTypeDefinition->withBehaviors() as $behavior) {
-                    if ($behavior instanceof $behaviorClassWrapper) {
-                        return $behavior;
-                    }
+                foreach ($this->findBehaviorsInCollection($priceTypeDefinition->withBehaviors(), $behaviorClassWrapper) as $behavior) {
+                    return $behavior;
                 }
             }
         }

-        throw new BehaviorNotFoundException('Behaviour was not found');
+        throw new BehaviorNotFoundException(
+            sprintf('Behavior of class "%s" not found for type "%s"', $behaviorClassWrapper, $type)
+        );
     }

+    /**
+     * @param string $behaviorClassWrapper The behavior class to match
+     * @return \Generator<int, BehaviorInterface>
+     * @throws \InvalidArgumentException When behavior class is invalid
+     */
     public function getBehaviors(string $behaviorClassWrapper): \Generator
     {
+        if (!class_exists($behaviorClassWrapper)) {
+            throw new \InvalidArgumentException(
+                sprintf('Behavior class "%s" does not exist', $behaviorClassWrapper)
+            );
+        }
+
         foreach ($this->tariffTypes as $tariffType) {
-            foreach ($tariffType->withBehaviors() as $behavior) {
-                if ($behavior instanceof $behaviorClassWrapper) {
-                    yield $behavior;
-                }
-            }
+            yield from $this->findBehaviorsInCollection($tariffType->withBehaviors(), $behaviorClassWrapper);
         }

         foreach ($this->priceTypes() as $priceTypeDefinition) {
-            foreach ($priceTypeDefinition->withBehaviors() as $behavior) {
-                if ($behavior instanceof $behaviorClassWrapper) {
-                    yield $behavior;
-                }
-            }
+            yield from $this->findBehaviorsInCollection($priceTypeDefinition->withBehaviors(), $behaviorClassWrapper);
         }
     }
📝 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
/**
* @param string $type - full type like 'overuse,lb_capacity_unit'
* @param string $behaviorClassWrapper
* @return BehaviorInterface
* @throws BehaviorNotFoundException
*/
public function getBehavior(string $type, string $behaviorClassWrapper): BehaviorInterface
{
$type = $this->convertStringTypeToType($type);
foreach ($this->priceTypes() as $priceTypeDefinition) {
if ($priceTypeDefinition->hasType($type)) {
foreach ($priceTypeDefinition->withBehaviors() as $behavior) {
if ($behavior instanceof $behaviorClassWrapper) {
return $behavior;
}
}
}
}
throw new BehaviorNotFoundException('Behaviour was not found');
}
public function getBehaviors(string $behaviorClassWrapper): \Generator
{
foreach ($this->tariffTypes as $tariffType) {
foreach ($tariffType->withBehaviors() as $behavior) {
if ($behavior instanceof $behaviorClassWrapper) {
yield $behavior;
}
}
}
foreach ($this->priceTypes() as $priceTypeDefinition) {
foreach ($priceTypeDefinition->withBehaviors() as $behavior) {
if ($behavior instanceof $behaviorClassWrapper) {
yield $behavior;
}
}
}
}
/**
* @param string $behaviorClassWrapper The behavior class to match
* @return \Generator<int, BehaviorInterface>
* @throws \InvalidArgumentException When behavior class is invalid
*/
private function findBehaviorsInCollection(iterable $collection, string $behaviorClassWrapper): \Generator
{
foreach ($collection as $behavior) {
if ($behavior instanceof $behaviorClassWrapper) {
yield $behavior;
}
}
}
/**
* @param string $type - full type like 'overuse,lb_capacity_unit'
* @param string $behaviorClassWrapper
* @return BehaviorInterface
* @throws BehaviorNotFoundException
* @throws \InvalidArgumentException When parameters are invalid
*/
public function getBehavior(string $type, string $behaviorClassWrapper): BehaviorInterface
{
if (empty($type)) {
throw new \InvalidArgumentException('Type cannot be empty');
}
if (!class_exists($behaviorClassWrapper)) {
throw new \InvalidArgumentException(
sprintf('Behavior class "%s" does not exist', $behaviorClassWrapper)
);
}
$type = $this->convertStringTypeToType($type);
foreach ($this->priceTypes() as $priceTypeDefinition) {
if ($priceTypeDefinition->hasType($type)) {
foreach ($this->findBehaviorsInCollection($priceTypeDefinition->withBehaviors(), $behaviorClassWrapper) as $behavior) {
return $behavior;
}
}
}
throw new BehaviorNotFoundException(
sprintf('Behavior of class "%s" not found for type "%s"', $behaviorClassWrapper, $type)
);
}
/**
* @param string $behaviorClassWrapper The behavior class to match
* @return \Generator<int, BehaviorInterface>
* @throws \InvalidArgumentException When behavior class is invalid
*/
public function getBehaviors(string $behaviorClassWrapper): \Generator
{
if (!class_exists($behaviorClassWrapper)) {
throw new \InvalidArgumentException(
sprintf('Behavior class "%s" does not exist', $behaviorClassWrapper)
);
}
foreach ($this->tariffTypes as $tariffType) {
yield from $this->findBehaviorsInCollection($tariffType->withBehaviors(), $behaviorClassWrapper);
}
foreach ($this->priceTypes() as $priceTypeDefinition) {
yield from $this->findBehaviorsInCollection($priceTypeDefinition->withBehaviors(), $behaviorClassWrapper);
}
}
🧰 Tools
🪛 GitHub Check: PHP 8.3

[failure] 104-104: UndefinedClass
src/product/BillingRegistry.php:104:22: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\behavior\TariffTypeBehaviorCollection does not exist (see https://psalm.dev/019)

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 (6)
src/product/AggregateNotFoundException.php (1)

7-9: Consider adding a default error message.

While extending RuntimeException is valid, including a default message or a context parameter could aid debugging. For instance:

-class AggregateNotFoundException extends RuntimeException
+class AggregateNotFoundException extends RuntimeException 
+{
+    public function __construct(string $type = '')
+    {
+        $message = $type !== '' 
+            ? "Aggregate not found for type '$type'"
+            : "Aggregate not found";
+        parent::__construct($message);
+    }
+}
src/product/ParentNodeDefinitionInterface.php (1)

5-10: Ensure comprehensive documentation for interface methods.

Documenting intent and usage in withBehaviors() and hasBehavior() allows future maintainers to implement or mock them correctly. Consider adding PHPDoc tags describing parameters and return types in more detail.

src/product/BillingRegistry.php (1)

41-44: Adjust docblock for more specific or correct interface references.

Static analysis indicates that @return RepresentationInterface[] may not match an actual, existing interface name. Update docblock references to reflect the correct interface or class name used in production code.

🧰 Tools
🪛 GitHub Check: PHP 8.3

[failure] 43-43: UndefinedDocblockClass
src/product/BillingRegistry.php:43:16: UndefinedDocblockClass: Docblock-defined class, interface or enum named hiqdev\billing\registry\invoice\RepresentationInterface does not exist (see https://psalm.dev/200)

src/product/PriceTypeDefinition.php (3)

48-50: Consider adding input validation for $unit.

$this->unit = Unit::create($unit); will fail if $unit is invalid or empty. Add checks to ensure $unit is valid, and throw an exception if not.


72-75: Resolve TODO on formatter class checks.

Line 74 references a TODO to check the formatter’s validity. Add class existence checks and interface checks to ensure the formatter class implements QuantityFormatterInterface.


137-140: Translate non-English comments to English.

Lines 138-140 are written in another language. For better maintainability, convert them to English so that all developers can understand the code’s purpose.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 1280f4c and 2a0421b.

📒 Files selected for processing (4)
  • src/product/AggregateNotFoundException.php (1 hunks)
  • src/product/BillingRegistry.php (1 hunks)
  • src/product/ParentNodeDefinitionInterface.php (1 hunks)
  • src/product/PriceTypeDefinition.php (1 hunks)
🧰 Additional context used
🪛 GitHub Check: PHP 8.3
src/product/BillingRegistry.php

[failure] 43-43: UndefinedDocblockClass
src/product/BillingRegistry.php:43:16: UndefinedDocblockClass: Docblock-defined class, interface or enum named hiqdev\billing\registry\invoice\RepresentationInterface does not exist (see https://psalm.dev/200)


[failure] 61-61: UndefinedClass
src/product/BillingRegistry.php:61:9: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\quantity\FractionQuantityData does not exist (see https://psalm.dev/019)


[failure] 105-105: UndefinedClass
src/product/BillingRegistry.php:105:22: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\behavior\TariffTypeBehaviorCollection does not exist (see https://psalm.dev/019)


[failure] 121-121: UndefinedClass
src/product/BillingRegistry.php:121:49: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\product\Aggregate does not exist (see https://psalm.dev/019)

src/product/PriceTypeDefinition.php

[failure] 24-24: UndefinedClass
src/product/PriceTypeDefinition.php:24:13: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\quantity\formatter\QuantityFormatterDefinition does not exist (see https://psalm.dev/019)


[failure] 26-26: UndefinedClass
src/product/PriceTypeDefinition.php:26:13: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\invoice\InvoiceRepresentationCollection does not exist (see https://psalm.dev/019)

🔇 Additional comments (2)
src/product/BillingRegistry.php (2)

7-8: Confirm presence of classes from external package.

QuantityFormatterNotFoundException and FractionQuantityData point to hiqdev\billing\registry\quantity. If these classes live in an external package, ensure composer dependencies are in place. Otherwise, update the namespaces or adapt the code to existing classes.


85-100: 🛠️ Refactor suggestion

Validate behavior class.

getBehavior doesn't validate $behaviorClassWrapper before using instanceof. Consider adding existence checks and ensuring it implements the expected interface. Avoid runtime failures from invalid classes.

 public function getBehavior(string $type, string $behaviorClassWrapper): BehaviorInterface
 {
+    if (!class_exists($behaviorClassWrapper)) {
+        throw new \InvalidArgumentException(
+            "Behavior class '$behaviorClassWrapper' does not exist"
+        );
+    }
+
     $type = $this->convertStringTypeToType($type);
     ...
 }

Likely invalid or redundant comment.

Comment on lines 121 to 132
public function getAggregate(string $type): Aggregate
{
$type = $this->convertStringTypeToType($type);

foreach ($this->priceTypes() as $priceTypeDefinition) {
if ($priceTypeDefinition->hasType($type)) {
return $priceTypeDefinition->getAggregate();
}
}

throw new AggregateNotFoundException('Aggregate was not found');
}
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

⚠️ Potential issue

The Aggregate class appears to be missing from the codebase

The class Aggregate is imported from hiqdev\billing\registry\product\Aggregate namespace in both BillingRegistry.php and PriceTypeDefinition.php, but:

  • The class definition is not found in the codebase
  • It's not available in the vendor directory (which is not present in the sandbox)
  • The namespace suggests it should be in a package named hiqdev/billing-registry, but this package is not listed in composer dependencies

This could lead to runtime errors. You should either:

  • Add the missing package that contains this class to composer dependencies
  • Create the Aggregate class in the correct namespace if it's meant to be part of this package
  • Update the code to use a different class if the dependency was renamed or moved
🔗 Analysis chain

Validate existence of Aggregate class prior to usage.

Aggregate is imported from hiqdev\billing\registry\product\Aggregate, but static analysis flags it as undefined. Ensure the class is correctly included in composer or located in the repository. Otherwise, reference the correct namespace or rename as needed.


Let me search for more information about the Aggregate class.


Let me check if this is a dependency from composer by looking at composer.json.


Let me try one more search with proper escaping to find any references to this class.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Verify the Aggregate class existence 
rg "class Aggregate" -t php

Length of output: 130


Script:

#!/bin/bash
# Check for any file named Aggregate.php
fd Aggregate.php

# Check for any usage or import of Aggregate class
rg "use.*Aggregate" -t php

Length of output: 209


Script:

#!/bin/bash
# Check composer.json for the package
cat composer.json

# Also check if there are any other references to the Aggregate class
rg "hiqdev\\billing\\registry\\product\\Aggregate" -t php

Length of output: 3132


Script:

#!/bin/bash
# Search for the Aggregate class definition in vendor directory
fd -t f "Aggregate.php" vendor

# Search for references with proper escaping
rg -l "hiqdev\\\\billing\\\\registry\\\\product\\\\Aggregate"

Length of output: 248

🧰 Tools
🪛 GitHub Check: PHP 8.3

[failure] 121-121: UndefinedClass
src/product/BillingRegistry.php:121:49: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\product\Aggregate does not exist (see https://psalm.dev/019)

Comment on lines 5 to 16
use hiqdev\billing\registry\behavior\PriceTypeDefinitionBehaviourCollection;
use hiqdev\billing\registry\Domain\Model\TariffType;
use hiqdev\billing\registry\invoice\InvoiceRepresentationCollection;
use hiqdev\billing\registry\product\Aggregate;
use hiqdev\billing\registry\quantity\formatter\QuantityFormatterDefinition;
use hiqdev\billing\registry\quantity\formatter\QuantityFormatterFactory;
use hiqdev\billing\registry\quantity\FractionQuantityData;
use hiqdev\billing\registry\Domain\Model\Unit\FractionUnit;
use hiqdev\php\billing\quantity\QuantityFormatterInterface;
use hiqdev\php\billing\type\TypeInterface;
use hiqdev\php\units\Unit;
use hiqdev\php\units\UnitInterface;
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

Missing required dependency hiqdev/billing-registry

The codebase heavily uses classes from the hiqdev\billing\registry namespace, but this dependency is not listed in the composer.json requirements. This could lead to runtime errors.

  • Add "hiqdev/billing-registry": "^version" to the require section in composer.json
  • Multiple files depend on this package:
    • src/product/PriceTypeDefinition.php
    • src/product/TariffTypeDefinition.php
    • src/product/BillingRegistry.php
    • And others
🔗 Analysis chain

Confirm external dependency availability.

PriceTypeDefinitionBehaviourCollection, InvoiceRepresentationCollection, and similar references to hiqdev\billing\registry\... classes imply an external or separate package. Verify that the correct dependency is installed and configured in composer.json.


Let me try a different approach to verify the dependency.


Let me check if the registry package exists in the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check composer.json for references to hiqdev\billing\registry
rg "hiqdev\\\\billing\\\\registry" composer.json

Length of output: 46


Script:

#!/bin/bash
# Check composer.json content to see all dependencies
cat composer.json

Length of output: 2946


Script:

#!/bin/bash
# Check if the registry package exists in the codebase
fd "registry" --type d

# Also check if there are any references to the registry namespace in the codebase
rg "hiqdev\\\\billing\\\\registry" -g "*.php"

Length of output: 2579

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

♻️ Duplicate comments (4)
src/product/PriceTypeDefinition.php (4)

5-14: ⚠️ Potential issue

Add missing dependency hiqdev/billing-registry

Multiple classes are imported from the hiqdev\billing\registry namespace, but this dependency appears to be missing from composer.json. This will cause runtime errors.

Add the following to your composer.json:

{
    "require": {
+        "hiqdev/billing-registry": "^version"
    }
}

19-29: 🛠️ Refactor suggestion

Initialize class properties

Properties should be initialized to prevent undefined property access.

Apply this diff:

-    private Unit $unit;
+    private Unit $unit;

-    private string $description;
+    private string $description = '';

-    private QuantityFormatterDefinition $quantityFormatterDefinition;
+    private QuantityFormatterDefinition $quantityFormatterDefinition;

-    private Aggregate $aggregate;
+    private ?Aggregate $aggregate = null;
🧰 Tools
🪛 GitHub Check: PHP 8.3

[failure] 19-19: UndefinedClass
src/product/PriceTypeDefinition.php:19:13: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\Domain\Model\Unit\Unit does not exist (see https://psalm.dev/019)


[failure] 23-23: UndefinedClass
src/product/PriceTypeDefinition.php:23:13: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\quantity\formatter\QuantityFormatterDefinition does not exist (see https://psalm.dev/019)


71-77: ⚠️ Potential issue

Implement class existence validation

The TODO comment indicates missing validation for the formatter class.

Add validation to ensure the formatter class exists and implements the correct interface.


89-93: ⚠️ Potential issue

Add validation in end() method

The method should validate that all required properties are set before returning.

Add validation for required properties:

     public function end(): PriceTypeDefinitionCollection
     {
-        // Validate the PriceType and lock its state
+        if (!isset($this->unit)) {
+            throw new \RuntimeException('Unit is required but not set');
+        }
+        if (empty($this->description)) {
+            throw new \RuntimeException('Description is required but not set');
+        }
+        if (!isset($this->quantityFormatterDefinition)) {
+            throw new \RuntimeException('Quantity formatter is required but not set');
+        }
         return $this->parent;
     }
🧹 Nitpick comments (1)
src/product/PriceTypeDefinition.php (1)

31-40: Add constructor documentation and clarify initialization

The constructor lacks PHPDoc documentation, and the purpose of the init() method is unclear.

Add constructor documentation and clarify the hook's purpose:

+    /**
+     * @param PriceTypeDefinitionCollection $parent Parent collection managing this definition
+     * @param TypeInterface $type Price type being defined
+     * @param TariffType $tariffType Associated tariff type
+     */
     public function __construct(
         private readonly PriceTypeDefinitionCollection $parent,
         private readonly TypeInterface $type,
         TariffType $tariffType,
     ) {
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 2a0421b and 78380f0.

📒 Files selected for processing (1)
  • src/product/PriceTypeDefinition.php (1 hunks)
🧰 Additional context used
🪛 GitHub Check: PHP 8.3
src/product/PriceTypeDefinition.php

[failure] 19-19: UndefinedClass
src/product/PriceTypeDefinition.php:19:13: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\Domain\Model\Unit\Unit does not exist (see https://psalm.dev/019)


[failure] 23-23: UndefinedClass
src/product/PriceTypeDefinition.php:23:13: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\quantity\formatter\QuantityFormatterDefinition does not exist (see https://psalm.dev/019)

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

100-103: Remove unused method

The measuredWith method is not used anywhere in the codebase and its implementation is a no-op.

Remove this unused method.


137-139: Translate documentation to English

Documentation should be in English for better maintainability and collaboration.

Replace the documentation with:

-     * це параметер визначає агрегатну функцію яка застосовується для щоденно записаних ресурсів щоб визнизначти
-     * місячне споживання за яке потрібно пробілити клієнта
+     * This parameter defines the aggregate function that is applied to daily recorded resources
+     * to determine the monthly consumption for which the client needs to be billed

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)
src/product/TariffTypeDefinition.php (4)

17-21: Consider input validation in the constructor.
Currently, there is no validation of the $tariffType object. If the constructor is a critical entry point for the system, you may wish to sanity-check or enforce constraints (e.g., ensuring $tariffType is fully formed).


35-39: Add validation or usage for $suggesterClass.
Currently, the setPricesSuggester() method simply comments about validation but doesn’t apply it. Consider implementing validation or storing $suggesterClass if it’s intended for later usage. Otherwise, you may remove the method if it serves no purpose.


51-60: Improve readability & reduce complexity in hasBehavior().
The loop checks each $behavior against instanceof $behaviorClassName. You may refactor using array helpers or short-circuit logic to make it more concise. For instance:

return array_reduce(
    iterator_to_array($this->behaviorCollection),
    fn($carry, $behavior) => $carry || $behavior instanceof $behaviorClassName,
    false
);

62-66: Clarify the end() method’s concerns.
If the goal is to "lock its state," consider throwing exceptions or performing final validations that ensure TariffType is correct before returning $this. Listing which validations occur may also help future maintenance.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 78380f0 and da2c506.

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

28-33: Potential null property usage.
The property $product is assigned in ofProduct() but is never checked or validated for null outside this method. If other methods rely on $product, they might operate on an uninitialized property.

Consider verifying if $product is always set before usage.


9-10: Class implements an unshown interface.
The file mentions ParentNodeDefinitionInterface, but it is not shown. Please ensure that the interface is defined and accessible.

Use the following script to confirm if the interface exists and is properly declared:

✅ Verification successful

Interface verification successful
The ParentNodeDefinitionInterface is properly defined in src/product/ParentNodeDefinitionInterface.php and is accessible within the same namespace.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Search for the declaration of ParentNodeDefinitionInterface
ast-grep --pattern $'interface ParentNodeDefinitionInterface {\n  $$$\n}'

Length of output: 72


Script:

#!/bin/bash
# Search for any mentions of ParentNodeDefinitionInterface
rg "ParentNodeDefinitionInterface" -A 2

# Get the namespace context and use statements from the implementing class
cat src/product/TariffTypeDefinition.php | head -n 10

Length of output: 1057

Comment on lines 5 to 13
use hiqdev\billing\registry\behavior\PriceTypeDefinitionBehaviourCollection;
use hiqdev\billing\registry\Domain\Model\TariffType;
use hiqdev\billing\registry\Domain\Model\Unit\Unit;
use hiqdev\billing\registry\invoice\InvoiceRepresentationCollection;
use hiqdev\billing\registry\product\Aggregate;
use hiqdev\billing\registry\quantity\formatter\QuantityFormatterDefinition;
use hiqdev\billing\registry\quantity\formatter\QuantityFormatterFactory;
use hiqdev\billing\registry\quantity\FractionQuantityData;
use hiqdev\billing\registry\Domain\Model\Unit\FractionUnit;
Copy link
Member

Choose a reason for hiding this comment

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

The same thing here.
We'll neet a call to figure it out.

…onBehaviourCollection and TariffTypeBehaviorCollection classes
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

🔭 Outside diff range comments (1)
src/product/ParentNodeDefinitionInterface.php (1)

5-12: ⚠️ Potential issue

Fix interface definition structure and inconsistent return types

There are structural issues in this interface definition:

  1. The interface appears to have duplicate method declarations with inconsistent return types:

    • Line 7: public function withBehaviors(): BehaviorCollection;
    • Line 9: public function withBehaviors(): BehaviorCollectionInterface;
  2. The code structure is broken - there's a closing brace at line 8, followed by method declarations outside the interface block.

Fix the interface structure by applying this diff:

interface ParentNodeDefinitionInterface
{
-    public function withBehaviors(): BehaviorCollection;
-}
    public function withBehaviors(): BehaviorCollectionInterface;

    public function hasBehavior(string $behaviorClassName): bool;
}

Also, consider adding the necessary import for BehaviorCollectionInterface:

use hiqdev\php\billing\product\behavior\BehaviorCollectionInterface;
🧹 Nitpick comments (9)
src/product/behavior/BehaviorCollectionInterface.php (1)

5-13: Interface looks good, consider adding behavioral check method

The interface is well-defined with appropriate methods for a collection pattern that can be iterated over.

Consider adding a method to check if a specific behavior exists in the collection, which would complement the attach() method:

interface BehaviorCollectionInterface extends \IteratorAggregate
{
    /**
     * @return BehaviorInterface[]
     */
    public function getIterator(): \Traversable;

    public function attach(BehaviorInterface $behavior): self;
+    
+    /**
+     * Checks if a behavior of the specified class exists in the collection
+     */
+    public function hasBehavior(string $behaviorClassName): bool;
}

This would align with the hasBehavior() method in the ParentNodeDefinitionInterface and provide a complete API for working with behaviors.

src/product/behavior/BehaviorTariffTypeCollection.php (1)

8-19: Good implementation of builder pattern

The class effectively implements a fluent builder pattern, storing a reference to the parent object and providing an end() method to return to it after working with behaviors.

Consider adding a PHPDoc comment to the end() method to clarify its purpose in the builder pattern:

+    /**
+     * Returns to the parent TariffTypeDefinition after configuring behaviors
+     * Allows for fluent method chaining
+     */
    public function end(): TariffTypeDefinition
    {
        return $this->parent;
    }
src/product/PriceTypeDefinition.php (3)

7-7: Check external dependency usage
Importing hiqdev\billing\registry\product\Aggregate may introduce an external dependency from a billing-registry package. Verify if you can replace or wrap it using internal interfaces to avoid coupling with external packages.


29-29: New aggregate property added
Declaring $aggregate in the class helps handle aggregation logic. Ensure you handle null or uninitialized states if an aggregate is not always defined.


131-139: Aggregation methods introduced
Adding aggregation(Aggregate $aggregate) and getAggregate() promotes flexible resource usage for monthly or daily aggregates. Confirm the Aggregate class is valid in your codebase or consider an alternative in-house type if external dependencies are undesired.

Do you want me to open an issue to track the possible in-house implementation of Aggregate?

src/product/BillingRegistry.php (1)

58-64: Updated createQuantityFormatter signature
Changing the function to accept a string type and resolving it via Type::anyId($type) simplifies the API. Ensure to document potential invalid string values, and consider throwing exceptions when the type cannot be resolved.

src/product/TariffTypeDefinition.php (3)

11-15: Private properties for product, prices, and behaviors
Defining separate properties contributes to a clear separation of concerns. Validate these properties upon assignment to catch inconsistent states.


28-33: ofProduct(...) method
Linking Product with the tariff type definition is logical. Consider adding basic validation (e.g., ensuring the product is compatible with this tariff type).


46-49: withBehaviors() method
Allowing retrieval of behavior collections fosters extension. Validate that behaviors remain consistent with the underlying TariffType.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 881679f and 1acbb7b.

📒 Files selected for processing (60)
  • src/product/BillingRegistry.php (1 hunks)
  • src/product/PriceTypeDefinition.php (1 hunks)
  • src/product/BillingRegistry.php (1 hunks)
  • src/product/PriceTypeDefinition.php (1 hunks)
  • src/product/PriceTypeDefinition.php (2 hunks)
  • src/product/PriceTypeDefinition.php (2 hunks)
  • src/product/BillingRegistry.php (1 hunks)
  • src/product/PriceTypeDefinition.php (3 hunks)
  • src/product/PriceTypeDefinition.php (3 hunks)
  • src/product/BillingRegistry.php (2 hunks)
  • src/product/PriceTypeDefinition.php (2 hunks)
  • src/product/PriceTypeDefinition.php (3 hunks)
  • src/product/PriceTypeDefinition.php (1 hunks)
  • src/product/PriceTypeDefinition.php (3 hunks)
  • src/product/PriceTypeDefinition.php (1 hunks)
  • src/product/PriceTypeDefinition.php (1 hunks)
  • src/product/PriceTypeDefinition.php (3 hunks)
  • src/product/BillingRegistry.php (2 hunks)
  • src/product/BillingRegistry.php (2 hunks)
  • src/product/PriceTypeDefinition.php (4 hunks)
  • src/product/BillingRegistry.php (1 hunks)
  • src/product/PriceTypeDefinition.php (1 hunks)
  • src/product/BillingRegistry.php (1 hunks)
  • src/product/PriceTypeDefinition.php (4 hunks)
  • src/product/BillingRegistry.php (4 hunks)
  • src/product/BillingRegistry.php (2 hunks)
  • src/product/BillingRegistry.php (2 hunks)
  • src/product/PriceTypeDefinition.php (4 hunks)
  • src/product/TariffTypeDefinition.php (1 hunks)
  • src/product/BillingRegistry.php (1 hunks)
  • src/product/ParentNodeDefinitionInterface.php (1 hunks)
  • src/product/PriceTypeDefinition.php (1 hunks)
  • src/product/TariffTypeDefinition.php (1 hunks)
  • src/product/BillingRegistry.php (1 hunks)
  • src/product/PriceTypeDefinition.php (1 hunks)
  • src/product/BillingRegistry.php (1 hunks)
  • src/product/BillingRegistry.php (2 hunks)
  • src/product/ParentNodeDefinitionInterface.php (1 hunks)
  • src/product/PriceTypeDefinition.php (1 hunks)
  • src/product/PriceTypeDefinition.php (5 hunks)
  • src/product/TariffTypeDefinition.php (1 hunks)
  • src/product/Domain/Model/TariffTypeInterface.php (1 hunks)
  • src/product/PriceTypeDefinition.php (2 hunks)
  • src/product/TariffTypeDefinition.php (2 hunks)
  • src/product/Domain/Model/Unit/FractionUnitInterface.php (1 hunks)
  • src/product/Domain/Model/Unit/UnitInterface.php (1 hunks)
  • src/product/PriceTypeDefinition.php (4 hunks)
  • src/product/BillingRegistry.php (1 hunks)
  • src/product/Domain/Model/TariffTypeInterface.php (1 hunks)
  • src/product/Domain/Model/Unit/FractionUnitInterface.php (1 hunks)
  • src/product/Domain/Model/Unit/UnitInterface.php (1 hunks)
  • src/product/ParentNodeDefinitionInterface.php (1 hunks)
  • src/product/PriceTypeDefinition.php (4 hunks)
  • src/product/TariffTypeDefinition.php (3 hunks)
  • src/product/behavior/BehaviorCollection.php (1 hunks)
  • src/product/behavior/BehaviorCollectionInterface.php (1 hunks)
  • src/product/behavior/BehaviorInterface.php (1 hunks)
  • src/product/behavior/BehaviorNotFoundException.php (1 hunks)
  • src/product/behavior/BehaviorTariffTypeCollection.php (1 hunks)
  • src/product/behavior/BehaviourPriceTypeDefinitionCollection.php (1 hunks)
✅ Files skipped from review due to trivial changes (8)
  • src/product/Domain/Model/TariffTypeInterface.php
  • src/product/Domain/Model/Unit/FractionUnitInterface.php
  • src/product/Domain/Model/Unit/UnitInterface.php
  • src/product/behavior/BehaviorInterface.php
  • src/product/PriceTypeDefinition.php
  • src/product/BillingRegistry.php
  • src/product/PriceTypeDefinition.php
  • src/product/behavior/BehaviorNotFoundException.php
🚧 Files skipped from review as they are similar to previous changes (30)
  • src/product/Domain/Model/Unit/FractionUnitInterface.php
  • src/product/TariffTypeDefinition.php
  • src/product/Domain/Model/TariffTypeInterface.php
  • src/product/BillingRegistry.php
  • src/product/ParentNodeDefinitionInterface.php
  • src/product/ParentNodeDefinitionInterface.php
  • src/product/BillingRegistry.php
  • src/product/TariffTypeDefinition.php
  • src/product/BillingRegistry.php
  • src/product/PriceTypeDefinition.php
  • src/product/BillingRegistry.php
  • src/product/PriceTypeDefinition.php
  • src/product/PriceTypeDefinition.php
  • src/product/Domain/Model/Unit/UnitInterface.php
  • src/product/PriceTypeDefinition.php
  • src/product/BillingRegistry.php
  • src/product/PriceTypeDefinition.php
  • src/product/PriceTypeDefinition.php
  • src/product/PriceTypeDefinition.php
  • src/product/PriceTypeDefinition.php
  • src/product/PriceTypeDefinition.php
  • src/product/BillingRegistry.php
  • src/product/BillingRegistry.php
  • src/product/BillingRegistry.php
  • src/product/TariffTypeDefinition.php
  • src/product/PriceTypeDefinition.php
  • src/product/PriceTypeDefinition.php
  • src/product/BillingRegistry.php
  • src/product/PriceTypeDefinition.php
  • src/product/PriceTypeDefinition.php
🧰 Additional context used
🧠 Learnings (1)
src/product/BillingRegistry.php (1)
Learnt from: SilverFire
PR: hiqdev/php-billing#93
File: src/product/BillingRegistry.php:5-5
Timestamp: 2025-01-13T12:20:09.570Z
Learning: The `hiqdev/php-billing` repository should not depend on `advancedhosters/billing-registry`. Use internal interfaces and implementations instead.
🔇 Additional comments (9)
src/product/behavior/BehaviorCollection.php (2)

3-3: Namespace alignment looks good
Moving the namespace to hiqdev\php\billing\product\behavior is consistent with the reorganization of product behavior classes.


7-7: Adoption of BehaviorCollectionInterface
Switching from implementing \IteratorAggregate to BehaviorCollectionInterface standardizes behavior management. This change appears well-aligned with the new interface-based design.

src/product/PriceTypeDefinition.php (1)

60-63: Getter for description
Providing a dedicated getter method improves encapsulation, allowing consistent read access to $description.

src/product/BillingRegistry.php (1)

8-8: Added Type import
Importing Type aligns with the new approach of converting string identifiers via Type::anyId(). This clarifies type usage.

src/product/TariffTypeDefinition.php (5)

1-3: New file with strict typing and namespace
Introducing strict types and setting the namespace under hiqdev\php\billing\product is consistent with modern PHP practices.


23-26: tariffType() method
Exposing the underlying TariffType neatly surfaces the associated data. Confirm that calling code doesn’t rely on internal states that might change in future refactors.


41-44: withPrices() method
Returning PriceTypeDefinitionCollection is consistent with the aggregator pattern. Confirm that future modifications to $this->prices remain thread-safe if concurrency is a concern.


51-55: end() method for finalization
Exposing a chainable end() is aligned with a fluent builder approach. Remember to include any final validation or locking logic, if needed, before returning $this.


17-21:

✅ Verification successful

Constructor sets up price and behavior collections
Initializing $this->prices and $this->behaviorCollection inside the constructor is good practice for guaranteeing minimal object completeness. However, ensure that TariffTypeBehaviorCollection aligns with your custom or internal classes rather than external.


🏁 Script executed:

#!/bin/bash
# Search for the TariffTypeBehaviorCollection class to verify it is declared in an internal namespace
rg -A 5 "class TariffTypeBehaviorCollection"

Length of output: 46


Below is a revised review comment. Note that the original search was performed using the wrong class name. The code instantiates a class called BehaviorTariffTypeCollection (not TariffTypeBehaviorCollection), so please verify its declaration to ensure it’s part of your internal implementation. In the meantime, run the script below to search for its definition and confirm it’s declared in an internal namespace:

Action Required: Verify Internal Declaration

  • The constructor correctly initializes both $this->prices and $this->behaviorCollection, ensuring object completeness.
  • However, since the code instantiates BehaviorTariffTypeCollection, please confirm that this class is defined internally (and not inadvertently pulling from an external package).

Once you’ve confirmed the implementation details with the script output, update this review comment accordingly.


🏁 Script executed:

#!/bin/bash
# Verify that BehaviorTariffTypeCollection is defined in an internal namespace.
rg -A 5 "class BehaviorTariffTypeCollection"

Length of output: 722


Constructor initializes internal collections correctly

The constructor in src/product/TariffTypeDefinition.php correctly sets up both the price and behavior collections. We confirmed that the BehaviorTariffTypeCollection—instantiated in the constructor—is defined internally in the file src/product/behavior/BehaviorTariffTypeCollection.php (extending BehaviorCollection), ensuring the implementation aligns with your internal design.

No changes are required.

Comment on lines +8 to +19
class BehaviourPriceTypeDefinitionCollection extends BehaviorCollection
{
public function __construct(private readonly PriceTypeDefinition $parent, TariffTypeInterface $tariffType)
{
parent::__construct($tariffType);
}

public function end(): PriceTypeDefinition
{
return $this->parent;
}
}
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

Fix naming inconsistency in class name

The class implementation is good, but there's an inconsistency in spelling - this class uses "Behaviour" (British English) while other related classes use "Behavior" (American English).

Rename the class to maintain naming consistency throughout the codebase:

-class BehaviourPriceTypeDefinitionCollection extends BehaviorCollection
+class BehaviorPriceTypeDefinitionCollection extends BehaviorCollection
{
    public function __construct(private readonly PriceTypeDefinition $parent, TariffTypeInterface $tariffType)
    {
        parent::__construct($tariffType);
    }

    public function end(): PriceTypeDefinition
    {
        return $this->parent;
    }
}

Also, consider adding a PHPDoc comment to the end() method as suggested for the BehaviorTariffTypeCollection class.


🏁 Script executed:

#!/bin/bash
# Verify the consistency of spelling "Behavior" vs "Behaviour" in the codebase

echo "Checking for 'Behavior' occurrences:"
rg -i "\bBehavior\b" --type php | wc -l

echo "Checking for 'Behaviour' occurrences:"
rg -i "\bBehaviour\b" --type php | wc -l

echo "Files containing 'Behaviour':"
rg -i "\bBehaviour\b" --type php -l

Length of output: 414


Rename class for naming consistency and add method PHPDoc

After verifying the codebase, only one occurrence of "Behaviour" was found (in src/product/BillingRegistry.php) compared to 33 occurrences of "Behavior". To maintain uniformity, please:

  • Rename the class: Change BehaviourPriceTypeDefinitionCollection to BehaviorPriceTypeDefinitionCollection in src/product/behavior/BehaviourPriceTypeDefinitionCollection.php.
  • Enhance documentation: Add a PHPDoc comment for the end() method similar to the practice in the BehaviorTariffTypeCollection class.
-class BehaviourPriceTypeDefinitionCollection extends BehaviorCollection
+class BehaviorPriceTypeDefinitionCollection extends BehaviorCollection
{
    public function __construct(private readonly PriceTypeDefinition $parent, TariffTypeInterface $tariffType)
    {
        parent::__construct($tariffType);
    }

    public function end(): PriceTypeDefinition
    {
+        /**
+         * Returns the associated PriceTypeDefinition instance.
+         *
+         * @return PriceTypeDefinition
+         */
        return $this->parent;
    }
}
📝 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
class BehaviourPriceTypeDefinitionCollection extends BehaviorCollection
{
public function __construct(private readonly PriceTypeDefinition $parent, TariffTypeInterface $tariffType)
{
parent::__construct($tariffType);
}
public function end(): PriceTypeDefinition
{
return $this->parent;
}
}
class BehaviorPriceTypeDefinitionCollection extends BehaviorCollection
{
public function __construct(private readonly PriceTypeDefinition $parent, TariffTypeInterface $tariffType)
{
parent::__construct($tariffType);
}
public function end(): PriceTypeDefinition
{
/**
* Returns the associated PriceTypeDefinition instance.
*
* @return PriceTypeDefinition
*/
return $this->parent;
}
}

Comment on lines 35 to 39
public function setPricesSuggester(string $suggesterClass): self
{
// Validate or store the suggester class
return $this;
}
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

setPricesSuggester(...) method
Storing or validating a suggester class fosters extensibility. If required, ensure the class exists and implements a known interface.

Comment on lines 5 to 7
use hiqdev\php\billing\product\behavior\BehaviorTariffTypeCollection;
use hiqdev\billing\registry\product\Product;
use hiqdev\php\billing\product\Domain\Model\TariffTypeInterface;
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

Avoid billing-registry package references
Lines 5-7 import classes from hiqdev\billing\registry, which may come from external packages. According to your learnings, this repository should not rely on that. Refactor to rely on internal interfaces or rename classes to maintain independence.

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

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

58-70: 🛠️ Refactor suggestion

Enhance error message in createQuantityFormatter.

The error message lacks context about which specific types were not found. This makes troubleshooting more difficult.

-        throw new QuantityFormatterNotFoundException('Quantity formatter not found');
+        throw new QuantityFormatterNotFoundException(
+            sprintf("Quantity formatter not found for gType '%s' and priceType '%s'", $gType, $priceType)
+        );

Additionally, ensure the parameter types are defined in the project's namespace, not from external dependencies.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 1acbb7b and 0c060ab.

📒 Files selected for processing (43)
  • src/product/BillingRegistry.php (1 hunks)
  • src/product/PriceTypeDefinition.php (1 hunks)
  • src/product/BillingRegistry.php (1 hunks)
  • src/product/PriceTypeDefinition.php (1 hunks)
  • src/product/PriceTypeDefinition.php (2 hunks)
  • src/product/PriceTypeDefinition.php (2 hunks)
  • src/product/BillingRegistry.php (1 hunks)
  • src/product/PriceTypeDefinition.php (3 hunks)
  • src/product/PriceTypeDefinition.php (3 hunks)
  • src/product/BillingRegistry.php (2 hunks)
  • src/product/PriceTypeDefinition.php (2 hunks)
  • src/product/PriceTypeDefinition.php (3 hunks)
  • src/product/PriceTypeDefinition.php (1 hunks)
  • src/product/PriceTypeDefinition.php (3 hunks)
  • src/product/PriceTypeDefinition.php (1 hunks)
  • src/product/PriceTypeDefinition.php (1 hunks)
  • src/product/PriceTypeDefinition.php (3 hunks)
  • src/product/BillingRegistry.php (2 hunks)
  • src/product/BillingRegistry.php (2 hunks)
  • src/product/PriceTypeDefinition.php (4 hunks)
  • src/product/BillingRegistry.php (1 hunks)
  • src/product/PriceTypeDefinition.php (1 hunks)
  • src/product/BillingRegistry.php (1 hunks)
  • src/product/PriceTypeDefinition.php (4 hunks)
  • src/product/BillingRegistry.php (4 hunks)
  • src/product/BillingRegistry.php (2 hunks)
  • src/product/BillingRegistry.php (2 hunks)
  • src/product/PriceTypeDefinition.php (4 hunks)
  • src/product/BillingRegistry.php (1 hunks)
  • src/product/PriceTypeDefinition.php (1 hunks)
  • src/product/BillingRegistry.php (1 hunks)
  • src/product/PriceTypeDefinition.php (1 hunks)
  • src/product/BillingRegistry.php (1 hunks)
  • src/product/BillingRegistry.php (2 hunks)
  • src/product/PriceTypeDefinition.php (1 hunks)
  • src/product/PriceTypeDefinition.php (5 hunks)
  • src/product/PriceTypeDefinition.php (2 hunks)
  • src/product/PriceTypeDefinition.php (4 hunks)
  • src/product/BillingRegistry.php (1 hunks)
  • src/product/PriceTypeDefinition.php (4 hunks)
  • src/product/AggregateInterface.php (1 hunks)
  • src/product/BillingRegistry.php (1 hunks)
  • src/product/PriceTypeDefinition.php (2 hunks)
✅ Files skipped from review due to trivial changes (2)
  • src/product/PriceTypeDefinition.php
  • src/product/PriceTypeDefinition.php
🚧 Files skipped from review as they are similar to previous changes (21)
  • src/product/BillingRegistry.php
  • src/product/PriceTypeDefinition.php
  • src/product/BillingRegistry.php
  • src/product/BillingRegistry.php
  • src/product/PriceTypeDefinition.php
  • src/product/PriceTypeDefinition.php
  • src/product/BillingRegistry.php
  • src/product/PriceTypeDefinition.php
  • src/product/BillingRegistry.php
  • src/product/BillingRegistry.php
  • src/product/PriceTypeDefinition.php
  • src/product/PriceTypeDefinition.php
  • src/product/BillingRegistry.php
  • src/product/PriceTypeDefinition.php
  • src/product/PriceTypeDefinition.php
  • src/product/PriceTypeDefinition.php
  • src/product/PriceTypeDefinition.php
  • src/product/PriceTypeDefinition.php
  • src/product/BillingRegistry.php
  • src/product/PriceTypeDefinition.php
  • src/product/PriceTypeDefinition.php
🧰 Additional context used
🧠 Learnings (1)
src/product/BillingRegistry.php (1)
Learnt from: SilverFire
PR: hiqdev/php-billing#93
File: src/product/BillingRegistry.php:5-5
Timestamp: 2025-01-13T12:20:09.570Z
Learning: The `hiqdev/php-billing` repository should not depend on `advancedhosters/billing-registry`. Use internal interfaces and implementations instead.
🔇 Additional comments (3)
src/product/AggregateInterface.php (1)

5-8: Well-designed interface with clear responsibility.

The new AggregateInterface follows good design principles by having a focused single method with a clear purpose. The interface is appropriately named and the method signature is concise with proper type hinting.

src/product/PriceTypeDefinition.php (2)

53-53: Update return statement for consistency.

This change is correct, but ensure the method signature and return type also use internal interfaces rather than external dependencies.


136-138: Translate documentation to English.

The PHPDoc comment is written in a non-English language. For better maintainability and collaboration, documentation should be in English.

-     * це параметер визначає агрегатну функцію яка застосовується для щоденно записаних ресурсів щоб визнизначти
-     * місячне споживання за яке потрібно пробілити клієнта
+     * This parameter defines the aggregate function that is applied to daily recorded resources
+     * to determine the monthly consumption for which the client needs to be billed

Comment on lines 6 to 9
use hiqdev\billing\registry\quantity\formatter\QuantityFormatterNotFoundException;
use hiqdev\billing\registry\quantity\FractionQuantityData;
use hiqdev\php\billing\product\behavior\BehaviorInterface;
use hiqdev\php\billing\product\behavior\BehaviorNotFoundException;
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

Remove dependencies on external packages.

These imports reference classes from the hiqdev\billing\registry namespace, which goes against the project requirement that hiqdev/php-billing should not depend on advancedhosters/billing-registry. This was previously noted in the learnings.

Replace these external dependencies with internal interfaces and implementations:

-use hiqdev\billing\registry\product\GType;
-use hiqdev\billing\registry\product\PriceType;
-use hiqdev\billing\registry\quantity\formatter\QuantityFormatterNotFoundException;
-use hiqdev\billing\registry\quantity\FractionQuantityData;
+use hiqdev\php\billing\product\GType;
+use hiqdev\php\billing\product\PriceType;
+use hiqdev\php\billing\product\formatter\QuantityFormatterNotFoundException;
+use hiqdev\php\billing\product\quantity\FractionQuantityData;

private UnitInterface $unit;

private string $description;

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

Update instantiation to use internal implementation.

This creates an instance of an external package class.

-        $this->invoiceCollection = new InvoiceRepresentationCollection($this);
+        $this->invoiceCollection = new DocumentRepresentationCollection($this);

Ensure a compatible implementation exists within this project's namespace.

use hiqdev\php\billing\type\TypeInterface;

class PriceTypeDefinition implements ParentNodeDefinitionInterface
{
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

Update property type to use internal interface.

This property uses a class from hiqdev\billing\registry which goes against the project requirement not to depend on external registry packages.

-    private InvoiceRepresentationCollection $invoiceCollection;
+    private DocumentRepresentationCollection $invoiceCollection;

Ensure that DocumentRepresentationCollection is defined within this project's namespace.

📝 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
{
<?php
namespace App\Product;
class PriceTypeDefinition
{
// Other properties...
- private InvoiceRepresentationCollection $invoiceCollection;
+ private DocumentRepresentationCollection $invoiceCollection;
// Rest of the class code...
}

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: 1

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

5-7: 🛠️ Refactor suggestion

Remove or replace references to external registry namespaces.
These imports may conflict with the requirement to avoid advancedhosters/billing-registry. Replace them with internal interfaces or implementations within hiqdev\php\billing.

-use hiqdev\billing\registry\invoice\RepresentationInterface;
 ...
-use hiqdev\billing\registry\quantity\FractionQuantityData;
+use hiqdev\php\billing\product\YourOwnRepresentationInterface;
 ...
+use hiqdev\php\billing\product\quantity\YourOwnFractionQuantityData;
🧹 Nitpick comments (3)
src/product/quantity/QuantityFormatterNotFoundException.php (1)

7-9: Consider providing additional context in the exception message.
Currently, the exception class is very minimal. If it’s used in multiple contexts, consider including details (such as the missing formatter type) to aid debugging.

 class QuantityFormatterNotFoundException extends RuntimeException
 {
+    // Example: Additional context or typed constructor for debugging
 }
src/product/BillingRegistry.php (2)

42-45: Refine return type for getRepresentationsByType.
The docblock indicates @return RepresentationInterface[], but it references an external interface. Confirm the correct interface name and replace if needed to maintain internal consistency.


80-101: Add input validation for $type and $behaviorClassWrapper.
To avoid subtle issues, validate $type is not empty and $behaviorClassWrapper references a known class implementing BehaviorInterface before iterating price types.

 public function getBehavior(string $type, string $behaviorClassWrapper): BehaviorInterface
 {
+    if (empty($type)) {
+        throw new \InvalidArgumentException('Type cannot be empty');
+    }
+    if (!class_exists($behaviorClassWrapper) || 
+        !is_subclass_of($behaviorClassWrapper, BehaviorInterface::class)) {
+        throw new \InvalidArgumentException(
+            "Invalid behavior class: $behaviorClassWrapper"
+        );
+    }
     $type = $this->convertStringTypeToType($type);
     ...
 }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 93686bb and 77b9efe.

📒 Files selected for processing (4)
  • src/product/BillingRegistry.php (1 hunks)
  • src/product/PriceTypeDefinition.php (1 hunks)
  • src/product/quantity/QuantityFormatterInterface.php (1 hunks)
  • src/product/quantity/QuantityFormatterNotFoundException.php (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
src/product/BillingRegistry.php (1)
Learnt from: SilverFire
PR: hiqdev/php-billing#93
File: src/product/BillingRegistry.php:5-5
Timestamp: 2025-01-13T12:20:09.570Z
Learning: The `hiqdev/php-billing` repository should not depend on `advancedhosters/billing-registry`. Use internal interfaces and implementations instead.
🔇 Additional comments (10)
src/product/quantity/QuantityFormatterInterface.php (1)

5-28: Interface design looks consistent.
The methods and docblocks provide clear guidance. Nice job defining the contract for textual, numeric, and client-friendly formats.

src/product/BillingRegistry.php (1)

122-133:

❓ Verification inconclusive

Verify AggregateInterface usage and add missing import.
Ensure AggregateInterface and AggregateNotFoundException are correctly declared or imported from the same package. Otherwise, this will lead to runtime errors.


🏁 Script executed:

#!/bin/bash
# Description: Check for AggregateInterface and AggregateNotFoundException definitions
fd "AggregateInterface.php" -t f
fd "AggregateNotFoundException.php" -t f
rg -A 5 "class AggregateInterface" 
rg -A 5 "class AggregateNotFoundException"

Length of output: 424


Aggregate Interface and Exception Import Verification

Both AggregateInterface (defined in src/product/AggregateInterface.php) and AggregateNotFoundException (defined in src/product/AggregateNotFoundException.php) are present in the repository. Please ensure that src/product/BillingRegistry.php includes the proper import statements for these classes if they’re not already in the same namespace. For example, if they reside in a different namespace, add:

  • use YourNamespace\AggregateInterface;
  • use YourNamespace\AggregateNotFoundException;

This will prevent any runtime errors due to unrecognized types.

src/product/PriceTypeDefinition.php (8)

16-16: Add a class-level docblock to explain responsibility.

The class currently lacks a high-level PHPDoc summary describing its purpose and usage. This was previously suggested, so marking it as a duplicate of earlier feedback.

+/**
+ * Defines and configures a price type within the billing system.
+ * Responsible for holding unit, description, formatter, and behavior definitions.
+ */
 class PriceTypeDefinition implements ParentNodeDefinitionInterface
 {

5-9: Ensure the required dependency for hiqdev/billing-registry is declared.

Classes from hiqdev\billing\registry are referenced here. Verify that "hiqdev/billing-registry" is added to composer.json under "require" to avoid runtime errors. This repeats a prior suggestion.


18-28: Add property-level documentation and consider default initializations.

The private properties are declared without any PHPDoc. Provide short doc comments for each and consider initializing them with default values if applicable. Similar feedback has been provided previously.

     /**
+     * @var UnitInterface The billing unit used to measure consumption.
+     */
     private UnitInterface $unit;

     /**
+     * @var string A short, human-readable description of the price type.
+     */
     private string $description;

     /**
+     * @var QuantityFormatterDefinition Holds the configuration for quantity formatting.
+     */
     private QuantityFormatterDefinition $quantityFormatterDefinition;

     /**
+     * @var InvoiceRepresentationCollection Collects representation data for invoices.
+     */
     private InvoiceRepresentationCollection $invoiceCollection;

46-51: Include input validation for the unit.

If $unit can be invalid or null, it may cause unexpected errors later. Adding checks or throwing an exception if $unit is invalid was suggested previously.

 public function unit(UnitInterface $unit): self
 {
+    if (!$unit) {
+        throw new \InvalidArgumentException('Unit must not be null.');
+    }
     $this->unit = $unit;

     return $this;
 }

70-73: Complete the TODO by validating the formatter class.

This has been flagged in a previous review. Implement class checks before instantiating QuantityFormatterDefinition.

-        // TODO: check if formatterClass exists
+        if (!class_exists($formatterClass)) {
+            throw new \InvalidArgumentException(sprintf(
+                'Formatter class "%s" does not exist',
+                $formatterClass
+            ));
+        }
+        if (!is_subclass_of($formatterClass, QuantityFormatterInterface::class)) {
+            throw new \InvalidArgumentException(sprintf(
+                'Formatter class "%s" must implement %s',
+                $formatterClass,
+                QuantityFormatterInterface::class
+            ));
+        }
         $this->quantityFormatterDefinition = new QuantityFormatterDefinition($formatterClass, $fractionUnit);

99-102: Remove or implement the no-op “measuredWith” method.

As previously mentioned, this method is unused and does nothing. Either implement its logic or remove it.

-public function measuredWith(\hiqdev\billing\registry\measure\RcpTrafCollector $param): self
-{
-    return $this;
-}

135-138: Translate documentation from Ukrainian to English.

This was highlighted earlier. Keeping docs in a common language helps all developers.

-     * це параметер визначає агрегатну функцію яка застосовується для щоденно записаних ресурсів щоб визнизначти
-     * місячне споживання за яке потрібно пробілити клієнта
+     * This parameter defines the aggregate function that is applied to daily recorded resources
+     * to determine the monthly consumption for which the client needs to be billed

28-28:

✅ Verification successful

Verify the import or definition of AggregateInterface.

You declare $aggregate and use AggregateInterface but do not import it. Confirm it’s either in the same namespace or properly imported. Let’s check for its definition or usage throughout the codebase:

Also applies to: 142-142


🏁 Script executed:

#!/bin/bash
# Searching for the import or definition of `AggregateInterface`
rg "AggregateInterface" -A 5 -t php

Length of output: 1961


AggregateInterface Import Verified

The search confirms that AggregateInterface is defined in src/product/AggregateInterface.php. Given that both the interface and the referencing class (PriceTypeDefinition) reside in the same directory (and likely in the same namespace), there’s no missing import issue.

Comment on lines +60 to +63
public function createQuantityFormatter(
string $type,
FractionQuantityData $data,
): array {
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

Evaluate the return type of createQuantityFormatter.
Its signature indicates it returns an array, but typically a “formatter” is an object implementing a known interface, not arbitrarily structured data. Consider returning a QuantityFormatterInterface or a dedicated object instead.

-public function createQuantityFormatter(
-    string $type,
-    FractionQuantityData $data,
-): array {
+public function createQuantityFormatter(
+    string $type,
+    FractionQuantityData $data,
+): QuantityFormatterInterface {
📝 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
public function createQuantityFormatter(
string $type,
FractionQuantityData $data,
): array {
public function createQuantityFormatter(
string $type,
FractionQuantityData $data,
): QuantityFormatterInterface {

…tyFormatterDefinition and QuantityFormatterFactory classes
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 (4)
src/product/quantity/FractionQuantityData.php (1)

9-13: Consider adding class and constructor-level docblocks.
While the class is self-documenting to an extent, adding docblocks for the constructor parameters can improve clarity for future maintainers, especially regarding the meaning of $fractionOfMonth.

src/product/quantity/QuantityFormatterFactory.php (1)

9-13: Add docblocks to clarify the factory method’s purpose and parameters.
A short docblock for create() explaining each parameter and possible exceptions can improve readability and maintainability.

src/product/quantity/QuantityFormatterDefinition.php (1)

14-18: Use strict typing or add docblocks for $fractionUnit.
Currently, $fractionUnit can be an interface, a string, or null, making it ambiguous. Either use scalar/union type hints if possible or document it in depth to avoid confusion.

src/product/BillingRegistry.php (1)

88-100: Unify spelling of "Behavior" for consistency.
Line 100 uses 'Behaviour was not found'. Consider using the same spelling (“Behavior”) project-wide.
Additionally, verify you have null or class checks before casting objects to $behaviorClassWrapper to avoid subtle runtime errors.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 77b9efe and 9dad8c6.

📒 Files selected for processing (5)
  • src/product/BillingRegistry.php (1 hunks)
  • src/product/PriceTypeDefinition.php (1 hunks)
  • src/product/quantity/FractionQuantityData.php (1 hunks)
  • src/product/quantity/QuantityFormatterDefinition.php (1 hunks)
  • src/product/quantity/QuantityFormatterFactory.php (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
src/product/BillingRegistry.php (1)
Learnt from: SilverFire
PR: hiqdev/php-billing#93
File: src/product/BillingRegistry.php:5-5
Timestamp: 2025-01-13T12:20:09.570Z
Learning: The `hiqdev/php-billing` repository should not depend on `advancedhosters/billing-registry`. Use internal interfaces and implementations instead.
🔇 Additional comments (11)
src/product/quantity/QuantityFormatterFactory.php (1)

16-16: Verify the formatter class implementation before instantiation.
Using new $formatterClass(...) is convenient, but if $formatterClass does not exist or does not implement the required interface, it can cause runtime errors. Consider validating or catching possible exceptions here.

src/product/BillingRegistry.php (3)

5-5: Remove external dependency references.
This line imports RepresentationInterface from the hiqdev\billing\registry\invoice namespace, which contradicts the guidance to avoid depending on external packages like advancedhosters/billing-registry.


42-45: Validate the correct interface is used for representations.
The docblock references an external interface, which may cause static analysis issues if the class is not in the same package.


60-63: Return a formatter interface rather than an array.
Given the usage of a “formatter,” returning QuantityFormatterInterface may be more appropriate than returning an array for clarity and consistency.

src/product/PriceTypeDefinition.php (7)

6-6: Add missing “hiqdev/billing-registry” dependency.

This file references classes from hiqdev\billing\registry\invoice without a corresponding dependency in composer.json. Failure to include "hiqdev/billing-registry" can lead to runtime errors in production environments.


16-28: Add class-level documentation and initialize properties.

A class docstring explaining the purpose of this class would improve clarity. Also, properties such as $unit, $description, $quantityFormatterDefinition, and $aggregate remain uninitialized at construction. If these methods are not called before usage, references to null values can lead to runtime errors.


72-72: Complete the TODO for formatter class validation.

The TODO note indicates that the existence of $formatterClass is not validated. Calling this method with an invalid or non-existent class will fail at runtime.


88-92: Add final validation in end() method.

The end() method currently returns $this->parent without checking whether required fields are set or valid. This was raised in a past review but is still unaddressed.


99-102: Remove or implement the no-op method.

The measuredWith() method is a no-op and is not referenced elsewhere. Remove it if unnecessary or provide an actual implementation.


114-117: Validate unit before returning.

The getUnit() method may return an uninitialized unit if unit() was never called. This could lead to unexpected errors. Ensure the unit is set or throw an exception if missing.


135-138: Translate the documentation to English.

The doc comment is still in a non-English language. Translating it into English will help maintain consistent and readable documentation for team members.

Comment on lines +122 to +132
public function getAggregate(string $type): AggregateInterface
{
$type = $this->convertStringTypeToType($type);

foreach ($this->priceTypes() as $priceTypeDefinition) {
if ($priceTypeDefinition->hasType($type)) {
return $priceTypeDefinition->getAggregate();
}
}

throw new AggregateNotFoundException('Aggregate was not found');
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

Ensure AggregateInterface and AggregateNotFoundException are imported or defined.
The code references AggregateInterface as a return type and throws AggregateNotFoundException, but these classes are not visibly imported. This can lead to runtime errors if they aren’t declared in the same namespace.

+use hiqdev\php\billing\product\AggregateInterface;
+use hiqdev\php\billing\product\AggregateNotFoundException;
📝 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
public function getAggregate(string $type): AggregateInterface
{
$type = $this->convertStringTypeToType($type);
foreach ($this->priceTypes() as $priceTypeDefinition) {
if ($priceTypeDefinition->hasType($type)) {
return $priceTypeDefinition->getAggregate();
}
}
throw new AggregateNotFoundException('Aggregate was not found');
<?php
namespace hiqdev\php\billing\product;
// Add the missing imports for AggregateInterface and AggregateNotFoundException
use hiqdev\php\billing\product\AggregateInterface;
use hiqdev\php\billing\product\AggregateNotFoundException;
// ... other use statements and code ...
class BillingRegistry
{
// ... other methods and properties ...
public function getAggregate(string $type): AggregateInterface
{
$type = $this->convertStringTypeToType($type);
foreach ($this->priceTypes() as $priceTypeDefinition) {
if ($priceTypeDefinition->hasType($type)) {
return $priceTypeDefinition->getAggregate();
}
}
throw new AggregateNotFoundException('Aggregate was not found');
}
// ... other methods and properties ...
}

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/BillingRegistry.php (1)

60-63: 🛠️ Refactor suggestion

Return a dedicated formatter object instead of an array.

The method name suggests it creates a “formatter,” yet it returns an array. Returning an object implementing a known QuantityFormatterInterface (if one exists) would be more intuitive and type-safe.

-): array {
+): QuantityFormatterInterface {
🧹 Nitpick comments (4)
src/product/invoice/RepresentationInterface.php (1)

7-14: Consider adding docblocks for clarity.

Defining docblocks for each method can improve understandability and consistency, especially regarding the expected behavior or possible exceptions. This is particularly helpful for new teammates unfamiliar with these methods.

src/product/invoice/InvoiceRepresentationCollection.php (1)

23-40: Add method-level documentation and reconsider naming for end().

  1. Methods like attach, end, and filterByType are missing docblocks. Including docblocks can clarify intent and usage, especially for parameters and return types.
  2. The method name end() can be confusing since it doesn’t indicate its behavior—returning the PriceTypeDefinition rather than “ending” anything. A more descriptive name (like finalize or getDefinition) could improve clarity.
src/product/BillingRegistry.php (2)

42-46: Refine the docblock return type for clarity.

Although the docblock indicates an array of RepresentationInterface, you might clarify whether it always returns such interfaces or if edge cases exist. Also consider specifying an empty array scenario if no matches are found.


100-100: Fix the spelling mismatch of 'Behaviour'.

Use consistent naming to match the exception class BehaviorNotFoundException:

-        throw new BehaviorNotFoundException('Behaviour was not found');
+        throw new BehaviorNotFoundException('Behavior was not found');
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 9dad8c6 and afe5a62.

📒 Files selected for processing (4)
  • src/product/BillingRegistry.php (1 hunks)
  • src/product/PriceTypeDefinition.php (1 hunks)
  • src/product/invoice/InvoiceRepresentationCollection.php (1 hunks)
  • src/product/invoice/RepresentationInterface.php (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
src/product/BillingRegistry.php (1)
Learnt from: SilverFire
PR: hiqdev/php-billing#93
File: src/product/BillingRegistry.php:5-5
Timestamp: 2025-01-13T12:20:09.570Z
Learning: The `hiqdev/php-billing` repository should not depend on `advancedhosters/billing-registry`. Use internal interfaces and implementations instead.
🔇 Additional comments (6)
src/product/BillingRegistry.php (1)

122-133:

❓ Verification inconclusive

Verify AggregateInterface and AggregateNotFoundException references.

Confirm these classes exist within the hiqdev\php\billing\product namespace or add the relevant use statements. If they reside elsewhere, importing them is necessary to avoid runtime errors.


🏁 Script executed:

#!/bin/bash
# Description: Search for the definitions of AggregateInterface and AggregateNotFoundException in the codebase.
ast-grep --pattern $'interface AggregateInterface { $$$ }'
ast-grep --pattern $'class AggregateNotFoundException extends $_ { $$$ }'

Length of output: 134


Ensure Correct Import of Aggregate Classes and Exceptions
The automated search for the definitions of AggregateInterface and AggregateNotFoundException didn’t return any results. Please verify manually that these entities are defined where expected (preferably under the hiqdev\php\billing\product namespace) or elsewhere, and that the file src/product/BillingRegistry.php (lines 122–133) includes the correct use statements. If they reside in another namespace, add the appropriate import statements to prevent runtime errors.

  • Verify that AggregateInterface is defined.
  • Verify that AggregateNotFoundException is defined (extending the correct base class).
  • Update the namespace imports accordingly if these definitions are located elsewhere.
src/product/PriceTypeDefinition.php (5)

16-17: Add PHPDoc for the class.

There is no class-level documentation describing its purpose and usage, which was suggested in previous reviews. Adding documentation will make it easier to understand and maintain the class.


70-73: Implement the TODO to check if the formatter class exists.

As noted in your code comment and previous reviews, you should confirm that $formatterClass is valid (e.g., class_exists) and that it implements the required interface.


88-92: Add final validation of required properties before returning.

Past comments recommended validating all required fields (e.g., $unit, $description, $quantityFormatterDefinition) in the end() method. This ensures a fully valid state when returning to the parent.


99-102: Address the no-op measuredWith method.

As previously noted, this method is currently a placeholder with no functionality. Either implement the behavior measurement logic or remove the method to avoid confusion.


135-138: Translate documentation to English.

The doc comment is in a non-English language, which was flagged in a previous review. For broader maintainability, translate these lines to English.

Comment on lines 20 to 21
private string $description;

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

Validate or initialize $description.

Currently, $description may remain unset if a caller never invokes the description() method. Consider assigning a default value in the constructor or validating it before access to avoid uninitialized property errors.

Comment on lines 78 to 86
public function createQuantityFormatter(
FractionQuantityData $data,
): QuantityFormatterInterface {
return QuantityFormatterFactory::create(
$this->getUnit()->createExternalUnit(),
$this->quantityFormatterDefinition,
$data,
);
}
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

Guard against uninitialized $unit in createQuantityFormatter.

If unit() is never called before invoking createQuantityFormatter(), $this->unit will be uninitialized. Add an early check or default assignment to prevent runtime errors.

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: 1

🧹 Nitpick comments (10)
src/product/invoice/InvoiceRepresentationCollection.php (4)

9-9: Consider documenting the array type

For clarity, consider adding a doc-block (e.g., /** @var RepresentationInterface[] */) above the $representations property or employing generics if your PHP version and tooling support it. This helps maintainers quickly grasp that only RepresentationInterface objects are stored.


11-13: Validate incoming parameter for robustness

Although the constructor is straightforward, you might consider verifying that $priceTypeDefinition meets any preconditions before storing it. Such guards can preemptively catch misconfiguration or usage errors in complex systems.


32-35: Rename method for clarity

The end() method name could be mistaken for PHP's built-in end() array function, which advances and returns the last element. Consider a more descriptive name, such as getPriceTypeDefinition(), to avoid confusion.

-public function end(): PriceTypeDefinition
+public function getPriceTypeDefinition(): PriceTypeDefinition

37-40: Refine collection filtering

filterByType() effectively narrows the collection by class name. For user convenience, consider returning the filtered results as a new InvoiceRepresentationCollection instance or reindexing the resulting array with array_values() to produce a zero-based index.

src/product/ProductInterface.php (1)

7-8: Consider a more conventional method name.

The "to" prefix typically suggests a conversion method, but it's unclear what's being converted to a product name. If this is not a conversion method, consider renaming to getProductName() or simply productName() for better clarity.

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

14-27: Consider optimizing the getAll method.

The nested loops could be replaced with a more concise solution using array functions.

 /**
  * @return PriceTypeDefinition[]
  */
 public function getAll(): array
 {
-    $allPrices = [];
-    foreach ($this->pricesGroupedByPriceType as $prices) {
-        foreach ($prices as $price) {
-            $allPrices[] = $price;
-        }
-    }
-
-    return $allPrices;
+    return array_merge(...array_values($this->pricesGroupedByPriceType));
 }

This approach flattens the nested arrays in a single line, making the code more concise and potentially more performant.


5-28: Consider adding helper methods for more flexibility.

The class currently only provides methods to add and get all price definitions. Consider adding additional methods to:

  1. Get prices for a specific price type
  2. Check if a price type exists
  3. Remove a price type or a specific price definition

This would make the class more flexible and robust, depending on your requirements.

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

137-141: Maintain consistent language in doc-block
The doc-block partially uses a non-English comment. Consider translating to English for consistency across the codebase.

src/product/TariffTypeDefinition.php (2)

35-39: Consider persisting or validating the suggester class
Right now, setPricesSuggester simply returns $this without actually storing or validating the $suggesterClass. If you intend to use this class downstream, consider adding a property and verifying it implements a known interface.

 private ?string $pricesSuggester = null;

 public function setPricesSuggester(string $suggesterClass): self
 {
+    if (!\class_exists($suggesterClass)) {
+        throw new \InvalidArgumentException("Suggester class {$suggesterClass} does not exist");
+    }
     $this->pricesSuggester = $suggesterClass;
     return $this;
 }

51-60: Efficient iteration in hasBehavior()
Using a simple foreach for behavior checks is fine for smaller collections. If you expect large numbers of behaviors, consider indexing them by class name for O(1) lookups.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between afe5a62 and 2cc1971.

📒 Files selected for processing (10)
  • src/product/BillingRegistryInterface.php (1 hunks)
  • src/product/ProductInterface.php (1 hunks)
  • src/product/TariffTypeDefinition.php (1 hunks)
  • src/product/TariffTypeDefinitionFactory.php (1 hunks)
  • src/product/behavior/BehaviourPriceTypeDefinitionCollection.php (1 hunks)
  • src/product/invoice/InvoiceRepresentationCollection.php (1 hunks)
  • src/product/price/PriceTypeDefinition.php (1 hunks)
  • src/product/price/PriceTypeDefinitionCollection.php (1 hunks)
  • src/product/price/PriceTypeInterface.php (1 hunks)
  • src/product/price/PriceTypeStorage.php (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/product/BillingRegistryInterface.php
  • src/product/behavior/BehaviourPriceTypeDefinitionCollection.php
🧰 Additional context used
🧠 Learnings (1)
src/product/TariffTypeDefinition.php (1)
Learnt from: SilverFire
PR: hiqdev/php-billing#93
File: src/product/BillingRegistry.php:5-5
Timestamp: 2025-03-13T15:02:11.733Z
Learning: The `hiqdev/php-billing` repository should not depend on `advancedhosters/billing-registry`. Use internal interfaces and implementations instead.
🔇 Additional comments (12)
src/product/invoice/InvoiceRepresentationCollection.php (2)

7-8: Well-structured use of IteratorAggregate

Implementing \IteratorAggregate is a clean approach to allow external iteration over the stored representations. This design aligns well with standard collection patterns.


23-30:

✅ Verification successful

Confirm side effects are intended

By calling $representation->setType($this->priceTypeDefinition->type()), you are modifying the $representation object. Ensure that external callers expect this side effect. If there are cases where RepresentationInterface should retain a different preexisting type, you may need additional logic or checks.


🏁 Script executed:

#!/bin/bash
# Description: Searches code references for RepresentationInterface usage to confirm that 
# the type assignment does not conflict with existing usage patterns.

fd RepresentationInterface --exec rg "RepresentationInterface"

Length of output: 138


Review Confirmation: Intended Side Effect on RepresentationInterface

The investigation shows that the current usage pattern—invoking

$representation->setType($this->priceTypeDefinition->type());

within the attach method is consistent with how RepresentationInterface is defined and used elsewhere. The search output only returned the interface declarations, with no evidence of conflicting usage that requires preserving a preexisting type. Please ensure that external callers are aware of this side effect, and if there’s ever a scenario where a representation should retain an existing type, consider adding the necessary checks or documenting the behavior clearly.

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

1-8: Clean and well-structured interface definition.

The interface is properly defined with a clear, single purpose and follows PSR-12 standards. The use of strict types is appropriate for ensuring type safety.

src/product/TariffTypeDefinitionFactory.php (1)

5-11: Factory implementation looks good.

The factory pattern is appropriately used here for creating TariffTypeDefinition objects. The class has a single, focused responsibility with a clear method signature.

src/product/ProductInterface.php (1)

5-10: Interface definition is clean and follows standards.

The interface has clear methods with proper return types and follows PSR-12 coding standards.

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

7-7: Property definition is clear and properly typed.

The array property with default empty array initialization is appropriate for the storage purpose.


9-12: Method implementation is correct and type-safe.

The add method correctly uses the PriceTypeInterface's name method to organize price definitions.

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

32-41: Overall constructor logic looks reasonable
The constructor correctly initializes invoice and behavior collections, calls init(), and sets up the relationships to the parent and tariffType. This is a good approach for ensuring each PriceTypeDefinition is self-contained and extensible.


101-104: Remove external registry reference
Referencing \hiqdev\billing\registry\measure\RcpTrafCollector introduces a dependency on billing-registry, which contradicts the retrieved learnings about avoiding external registry dependencies. Refactor to rely on internal interfaces or rename classes to maintain independence.

src/product/TariffTypeDefinition.php (1)

17-21: Constructor logic is straightforward
You’re initializing a PriceTypeDefinitionCollection and a BehaviorTariffTypeCollection with clear references to the parent TariffTypeDefinition. This promotes a clean design and a well-defined relationship between tariff types and price definitions.

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

5-7: Avoid external package references
Imports from hiqdev\billing\registry\product\... rely on an external package, which contradicts your retrieved learning advising independence. Migrate to internal interfaces or rename classes to remove this dependency.


36-42: General approach to adding price types is solid
The private addPriceType() method cleanly factors out creation and storage logic, ensuring consistent handling of all price types. This fosters maintainability and clarity.

Comment on lines +72 to +78
public function quantityFormatter(string $formatterClass, $fractionUnit = null): self
{
// TODO: check if formatterClass exists
$this->quantityFormatterDefinition = new QuantityFormatterDefinition($formatterClass, $fractionUnit);

return $this;
}
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

Implement the “TODO” check for formatter class existence
Currently, there is a // TODO: check if formatterClass exists comment. Leaving it unimplemented might cause runtime errors if a non-existent class name is provided.

You could add a simple guard clause like:

 public function quantityFormatter(string $formatterClass, $fractionUnit = null): self
 {
+    if (!\class_exists($formatterClass)) {
+        throw new \InvalidArgumentException("Formatter class {$formatterClass} does not exist");
+    }
     $this->quantityFormatterDefinition = new QuantityFormatterDefinition($formatterClass, $fractionUnit);
     return $this;
 }
📝 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
public function quantityFormatter(string $formatterClass, $fractionUnit = null): self
{
// TODO: check if formatterClass exists
$this->quantityFormatterDefinition = new QuantityFormatterDefinition($formatterClass, $fractionUnit);
return $this;
}
public function quantityFormatter(string $formatterClass, $fractionUnit = null): self
{
if (!\class_exists($formatterClass)) {
throw new \InvalidArgumentException("Formatter class {$formatterClass} does not exist");
}
// TODO: check if formatterClass exists
$this->quantityFormatterDefinition = new QuantityFormatterDefinition($formatterClass, $fractionUnit);
return $this;
}

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/price/PriceTypeDefinitionCollection.php (2)

1-4: Enforce consistent file-level doc comments.
Although the file is new, consider adding a PHPDoc-style comment at the top describing the class’s purpose, usage context, and dependencies. This will help future maintainers quickly understand the file’s responsibilities.


31-34: Repeated methods could benefit from docblocks or potential refactoring.
All these methods (monthly, overuse, feature, domain, certificate, discount) share a similar pattern, differing only by the GType constant. Though not intrinsically problematic, consider adding doc comments for each method to clarify usage and expected parameters or unify them via a single convenience method if desired.

Also applies to: 36-42, 44-47, 49-52, 54-57, 59-62, 64-67

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 2cc1971 and 6411325.

📒 Files selected for processing (1)
  • src/product/price/PriceTypeDefinitionCollection.php (1 hunks)
🔇 Additional comments (5)
src/product/price/PriceTypeDefinitionCollection.php (5)

11-12: Implementation of \IteratorAggregate looks appropriate.
The class appropriately implements \IteratorAggregate, which is a suitable choice for exposing internal data through iteration. No issues here.


13-16: Properties are well-defined and typed.
Utilizing typed properties (PriceTypeStorage, PriceTypeDefinitionFactory) enforces object expectations and improves maintainability.


17-21: Constructor injects dependencies neatly.
Marking $parent as private readonly is a good practice with constructor property promotion in PHP 8.1+, ensuring immutability and clarity.


69-72: Fluent builder pattern is consistent.
Returning $this->parent from the end() method is a clean way to finalize the chained configuration while maintaining a fluent interface. This aligns well with the rest of the design.


23-29:

✅ Verification successful

Iterator method is straightforward.
Using \ArrayIterator to wrap storage->getAll() is a concise approach. Ensure PriceTypeStorage::getAll() returns an array or is array-castable at all times.

If there's any doubt about the return type from PriceTypeStorage, please verify it with the following command:


🏁 Script executed:

#!/bin/bash
rg -A 5 'function getAll' 

Length of output: 472


Iterator method correctness verified.

The implementation in src/product/price/PriceTypeDefinitionCollection.php correctly wraps the array returned by PriceTypeStorage::getAll() with an \ArrayIterator. Verification via the command confirms that PriceTypeStorage::getAll() indeed returns an array. No further changes are required.

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/TariffTypeDefinition.php (1)

36-40: ⚠️ Potential issue

Implement the setPricesSuggester method

The setPricesSuggester method contains only a comment and doesn't actually store or validate the suggester class. This method should be implemented to ensure proper functionality.

 public function setPricesSuggester(string $suggesterClass): TariffTypeDefinitionInterface
 {
-    // Validate or store the suggester class
+    // Validate that the suggester class exists and implements the expected interface
+    if (!class_exists($suggesterClass)) {
+        throw new \InvalidArgumentException(sprintf('Price suggester class "%s" does not exist', $suggesterClass));
+    }
+    
+    // Here you might want to store the suggester class in a property
+    $this->pricesSuggesterClass = $suggesterClass;
     return $this;
 }
🧹 Nitpick comments (8)
src/product/price/PriceTypeDefinition.php (3)

111-114: Consider implementing the measuredWith method or remove it if unused

The measuredWith method takes a parameter but doesn't use it, simply returning $this. If this is intended as a placeholder for future implementation, consider adding a TODO comment. Otherwise, either implement the functionality or remove the method if it's not needed.


147-159: Translate non-English comments to improve code readability

The comments on lines 148-149 appear to be in Ukrainian. Consider translating these to English to maintain consistency and improve accessibility for all developers working with the codebase.

Suggested translation:

- /**
-  * це параметер визначає агрегатну функцію яка застосовується для щоденно записаних ресурсів щоб визнизначти
-  * місячне споживання за яке потрібно пробілити клієнта
-  *
+ /**
+  * This parameter defines the aggregate function that is applied to daily recorded resources to determine
+  * the monthly consumption for which the client needs to be billed
+  *

24-44: Initialize all properties in the constructor to prevent undefined property errors

Some properties (unit, description, quantityFormatterDefinition, aggregate) are declared but not initialized in the constructor. This could lead to undefined property errors if the corresponding setters aren't called before these properties are accessed.

Consider initializing these properties with default values in the constructor, or make them nullable and add appropriate null checks in the getter methods.

src/product/TariffTypeDefinitionInterface.php (2)

11-26: Add class-level documentation to explain interface purpose

While the interface is well-structured with properly typed methods, it lacks class-level documentation explaining its overall purpose and role in the billing system. Adding a class PHPDoc comment would improve maintainability and help new developers understand how this interface fits into the architecture.

 /**
+ * Interface for defining tariff types and their pricing structures.
+ * Provides a fluent interface for configuring tariff types with associated products,
+ * price definitions, and additional behaviors.
+ *
  * @template T of PriceTypeDefinitionCollectionInterface
  */
 interface TariffTypeDefinitionInterface extends ParentNodeDefinitionInterface

25-25: Consider documenting the end() method's purpose

The purpose of the end() method isn't immediately clear from its name or signature. Adding a PHPDoc comment would clarify its role in completing the configuration of a tariff type definition.

+/**
+ * Finalizes the tariff type definition and returns the instance.
+ * This method should be called after all configuration is complete.
+ * 
+ * @return static
+ */
 public function end(): static;
src/product/TariffTypeDefinition.php (3)

52-61: Improve the hasBehavior method implementation

The current implementation checks if a behavior is an instance of a class name, but this approach has limitations when dealing with interfaces or class hierarchies. Consider enhancing this method for more robust behavior checking.

 public function hasBehavior(string $behaviorClassName): bool
 {
-    foreach ($this->behaviorCollection as $behavior) {
-        if ($behavior instanceof $behaviorClassName) {
-            return true;
-        }
-    }
-
-    return false;
+    return $this->behaviorCollection->hasBehaviorOfType($behaviorClassName);
 }

This suggests delegating the behavior check to the collection, which could have a more sophisticated implementation while keeping this class focused on its main responsibilities.


47-50: Consider adding a method to the interface for behavior management

The withBehaviors() method is defined in the implementation but not in the TariffTypeDefinitionInterface. Consider adding this method to the interface for consistency and to ensure users can access behavior-related functionality through the interface.

// In TariffTypeDefinitionInterface.php:
 interface TariffTypeDefinitionInterface extends ParentNodeDefinitionInterface
 {
     // ... existing methods
+
+    /**
+     * Access behavior collection for configuration.
+     * 
+     * @return BehaviorTariffTypeCollectionInterface
+     */
+    public function withBehaviors(): BehaviorTariffTypeCollectionInterface;
+
+    /**
+     * Check if this tariff type has a specific behavior.
+     * 
+     * @param string $behaviorClassName Class or interface name to check
+     * @return bool
+     */
+    public function hasBehavior(string $behaviorClassName): bool;
 }

12-16: Add property type initialization and visibility

The properties are properly typed, but their initialization is distributed between declaration and constructor. Consider adding initializers to make the code more maintainable and consider if these properties should be readonly.

-    private ProductInterface $product;
+    private ProductInterface $product;  // Initialized in ofProduct method
 
-    private PriceTypeDefinitionCollection $prices;
+    private readonly PriceTypeDefinitionCollection $prices;
 
-    private BehaviorTariffTypeCollection $behaviorCollection;
+    private readonly BehaviorTariffTypeCollection $behaviorCollection;
+
+    private bool $locked = false;  // To implement state locking in end() method
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 6411325 and c75b5d4.

📒 Files selected for processing (9)
  • src/product/TariffTypeDefinition.php (1 hunks)
  • src/product/TariffTypeDefinitionInterface.php (1 hunks)
  • src/product/invoice/InvoiceRepresentationCollection.php (1 hunks)
  • src/product/price/PriceTypeDefinition.php (1 hunks)
  • src/product/price/PriceTypeDefinitionCollection.php (1 hunks)
  • src/product/price/PriceTypeDefinitionCollectionInterface.php (1 hunks)
  • src/product/price/PriceTypeDefinitionFactory.php (1 hunks)
  • src/product/price/PriceTypeDefinitionFactoryInterface.php (1 hunks)
  • src/product/price/PriceTypeStorage.php (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/product/price/PriceTypeStorage.php
  • src/product/price/PriceTypeDefinitionCollection.php
  • src/product/invoice/InvoiceRepresentationCollection.php
🧰 Additional context used
🧠 Learnings (1)
src/product/TariffTypeDefinition.php (1)
Learnt from: SilverFire
PR: hiqdev/php-billing#93
File: src/product/BillingRegistry.php:5-5
Timestamp: 2025-03-13T15:02:11.733Z
Learning: The `hiqdev/php-billing` repository should not depend on `advancedhosters/billing-registry`. Use internal interfaces and implementations instead.
🔇 Additional comments (8)
src/product/price/PriceTypeDefinitionFactory.php (1)

8-17: Well-implemented factory class

The PriceTypeDefinitionFactory follows the factory pattern correctly, providing a clean implementation for creating PriceTypeDefinition instances. The code is concise and follows good dependency injection practices.

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

8-15: Interface definition looks good

The interface is well-defined with appropriate method signature and return type. It creates a clear contract for implementing classes.

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

8-18: Good interface design with appropriate extension

The interface is well-structured, extending \IteratorAggregate which is appropriate for a collection. The method signatures are clear and the PHPDoc comment for getIterator() adds helpful type information.

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

76-82: Implement the "TODO" check for formatter class existence

Currently, there is a // TODO: check if formatterClass exists comment. Leaving it unimplemented might cause runtime errors if a non-existent class name is provided.

You could add a simple guard clause like:

 public function quantityFormatter(string $formatterClass, $fractionUnit = null): self
 {
+    if (!\class_exists($formatterClass)) {
+        throw new \InvalidArgumentException("Formatter class {$formatterClass} does not exist");
+    }
     $this->quantityFormatterDefinition = new QuantityFormatterDefinition($formatterClass, $fractionUnit);

     return $this;
 }

18-165: Overall well-structured class with good implementation

The PriceTypeDefinition class provides a comprehensive structure for managing pricing definitions. It follows good OOP principles, uses type hints effectively, and provides clear method contracts. With a few minor improvements as noted, this will be an excellent addition to the codebase.

src/product/TariffTypeDefinitionInterface.php (2)

8-10: Good use of PHPDoc templates for type safety

The template annotation on this interface provides strong typing for the withPrices() method return value, which is excellent for static analysis tools like Psalm.


19-23: Excellent use of PHPDoc annotations for generic return types

The PHPDoc for withPrices() properly documents both the general return type and the templated type using @psalm-return, which ensures type-safety while maintaining compatibility with various static analysis tools.

src/product/TariffTypeDefinition.php (1)

1-69: Overall implementation looks promising

The implementation of TariffTypeDefinition provides a good foundation for managing tariff types in the billing system. With the suggested improvements to handle edge cases, implement validation, and improve type consistency, this class will be robust and maintainable.

Comment on lines +63 to +67
public function end(): TariffTypeDefinitionInterface
{
// Validate the TariffType and lock its state
return $this;
}
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

Implement validation in the end() method

The end() method contains a comment about validating the tariff type and locking its state, but doesn't implement any actual validation logic. This should be implemented to ensure tariff types are properly configured before use.

 public function end(): TariffTypeDefinitionInterface
 {
-    // Validate the TariffType and lock its state
+    // Validate required properties are set
+    if (!isset($this->product)) {
+        throw new \LogicException('Product must be set using ofProduct() method');
+    }
+    
+    // Validate prices configuration is complete
+    if ($this->prices->isEmpty()) {
+        throw new \LogicException('At least one price type must be defined');
+    }
+    
+    // Lock the state to prevent further modifications
+    $this->locked = true;
     return $this;
 }
📝 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
public function end(): TariffTypeDefinitionInterface
{
// Validate the TariffType and lock its state
return $this;
}
public function end(): TariffTypeDefinitionInterface
{
// Validate required properties are set
if (!isset($this->product)) {
throw new \LogicException('Product must be set using ofProduct() method');
}
// Validate prices configuration is complete
if ($this->prices->isEmpty()) {
throw new \LogicException('At least one price type must be defined');
}
// Lock the state to prevent further modifications
$this->locked = true;
return $this;
}

Comment on lines +29 to +34
public function ofProduct(ProductInterface $product): TariffTypeDefinitionInterface
{
$this->product = $product;

return $this;
}
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

Add product property initialization in constructor

The ofProduct method sets the $product property, but this property isn't initialized in the constructor. If ofProduct is never called, accessing $product could result in an undefined property error.

 public function __construct(private readonly TariffTypeInterface $tariffType)
 {
     $this->prices = new PriceTypeDefinitionCollection($this, new PriceTypeDefinitionFactory());
     $this->behaviorCollection = new BehaviorTariffTypeCollection($this, $tariffType);
+    $this->product = new NullProduct(); // Or use null and check before access
 }

Alternatively, add checks before accessing $product:

 public function someMethodUsingProduct()
 {
+    if (!isset($this->product)) {
+        throw new \LogicException('Product must be set using ofProduct() method before accessing it');
+    }
     // use $this->product
 }
📝 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
public function ofProduct(ProductInterface $product): TariffTypeDefinitionInterface
{
$this->product = $product;
return $this;
}
<?php
namespace YourNamespace;
use hiqdev\php\billing\product\TariffTypeInterface;
use YourNamespace\ProductInterface;
use YourNamespace\NullProduct;
class TariffTypeDefinition implements TariffTypeDefinitionInterface
{
// Declare the product as a nullable property if you plan to use null checks.
private ?ProductInterface $product = null;
public function __construct(private readonly TariffTypeInterface $tariffType)
{
$this->prices = new PriceTypeDefinitionCollection($this, new PriceTypeDefinitionFactory());
$this->behaviorCollection = new BehaviorTariffTypeCollection($this, $tariffType);
$this->product = new NullProduct(); // Initialize product to avoid undefined property errors
}
public function ofProduct(ProductInterface $product): TariffTypeDefinitionInterface
{
$this->product = $product;
return $this;
}
// Alternative: If you prefer to check the product before usage rather than initializing it
public function someMethodUsingProduct()
{
if (!isset($this->product)) {
throw new \LogicException('Product must be set using ofProduct() method before accessing it');
}
// Use $this->product safely here
}
// Other methods and class content...
}

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 (1)
composer.json (1)

64-64: Review the Addition of the 'opis/closure' Dependency with Version Aliasing

The addition of

"opis/closure": "3.x-dev as 3.6.x-dev"

in the require-dev section is intended to support closure handling in the context of the new billing functionalities. Please verify that using a development branch with an alias to 3.6.x-dev is aligned with our compatibility and stability requirements for development and testing. If possible, consider whether pinning to a stable version might be preferable to ensure consistency across environments.

Tags:

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 27c16d1 and 6808daf.

📒 Files selected for processing (1)
  • composer.json (1 hunks)

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: 1

🧹 Nitpick comments (5)
src/product/price/PriceTypeDefinition.php (5)

151-153: Translate comment to match documentation language

The comment appears to be in Ukrainian, while the rest of the code documentation is in English. For consistency and to improve code maintainability across global teams, consider translating it.

-/**
- * це параметер визначає агрегатну функцію яка застосовується для щоденно записаних ресурсів щоб визнизначти
- * місячне споживання за яке потрібно пробілити клієнта
- *
+ /**
+  * This parameter defines the aggregate function that is applied to daily recorded resources
+  * to determine the monthly consumption for which the client needs to be billed
+  *

87-95: Add null check for quantityFormatterDefinition

The createQuantityFormatter method uses $this->quantityFormatterDefinition without checking if it has been set. If quantityFormatter() is not called before this method, it could lead to undefined property errors.

 public function createQuantityFormatter(
     FractionQuantityData $data,
 ): QuantityFormatterInterface {
+    if (!isset($this->quantityFormatterDefinition)) {
+        throw new \LogicException('Quantity formatter definition has not been set. Call quantityFormatter() first.');
+    }
     return QuantityFormatterFactory::create(
         $this->getUnit()->createExternalUnit(),
         $this->quantityFormatterDefinition,
         $data,
     );
 }

100-104: Implement validation mentioned in comment

The comment indicates that the end() method should "Validate the PriceType and lock its state", but no actual validation is performed. Consider implementing validation logic to ensure the price type is in a valid state before returning.

 public function end(): PriceTypeDefinitionCollectionInterface
 {
     // Validate the PriceType and lock its state
+    if (!isset($this->unit)) {
+        throw new \LogicException('Unit must be set before ending price type definition');
+    }
+    if (!isset($this->description)) {
+        throw new \LogicException('Description must be set before ending price type definition');
+    }
     return $this->parent;
 }

129-132: Add null check for unit property

The getUnit() method returns $this->unit without checking if it has been set. If unit() is not called before this method, it could lead to undefined property errors.

 public function getUnit(): UnitInterface
 {
+    if (!isset($this->unit)) {
+        throw new \LogicException('Unit has not been set. Call unit() first.');
+    }
     return $this->unit;
 }

164-167: Add null check for aggregate property

The getAggregate() method returns $this->aggregate without checking if it has been set. If aggregation() is not called before this method, it could lead to undefined property errors.

 public function getAggregate(): AggregateInterface
 {
+    if (!isset($this->aggregate)) {
+        throw new \LogicException('Aggregate has not been set. Call aggregation() first.');
+    }
     return $this->aggregate;
 }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 6808daf and 413f729.

📒 Files selected for processing (2)
  • src/product/price/PriceTypeDefinition.php (1 hunks)
  • src/product/price/PriceTypeDefinitionFactoryInterface.php (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/product/price/PriceTypeDefinitionFactoryInterface.php
🧰 Additional context used
🧬 Code Definitions (1)
src/product/price/PriceTypeDefinition.php (5)
src/product/behavior/BehaviourPriceTypeDefinitionCollection.php (3) (3)
  • BehaviourPriceTypeDefinitionCollection (8-19)
  • __construct (10-13)
  • end (15-18)
src/product/invoice/InvoiceRepresentationCollection.php (3) (3)
  • InvoiceRepresentationCollection (10-47)
  • __construct (14-16)
  • end (38-41)
src/product/price/PriceTypeDefinitionCollection.php (2) (2)
  • __construct (17-22)
  • end (44-47)
src/product/TariffTypeDefinitionInterface.php (2) (2)
  • tariffType (13-13)
  • end (25-25)
src/product/price/PriceTypeDefinitionFactoryInterface.php (1) (1)
  • create (10-14)
🪛 GitHub Check: PHP 8.3
src/product/price/PriceTypeDefinition.php

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

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

79-85: Implement the "TODO" check for formatter class existence

Currently, there is a // TODO: check if formatterClass exists comment. Leaving it unimplemented might cause runtime errors if a non-existent class name is provided.

You could add a simple guard clause like:

 public function quantityFormatter(string $formatterClass, $fractionUnit = null): self
 {
+    if (!\class_exists($formatterClass)) {
+        throw new \InvalidArgumentException("Formatter class {$formatterClass} does not exist");
+    }
     $this->quantityFormatterDefinition = new QuantityFormatterDefinition($formatterClass, $fractionUnit);
     return $this;
 }

return $this->invoiceCollection;
}

public function measuredWith(\hiqdev\billing\registry\measure\RcpTrafCollector $param): self
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 undefined class reference in measuredWith method

The static analysis tool has detected that the class \hiqdev\billing\registry\measure\RcpTrafCollector does not exist. This will cause runtime errors if this method is called.

Fix by:

  1. Adding the missing class, or
  2. Updating the parameter type to a valid class, or
  3. Using an interface or abstract class that exists in your codebase
-public function measuredWith(\hiqdev\billing\registry\measure\RcpTrafCollector $param): self
+public function measuredWith(/* Replace with correct type */ $param): self
📝 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
public function measuredWith(\hiqdev\billing\registry\measure\RcpTrafCollector $param): self
public function measuredWith(/* Replace with correct type */ $param): self
🧰 Tools
🪛 GitHub Check: PHP 8.3

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

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)
src/product/price/PriceTypeDefinitionCollection.php (4)

8-12: Good use of PHP generics templates

The template annotations are well-structured, providing clear type information for static analysis tools. The @mixin T annotation suggests this class forwards method calls to the collection instance, but there's no visible implementation of magic methods like __call to handle this behavior.

Consider adding a __call method if you intend to forward methods to the collection instance:

public function __call(string $name, array $arguments)
{
    if (method_exists($this->collectionInstance, $name)) {
        return $this->collectionInstance->$name(...$arguments);
    }
    
    throw new \BadMethodCallException("Method $name does not exist");
}

36-42: Consider handling duplicate price types

The priceType() method creates and adds a new price type definition without checking if one with the same type already exists. This could potentially lead to duplicate entries for the same type.

Consider adding a check for existing price types:

public function priceType(TypeInterface $type): PriceTypeDefinition
{
+    // Check if price type already exists
+    foreach ($this->storage->getAll() as $existingPriceType) {
+        if ($existingPriceType->getType()->getName() === $type->getName()) {
+            return $existingPriceType;
+        }
+    }
    
    $priceType = $this->factory->create($this->collectionInstance, $type, $this->parent->tariffType());
    $this->storage->add($type, $priceType);

    return $priceType;
}

Alternatively, if duplicates are intentional, consider adding a comment explaining this design decision.


44-51: Fix typo in PHPDoc annotation

There's a typo in the PHPDoc annotation on line 46.

/**
 * @return TariffTypeDefinitionInterface
- * @plsam-return M
+ * @psalm-return M
 */
public function end(): TariffTypeDefinitionInterface
{
    return $this->parent;
}

1-52: Consider adding more collection-like methods

For a collection class, it would be useful to have additional methods like has(), get(), or remove() to provide more robust collection manipulation capabilities.

Here are some methods you might consider adding:

/**
 * Check if a price type with the given type exists in the collection
 */
public function has(TypeInterface $type): bool
{
    foreach ($this->storage->getAll() as $priceType) {
        if ($priceType->getType()->getName() === $type->getName()) {
            return true;
        }
    }
    
    return false;
}

/**
 * Get a price type by its type
 */
public function get(TypeInterface $type): ?PriceTypeDefinition
{
    foreach ($this->storage->getAll() as $priceType) {
        if ($priceType->getType()->getName() === $type->getName()) {
            return $priceType;
        }
    }
    
    return null;
}

/**
 * Remove a price type from the collection
 */
public function remove(TypeInterface $type): void
{
    // This would require adding a remove method to PriceTypeStorage
    $this->storage->remove($type);
}
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 413f729 and d7d838a.

📒 Files selected for processing (2)
  • src/product/price/PriceTypeDefinitionCollection.php (1 hunks)
  • src/product/price/PriceTypeDefinitionFactory.php (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/product/price/PriceTypeDefinitionFactory.php
🧰 Additional context used
🧬 Code Definitions (1)
src/product/price/PriceTypeDefinitionCollection.php (5)
src/product/price/PriceTypeDefinition.php (4) (4)
  • type (119-122)
  • __construct (36-48)
  • PriceTypeDefinition (22-168)
  • end (100-104)
src/product/price/PriceTypeStorage.php (3) (3)
  • PriceTypeStorage (7-30)
  • getAll (19-29)
  • add (11-14)
src/product/price/PriceTypeDefinitionCollectionInterface.php (3) (3)
  • getIterator (13-13)
  • priceType (15-15)
  • end (17-17)
src/product/price/PriceTypeDefinitionFactoryInterface.php (1) (1)
  • create (10-14)
src/product/TariffTypeDefinitionInterface.php (2) (2)
  • tariffType (13-13)
  • end (25-25)
🔇 Additional comments (2)
src/product/price/PriceTypeDefinitionCollection.php (2)

19-26: LGTM! Good constructor implementation

The constructor properly initializes the class properties and uses a sensible default for the optional collection instance parameter. The use of dependency injection for the parent and factory is a good practice.


28-34: LGTM! Well-implemented iterator method

The getIterator() method correctly returns an ArrayIterator populated with all price type definitions from the storage, making the collection iterable.

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.

2 participants