Skip to content

Conversation

@VadymHrechukha
Copy link
Collaborator

@VadymHrechukha VadymHrechukha commented Nov 6, 2025

Summary by CodeRabbit

  • New Features

    • Added a price-type collection for storing, enumerating and quickly checking available price types.
    • Introduced a dedicated exception for invalid price-type collections.
  • Breaking Changes

    • Price type interface namespace relocated; integrations may need to update references.
  • Tests

    • Added unit tests covering collection behavior, iteration, duplicates, names and existence checks.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 6, 2025

Walkthrough

Adds a new PriceTypeCollection class to manage PriceTypeInterface instances with iteration, counting, name listing and O(1) existence checks; moves PriceTypeInterface into namespace hiqdev\php\billing\product\Domain\Model\Price; adds InvalidPriceTypeCollectionException and unit tests covering collection behavior.

Changes

Cohort / File(s) Summary
New Collection Class
src/product/Domain/Model/Price/PriceTypeCollection.php
Adds PriceTypeCollection implementing IteratorAggregate and Countable. Stores PriceTypeInterface objects in a private readonly array, builds a flipped lookup map by name() for O(1) existence checks, and exposes names(), getIterator(), has(string), count(), and hasItems().
Interface Namespace Move
src/product/Domain/Model/Price/PriceTypeInterface.php
Changes namespace of PriceTypeInterface from hiqdev\php\billing\product\price to hiqdev\php\billing\product\Domain\Model\Price without altering its members.
Exception
src/product/Domain/Model/Price/Exception/InvalidPriceTypeCollectionException.php
Adds InvalidPriceTypeCollectionException extending InvalidArgumentException with factory becauseContainsNonPriceType(mixed $value): self.
Unit Tests
tests/unit/product/Domain/Model/Price/PriceTypeCollectionTest.php
Adds tests for empty collection, count/hasItems, has() behavior (including duplicates), iterator contents, and names(). Uses an anonymous class helper implementing PriceTypeInterface.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Collection as PriceTypeCollection
    participant Type as PriceTypeInterface

    Client->>Collection: __construct(types[])
    Note right of Collection `#E8F5E9`: store types[]\nbuild flipped map (name => true)
    Client->>Collection: has("hourly")
    alt exists
        Collection->>Collection: lookup flipped["hourly"]
        Collection-->>Client: true
    else missing
        Collection->>Collection: lookup flipped["hourly"]
        Collection-->>Client: false
    end

    Client->>Collection: getIterator()
    Collection-->>Client: ArrayIterator(types[])
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Pay attention to:
    • All imports/references updated to the new PriceTypeInterface namespace.
    • Constructor validation and InvalidPriceTypeCollectionException message correctness.
    • Flipped map behavior with duplicate type names and iteration order in tests.

Suggested reviewers

  • SilverFire

Poem

🐰 I hop through types with names in line,
I count and flip each tiny sign.
I guard the names, both new and old,
I iterate stories neatly told. 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.56% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: creating a new PriceTypeCollection class to provide simplified collection management for PriceTypeInterface instances, which aligns with the primary addition shown in the changeset.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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 (1)
src/product/Domain/Model/Price/PriceTypeCollection.php (1)

12-13: Consider adding class-level PHPDoc.

Adding a class-level docblock would improve documentation and clarify the purpose of this collection.

Apply this diff to add class-level documentation:

+/**
+ * Collection of PriceTypeInterface instances with efficient lookup by name.
+ */
 class PriceTypeCollection implements IteratorAggregate, Countable
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 29be10d and 0e563ff.

📒 Files selected for processing (1)
  • src/product/Domain/Model/Price/PriceTypeCollection.php (1 hunks)
🔇 Additional comments (4)
src/product/Domain/Model/Price/PriceTypeCollection.php (4)

24-30: LGTM!

The getIterator() implementation correctly returns an ArrayIterator over the types array. The PHPDoc type annotation is clear and accurate.


32-35: LGTM!

The has() method efficiently checks existence using the flipped array for O(1) lookup. The use of array_key_exists() is appropriate for this check.


37-40: LGTM!

The count() method correctly implements the Countable interface requirement.


42-45: LGTM!

The hasItems() convenience method provides a readable way to check if the collection is non-empty.


public function __construct(private readonly array $types = [])
{
$this->flippedTypes = array_flip(array_map(fn(PriceTypeInterface $t) => $t->name(), $types));
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 | 🟠 Major

Clarify or prevent duplicate name handling.

When multiple PriceTypeInterface instances have the same name(), array_flip silently keeps only the last occurrence in $flippedTypes. This creates an inconsistency: all duplicates remain in $types (returned by getIterator()), but has() won't distinguish between them. Consider either:

  1. Preventing duplicates by validating in the constructor
  2. Documenting this behavior explicitly

Example validation to prevent duplicates:

     public function __construct(private readonly array $types = [])
     {
+        $names = array_map(fn(PriceTypeInterface $t) => $t->name(), $types);
+        if (count($names) !== count(array_unique($names))) {
+            throw new \InvalidArgumentException('Duplicate price type names are not allowed');
+        }
         $this->flippedTypes = array_flip(array_map(fn(PriceTypeInterface $t) => $t->name(), $types));
     }

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)
tests/unit/product/Domain/Model/Price/PriceTypeCollectionTest.php (1)

69-80: Clarify the intended duplicate-name behavior.

The test validates that the collection stores multiple instances with the same name, but this creates ambiguous semantics:

  • Both instances are stored (count returns 2)
  • The lookup map only tracks existence, not which instance
  • There's no retrieval method to access specific instances

This behavior could confuse users who might expect either:

  1. Uniqueness enforcement (like a Set)
  2. The ability to retrieve all instances with a given name

Consider:

  • Adding constructor validation to prevent duplicates and throw a descriptive exception
  • Documenting this behavior explicitly in the class docblock if it's intentional
  • Clarifying the use case: Is the collection meant only for iteration and existence checks, or should it support retrieval?

Can you clarify the intended behavior? Should the collection enforce uniqueness, or is the current "allow duplicates but only track existence" behavior intentional for your use case?

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 70cb8c5 and 0ee2b4c.

📒 Files selected for processing (1)
  • tests/unit/product/Domain/Model/Price/PriceTypeCollectionTest.php (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
tests/unit/product/Domain/Model/Price/PriceTypeCollectionTest.php (1)
src/product/Domain/Model/Price/PriceTypeCollection.php (3)
  • PriceTypeCollection (11-45)
  • hasItems (41-44)
  • has (31-34)
🔇 Additional comments (5)
tests/unit/product/Domain/Model/Price/PriceTypeCollectionTest.php (5)

13-20: LGTM!

The empty collection test correctly verifies all three key behaviors: count, hasItems, and has. Good baseline test coverage.


22-29: LGTM!

This test correctly validates count and hasItems for a non-empty collection.


31-37: LGTM!

The helper method provides a clean way to create test instances. The use of an anonymous class is appropriate for unit testing.


39-49: LGTM!

This test provides good coverage of the has() method, testing both existing and non-existent types.


51-67: LGTM!

This test thoroughly validates the iterator implementation, checking both the type of yielded items and their order.

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/Domain/Model/Price/Exception/InvalidPriceTypeCollectionException.php (1)

1-11: Consider adding a docblock and optionally a custom error message.

The exception class is syntactically correct and appropriately extends InvalidArgumentException. However, adding a docblock would clarify its purpose and usage for other developers. Additionally, consider whether a custom constructor or factory method providing details about invalid items would improve debugging (e.g., which item failed validation, expected vs. actual type).

Example enhancement:

+/**
+ * Exception thrown when PriceTypeCollection receives invalid items.
+ * All items must implement PriceTypeInterface.
+ */
 class InvalidPriceTypeCollectionException extends InvalidArgumentException
 {
+    public static function forInvalidItem(mixed $item): self
+    {
+        $type = get_debug_type($item);
+        return new self("Invalid item of type '{$type}'. Expected PriceTypeInterface.");
+    }
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a36d665 and ed0e403.

📒 Files selected for processing (3)
  • src/product/Domain/Model/Price/Exception/InvalidPriceTypeCollectionException.php (1 hunks)
  • src/product/Domain/Model/Price/PriceTypeCollection.php (1 hunks)
  • tests/unit/product/Domain/Model/Price/PriceTypeCollectionTest.php (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/product/Domain/Model/Price/PriceTypeCollection.php
  • tests/unit/product/Domain/Model/Price/PriceTypeCollectionTest.php

…sNonPriceType()` method because it makes the domain reason for the exception explicit and self-documenting
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 (3)
src/product/Domain/Model/Price/PriceTypeCollection.php (3)

19-23: Prevent or document duplicate price type names.

When multiple PriceTypeInterface instances share the same name(), array_flip on line 22 silently retains only the last occurrence in $flippedTypeNames. This creates an inconsistency: getIterator() returns all duplicates from $types, but has() and count() won't reflect them accurately (count will include duplicates, but has() treats them as one).

Consider validating uniqueness in the constructor:

     public function __construct(private readonly array $types = [])
     {
         $this->assertAllPriceTypes($types);
+        $names = $this->names();
+        if (count($names) !== count(array_unique($names))) {
+            throw InvalidPriceTypeCollectionException::becauseDuplicateNames($names);
+        }
         $this->flippedTypeNames = array_flip($this->names());
     }

You'll also need to add the corresponding factory method to InvalidPriceTypeCollectionException.


19-19: Add PHPDoc for better IDE support and static analysis.

The $types parameter lacks documentation indicating it expects PriceTypeInterface[]. While runtime validation exists, adding PHPDoc improves IDE autocomplete and static analysis tools.

+    /**
+     * @param PriceTypeInterface[] $types
+     */
     public function __construct(private readonly array $types = [])

34-37: Consider adding return type annotation.

Adding a PHPDoc return type annotation would improve static analysis and IDE support.

+    /**
+     * @return string[]
+     */
     public function names(): array
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ed0e403 and b7a4624.

📒 Files selected for processing (2)
  • src/product/Domain/Model/Price/Exception/InvalidPriceTypeCollectionException.php (1 hunks)
  • src/product/Domain/Model/Price/PriceTypeCollection.php (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/product/Domain/Model/Price/Exception/InvalidPriceTypeCollectionException.php
🧰 Additional context used
🧬 Code graph analysis (1)
src/product/Domain/Model/Price/PriceTypeCollection.php (3)
src/product/Domain/Model/Price/Exception/InvalidPriceTypeCollectionException.php (2)
  • InvalidPriceTypeCollectionException (9-20)
  • becauseContainsNonPriceType (11-19)
tests/unit/product/Domain/Model/Price/PriceTypeCollectionTest.php (2)
  • __construct (35-35)
  • name (36-36)
src/product/Domain/Model/Price/PriceTypeInterface.php (1)
  • name (7-7)
🔇 Additional comments (2)
src/product/Domain/Model/Price/PriceTypeCollection.php (2)

25-32: LGTM! Type validation properly implemented.

The validation method correctly ensures type safety at runtime and uses the appropriate custom exception with a descriptive error message.


39-60: LGTM! Well-implemented collection methods.

The iterator, existence check, and counting methods are correctly implemented:

  • getIterator() properly returns an ArrayIterator with appropriate PHPDoc
  • has() uses efficient O(1) lookup via array_key_exists
  • count() and hasItems() provide clear, straightforward functionality

@SilverFire SilverFire merged commit ab843be into hiqdev:master Nov 12, 2025
2 of 5 checks passed
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