Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
45 changes: 26 additions & 19 deletions src/product/Application/BillingRegistryService.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace hiqdev\php\billing\product\Application;

use Generator;
use hiqdev\php\billing\product\AggregateInterface;
use hiqdev\php\billing\product\behavior\BehaviorInterface;
use hiqdev\php\billing\product\behavior\BehaviorNotFoundException;
Expand All @@ -25,17 +26,17 @@ public function __construct(private readonly BillingRegistryInterface $registry)
public function getRepresentationsByType(string $representationClass): array
{
if (!class_exists($representationClass) && !interface_exists($representationClass)) {
throw new InvalidRepresentationException("Class '$representationClass' does not exist");
throw InvalidRepresentationException::make("Representation does not exist", [
'representationClass' => $representationClass,
]);
}

if (class_exists($representationClass)
&& !is_subclass_of($representationClass, RepresentationInterface::class)
) {
throw new InvalidBehaviorException(
sprintf(
'Representation class "%s" does not implement RepresentationInterface',
$representationClass,
)
throw InvalidBehaviorException::make('Representation class does not implement RepresentationInterface', [
'representationClass' => $representationClass,
]
);
Comment on lines +37 to 40

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Wrong exception type for representation contract violation.
Should use InvalidRepresentationException, not InvalidBehaviorException.

Apply this diff:

-            throw InvalidBehaviorException::make('Representation class does not implement RepresentationInterface', [
+            throw InvalidRepresentationException::make('Representation class does not implement RepresentationInterface', [
                 'representationClass' => $representationClass,
             ]
         );

Optional hardening (outside selected lines): validate interfaces too, not only classes:

// replace the preceding condition with:
if ((class_exists($representationClass) || interface_exists($representationClass))
    && !is_subclass_of($representationClass, RepresentationInterface::class)
) {
    throw InvalidRepresentationException::make(
        'Representation class does not implement RepresentationInterface',
        ['representationClass' => $representationClass]
    );
}
🤖 Prompt for AI Agents
In src/product/Application/BillingRegistryService.php around lines 37 to 40, the
code throws InvalidBehaviorException for a representation contract violation;
replace that with InvalidRepresentationException and update the thrown call
parameters accordingly. Also (optional/hardening) adjust the condition that
checks the representation type to allow interfaces as well as classes by
checking class_exists OR interface_exists and then confirming the type
implements/extends RepresentationInterface before throwing
InvalidRepresentationException with the same message and representationClass
context.

}

Expand All @@ -59,20 +60,25 @@ public function getTariffTypeDefinitionByTariffName(string $tariffName): TariffT
}
}

throw new TariffTypeDefinitionNotFoundException('Tariff type definition was not found');
throw TariffTypeDefinitionNotFoundException::make('TariffTypeDefinition was not found', [
'tariffName' => $tariffName,
]);
}

public function getBehavior(string $type, string $behaviorClassWrapper): BehaviorInterface
{
if (!class_exists($behaviorClassWrapper)) {
throw new InvalidBehaviorException(
sprintf('Behavior class "%s" does not exist', $behaviorClassWrapper)
throw InvalidBehaviorException::make( 'Behavior class does not exist', [
'behavior' => $behaviorClassWrapper,
]
);
}

if (!is_subclass_of($behaviorClassWrapper, BehaviorInterface::class)) {
throw new InvalidBehaviorException(
sprintf('Behavior class "%s" does not implement BehaviorInterface', $behaviorClassWrapper)
throw InvalidBehaviorException::make(
'Behavior class does not implement BehaviorInterface', [
'behavior' => $behaviorClassWrapper,
]
);
}

Expand All @@ -88,8 +94,10 @@ public function getBehavior(string $type, string $behaviorClassWrapper): Behavio
}
}

throw new BehaviorNotFoundException(
sprintf('Behavior of class "%s" not found for type "%s"', $behaviorClassWrapper, $type),
throw BehaviorNotFoundException::make('Behavior was not found', [
'behavior' => $behaviorClassWrapper,
'type' => $type,
],
);
}

Expand All @@ -111,7 +119,7 @@ private function findBehaviorInPriceType(
return null;
}

public function getBehaviors(string $behaviorClassWrapper): \Generator
public function getBehaviors(string $behaviorClassWrapper): Generator
{
foreach ($this->registry->getTariffTypeDefinitions() as $tariffTypeDefinition) {
foreach ($tariffTypeDefinition->withBehaviors() as $behavior) {
Expand Down Expand Up @@ -145,13 +153,12 @@ public function getPriceTypeDefinitionByPriceTypeName(string $typeName): PriceTy
}
}

throw new PriceTypeDefinitionNotFoundException(sprintf(
'PriceTypeDefinition was not found for %s type',
$typeName,
));
throw PriceTypeDefinitionNotFoundException::make('PriceTypeDefinition was not found', [
'type' => $typeName,
]);
}

public function findPriceTypeDefinitionsByBehavior(string $behaviorClassWrapper): \Generator
public function findPriceTypeDefinitionsByBehavior(string $behaviorClassWrapper): Generator
{
foreach ($this->registry->priceTypes() as $priceTypeDefinition) {
if ($priceTypeDefinition->hasBehavior($behaviorClassWrapper)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

namespace hiqdev\php\billing\product\Exception;

use hidev\exception\HasContext;
use hidev\exception\HasContextInterface;
use hiqdev\php\billing\Exception\RuntimeException;

class PriceTypeDefinitionNotFoundException extends RuntimeException
class PriceTypeDefinitionNotFoundException extends RuntimeException implements HasContextInterface
{

use HasContext;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

namespace hiqdev\php\billing\product\Exception;

use hidev\exception\HasContext;
use hidev\exception\HasContextInterface;
use hiqdev\php\billing\Exception\RuntimeException;

class TariffTypeDefinitionNotFoundException extends RuntimeException
class TariffTypeDefinitionNotFoundException extends RuntimeException implements HasContextInterface
{

use HasContext;
}
5 changes: 4 additions & 1 deletion src/product/behavior/BehaviorNotFoundException.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

namespace hiqdev\php\billing\product\behavior;

use hidev\exception\HasContext;
use hidev\exception\HasContextInterface;
use hiqdev\php\billing\Exception\RuntimeException;

class BehaviorNotFoundException extends RuntimeException
class BehaviorNotFoundException extends RuntimeException implements HasContextInterface
{
use HasContext;
Comment on lines +9 to +11

Copy link
Copy Markdown
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

Verification: ensure the trait/interface and factory usage are wired in the repo.

  • Confirm hidev/exception is required.
  • Ensure callers consistently use static factories (e.g., ::make(...)) and no lingering direct “new …Exception(...)” remain if you intend uniform payloads.

Run:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "1) Check hidev/exception dependency"
python - <<'PY'
import json,sys,os
p='composer.json'
if not os.path.exists(p):
    print('composer.json not found'); sys.exit(0)
d=json.load(open(p))
req = {k.lower():v for k,v in d.get('require',{}).items()}
dev = {k.lower():v for k,v in d.get('require-dev',{}).items()}
for sect,name in ((req,'require'), (dev,'require-dev')):
    if 'hidev/exception' in sect:
        print(f'Found hidev/exception in {name}:', sect['hidev/exception']); break
else:
    print('MISSING hidev/exception in require/require-dev')
PY

echo -e "\n2) Call sites using ::make(...)"
rg -nP --no-heading -C1 '\b(InvalidRepresentationException|TariffTypeDefinitionNotFoundException|PriceTypeDefinitionNotFoundException|InvalidBehaviorException|BehaviorNotFoundException)::make\s*\(' || true

echo -e "\n3) Direct instantiations (review and convert to factories if desired)"
rg -nP --no-heading -C1 '\bnew\s+(InvalidRepresentationException|TariffTypeDefinitionNotFoundException|PriceTypeDefinitionNotFoundException|InvalidBehaviorException|BehaviorNotFoundException)\s*\(' || true

Length of output: 3454


Add hidev/exception dependency and unify exception instantiation

The HasContext trait won’t work without the hidev/exception package, and there’s still one direct new …Exception() call that should be converted to use the static factory.

• composer.json
– Currently missing hidev/exception in both require and require-dev. Add the package (e.g. "hidev/exception": "^<version>") to the appropriate section.
• Exception instantiation in src/product/invoice/Representation.php:19
– Replace
php throw new InvalidRepresentationException('Representation SQL cannot be empty.');
with
php throw InvalidRepresentationException::make('Representation SQL cannot be empty.');
• BehaviorNotFoundException usage
– Call sites (e.g. in BillingRegistryService) correctly use BehaviorNotFoundException::make(...); no direct new BehaviorNotFoundException() instances remain.

🤖 Prompt for AI Agents
In src/product/behavior/BehaviorNotFoundException.php around lines 9-11, the
class uses the HasContext trait which requires the hidev/exception package; add
"hidev/exception":"^<version>" to composer.json (in require or require-dev as
appropriate) and run composer update so HasContext methods are available; in
src/product/invoice/Representation.php line 19 replace the direct instantiation
throw new InvalidRepresentationException('Representation SQL cannot be empty.');
with the static factory throw
InvalidRepresentationException::make('Representation SQL cannot be empty.'); and
audit the codebase to ensure no other direct new ...Exception() calls remain for
exceptions that provide a ::make factory (e.g., BehaviorNotFoundException),
converting them to the static factory where found.

}
5 changes: 4 additions & 1 deletion src/product/behavior/InvalidBehaviorException.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

namespace hiqdev\php\billing\product\behavior;

use hidev\exception\HasContext;
use hidev\exception\HasContextInterface;
use InvalidArgumentException;

class InvalidBehaviorException extends InvalidArgumentException
class InvalidBehaviorException extends InvalidArgumentException implements HasContextInterface
{
use HasContext;
}
6 changes: 4 additions & 2 deletions src/product/invoice/InvalidRepresentationException.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

namespace hiqdev\php\billing\product\invoice;

use hidev\exception\HasContext;
use hidev\exception\HasContextInterface;
use InvalidArgumentException;

class InvalidRepresentationException extends InvalidArgumentException
class InvalidRepresentationException extends InvalidArgumentException implements HasContextInterface
{

use HasContext;
}