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 37 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
818174f
HP-1751 Create configurations for billing types
VadymHrechukha Nov 28, 2024
7aa8fcc
HP-1751 Created Unit test for InvoiceDescriptionsBuilder class
VadymHrechukha Nov 28, 2024
d8d3535
HP-1751 Create configurations for billing types
VadymHrechukha Dec 6, 2024
59a8d47
HP-1751 Create configurations for billing types
VadymHrechukha Dec 6, 2024
cbc66f2
HP-1751 Create configurations for billing types
VadymHrechukha Dec 9, 2024
1e3d171
HP-1751 created product Type
VadymHrechukha Dec 9, 2024
a2e19d3
HP-1751 added stubs for all types in ServerTariff config
VadymHrechukha Dec 10, 2024
93d7fad
HP-1751 tiny
VadymHrechukha Dec 10, 2024
d678b94
HP-1751 tiny
VadymHrechukha Dec 10, 2024
75b747c
HP-1751 implemented BillingRegistry::getRepresentationsByType() method
VadymHrechukha Dec 10, 2024
f01cc94
HP-1751 fixed the case when we pass two sql for one type in AbstractL…
VadymHrechukha Dec 10, 2024
f7e6961
HP-1751 tiny
VadymHrechukha Dec 12, 2024
7ab9d97
HP-1751 added ability to extend PriceTypeDefinition class
VadymHrechukha Dec 12, 2024
0ab443b
HP-1751 Created QuantityFormatterDefinition for storing both the $for…
VadymHrechukha Dec 17, 2024
3209f16
HP-1751 created formatters
VadymHrechukha Dec 17, 2024
1853b86
HP-1751 configured QuantityFormatter
VadymHrechukha Dec 17, 2024
a9224f6
HP-1751 working on QuantityFormatter classes
VadymHrechukha Dec 18, 2024
3f8e530
HP-1751 created interface for easy get QuantityFormatter from Billing…
VadymHrechukha Dec 18, 2024
6548d55
HP-1751 changed signature of BillingRegistry::createQuantityFormatter…
VadymHrechukha Dec 19, 2024
a1c0436
HP-1751 added consumption config for TariffType
VadymHrechukha Dec 20, 2024
27b606f
HP-1751 added ability to fetch consumption columns and groups into Bi…
VadymHrechukha Dec 22, 2024
be007f7
HP-1751 added consumption classes
VadymHrechukha Dec 22, 2024
ab1f2f5
HP-1751 created BehaviorInterface
VadymHrechukha Dec 23, 2024
dd5e730
HP-1751 added aggregation to PriceTypeDefinition
VadymHrechukha Dec 23, 2024
73bf433
HP-1751 created BehaviorNotFoundException
VadymHrechukha Dec 23, 2024
4c4d0a5
HP-1751 removed Consumption from billing-registry
VadymHrechukha Dec 23, 2024
b414de3
HP-1751 created TariffType class and use it in Behavior
VadymHrechukha Dec 26, 2024
b6893c5
HP-1751 Throw BehaviorNotFoundException instead of returning null in …
VadymHrechukha Dec 27, 2024
f66e578
HP-1751 created ParentNodeDefinitionInterface
VadymHrechukha Dec 30, 2024
63f5327
HP-1751 changed directory of TariffType, Unit and FractionUnit
VadymHrechukha Dec 30, 2024
5c69b42
HP-1751 tiny
VadymHrechukha Dec 30, 2024
49989bf
HP-1751 tiny
VadymHrechukha Dec 30, 2024
1280f4c
HP-1751 added annotation for BillingRegistry::getBehavior() method
VadymHrechukha Dec 31, 2024
10cb39b
HP-1751 Implemented BillingRegistry::getAggregate() method
VadymHrechukha Jan 2, 2025
2a0421b
HP-1751 added ability to check if PriceTypeDefinition has specific be…
VadymHrechukha Jan 3, 2025
78380f0
HP-1751 tiny
VadymHrechukha Jan 6, 2025
da2c506
HP-1751 added TariffTypeDefinition::hasBehavior() method
VadymHrechukha Jan 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/product/AggregateNotFoundException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php declare(strict_types=1);

namespace hiqdev\php\billing\product;

use hiqdev\php\billing\Exception\RuntimeException;

class AggregateNotFoundException extends RuntimeException
{
}
32 changes: 32 additions & 0 deletions src/product/BehaviorCollection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php declare(strict_types=1);

namespace hiqdev\php\billing\product;

use hiqdev\billing\registry\Domain\Model\TariffType;

class BehaviorCollection implements \IteratorAggregate

Check failure on line 7 in src/product/BehaviorCollection.php

View workflow job for this annotation

GitHub Actions / PHP 8.3

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)
{
/** @var BehaviorInterface[] */
private array $behaviors = [];

public function __construct(private readonly TariffType $tariffType)

Check failure on line 12 in src/product/BehaviorCollection.php

View workflow job for this annotation

GitHub Actions / PHP 8.3

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)
{
}

/**
* @return BehaviorInterface[]
*/
public function getIterator(): \Traversable
{
return new \ArrayIterator($this->behaviors);
}

public function attach(BehaviorInterface $behavior): self
{
$behavior->setTariffType($this->tariffType);

$this->behaviors[] = $behavior;

return $this;
}
}
15 changes: 15 additions & 0 deletions src/product/BehaviorInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php declare(strict_types=1);

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"
    }
}


/**
* Empty interface for mark product behavior
*/
interface BehaviorInterface
{
public function setTariffType(TariffType $tariffTypeName): void;

Check failure on line 12 in src/product/BehaviorInterface.php

View workflow job for this annotation

GitHub Actions / PHP 8.3

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)

public function getTariffType(): TariffType;

Check failure on line 14 in src/product/BehaviorInterface.php

View workflow job for this annotation

GitHub Actions / PHP 8.3

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)
}
9 changes: 9 additions & 0 deletions src/product/BehaviorNotFoundException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php declare(strict_types=1);

namespace hiqdev\php\billing\product;

use hiqdev\php\billing\Exception\RuntimeException;

class BehaviorNotFoundException extends RuntimeException
{
}
133 changes: 133 additions & 0 deletions src/product/BillingRegistry.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
<?php declare(strict_types=1);

namespace hiqdev\php\billing\product;

use hiqdev\billing\registry\invoice\RepresentationInterface;
Copy link
Contributor

@coderabbitai coderabbitai bot Jan 3, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix docblock reference for RepresentationInterface.

hiqdev\billing\registry\invoice\RepresentationInterface might be incorrect. If the actual interface resides at a different namespace, update the import statement or docblock accordingly to avoid static analysis issues.

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

Copy link
Member

Choose a reason for hiding this comment

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

@coderabbitai is right.

@VadymHrechukha this repo should not depend on `advancedhosters/billing-registry’

Copy link
Contributor

Choose a reason for hiding this comment

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

@SilverFire Thank you for confirming. I'll remember this for future reviews.


✏️ Learnings added
Learnt from: SilverFire
PR: hiqdev/php-billing#93
File: src/product/BillingRegistry.php:5-5
Timestamp: 2025-01-13T12:20:09.424Z
Learning: The `hiqdev/php-billing` repository should not depend on `advancedhosters/billing-registry`. Use internal interfaces and implementations instead.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

use hiqdev\billing\registry\product\Aggregate;
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;

class BillingRegistry implements BillingRegistryInterface
{
/** @var TariffTypeDefinition[] */
private array $tariffTypes = [];
private bool $locked = false;

public function addTariffType(TariffTypeDefinition $tariffType): void
{
if ($this->locked) {
throw new \RuntimeException("BillingRegistry is locked and cannot be modified.");
}

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

public function lock(): void
{
$this->locked = true;
}

public function priceTypes(): \Generator
{
foreach ($this->tariffTypes as $tariffType) {
foreach ($tariffType->withPrices() as $priceTypeDefinition) {
yield $priceTypeDefinition;
}
}
}

/**
* @param string $representationClass
* @return RepresentationInterface[]

Check failure on line 43 in src/product/BillingRegistry.php

View workflow job for this annotation

GitHub Actions / PHP 8.3

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)
*/
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;
}
Comment on lines +45 to +57
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 +41 to +57
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)


public function createQuantityFormatter(
string $type,
FractionQuantityData $data,

Check failure on line 61 in src/product/BillingRegistry.php

View workflow job for this annotation

GitHub Actions / PHP 8.3

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)
): array {
$type = $this->convertStringTypeToType($type);

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

throw new QuantityFormatterNotFoundException('Quantity formatter not found');
}
Comment on lines +59 to +72
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 createQuantityFormatter method documentation and error handling

The method needs better documentation and more informative error messages.

+/**
+ * Creates a quantity formatter for the specified type.
+ *
+ * @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 for the type
+ * @throws \InvalidArgumentException When input parameters are invalid
+ */
 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)
+    );
 }
📝 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 = $this->convertStringTypeToType($type);
foreach ($this->priceTypes() as $priceTypeDefinition) {
if ($priceTypeDefinition->hasType($type)) {
return $priceTypeDefinition->createQuantityFormatter($data);
}
}
throw new QuantityFormatterNotFoundException('Quantity formatter not found');
}
/**
* Creates a quantity formatter for the specified type.
*
* @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 for the type
* @throws \InvalidArgumentException When input parameters are invalid
*/
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(
sprintf("Quantity formatter not found for type '%s'", $type)
);
}
🧰 Tools
🪛 GitHub Check: PHP 8.3

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


private function convertStringTypeToType(string $type): TypeInterface
{
return Type::anyId($type);
}

/**
* @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) {

Check failure on line 105 in src/product/BillingRegistry.php

View workflow job for this annotation

GitHub Actions / PHP 8.3

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)
if ($behavior instanceof $behaviorClassWrapper) {
yield $behavior;
}
}
}

foreach ($this->priceTypes() as $priceTypeDefinition) {
foreach ($priceTypeDefinition->withBehaviors() as $behavior) {
if ($behavior instanceof $behaviorClassWrapper) {
yield $behavior;
}
}
}
}
Comment on lines +79 to +119
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)


public function getAggregate(string $type): Aggregate

Check failure on line 121 in src/product/BillingRegistry.php

View workflow job for this annotation

GitHub Actions / PHP 8.3

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)
{
$type = $this->convertStringTypeToType($type);

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

throw new AggregateNotFoundException('Aggregate was not found');
}
Comment on lines +121 to +132
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)

}
11 changes: 11 additions & 0 deletions src/product/BillingRegistryInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php declare(strict_types=1);

namespace hiqdev\php\billing\product;

interface BillingRegistryInterface
{
/**
* @return PriceTypeDefinition[]
*/
public function priceTypes(): \Generator;
}
8 changes: 8 additions & 0 deletions src/product/DocumentRepresentationInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php declare(strict_types=1);

namespace hiqdev\php\billing\product;

interface DocumentRepresentationInterface
{

}
23 changes: 23 additions & 0 deletions src/product/InvoiceDescriptionsBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php declare(strict_types=1);

namespace hiqdev\php\billing\product;

class InvoiceDescriptionsBuilder
{
private BillingRegistry $registry;

public function __construct(BillingRegistry $registry)
{
$this->registry = $registry;
}

public function build(): array
{
$descriptions = [];
foreach ($this->registry->priceTypes() as $priceType) {
$descriptions[] = $priceType->representInvoice();
}
Comment on lines +17 to +19
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


return $descriptions;
}
Comment on lines +14 to +22
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
);
}
}

}
10 changes: 10 additions & 0 deletions src/product/ParentNodeDefinitionInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php declare(strict_types=1);

namespace hiqdev\php\billing\product;

interface ParentNodeDefinitionInterface
{
public function withBehaviors(): BehaviorCollection;

public function hasBehavior(string $behaviorClassName): bool;
}
Loading
Loading