Skip to content

Create custom availability strategy

The product catalog uses an availability strategy to calculate computed availability for a product, deciding whether the customers can order it. The default is based on the product availability and stock amount.

You can replace this logic with a custom strategy to handle specific business scenarios, for example preorders, minimum order quantities, or per-region availability.

The following example implements an availability strategy which allows buying products when they're set as available, without taking their stock into account. You could use it for virtual products or in preorder scenarios.

Create custom availability context

Use an availability context to pass the parameters needed by the strategy to evaluate computed availability. To do it, create a class implementing the AvailabilityContextInterface interface:

1
2
3
4
5
6
7
8
9
<?php declare(strict_types=1);

namespace App\ProductCatalog\Availability;

use Ibexa\Contracts\ProductCatalog\Values\Availability\AvailabilityContextInterface;

final class PurchasableWithoutStockAvailabilityContext implements AvailabilityContextInterface
{
}

Create custom availability strategy

Create a class implementing ProductAvailabilityStrategyInterface:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
<?php declare(strict_types=1);

namespace App\ProductCatalog\Availability;

use Ibexa\Contracts\ProductCatalog\ProductAvailabilityStrategyInterface;
use Ibexa\Contracts\ProductCatalog\Values\Availability\AvailabilityContextInterface;
use Ibexa\Contracts\ProductCatalog\Values\Availability\AvailabilityInterface;
use Ibexa\Contracts\ProductCatalog\Values\ProductInterface;
use Ibexa\ProductCatalog\Local\Persistence\Legacy\ProductAvailability\HandlerInterface;
use Ibexa\ProductCatalog\Local\Repository\Values\Availability;

final class ProductAvailabilityPurchasableWithoutStockStrategy implements ProductAvailabilityStrategyInterface
{
    private HandlerInterface $handler;

    public function __construct(HandlerInterface $handler)
    {
        $this->handler = $handler;
    }

    public function accept(AvailabilityContextInterface $context): bool
    {
        return $context instanceof PurchasableWithoutStockAvailabilityContext;
    }

    public function getProductAvailability(
        ProductInterface $product,
        AvailabilityContextInterface $context
    ): AvailabilityInterface {
        $productAvailability = $this->handler->find($product->getCode());

        $rawAvailableFlag = $productAvailability->isAvailable();
        $stock = $productAvailability->getStock();
        $isInfinite = $productAvailability->isInfinite();

        $computedAvailable = $this->calculateAvailability(
            $rawAvailableFlag,
            $stock,
            $isInfinite,
        );

        return new Availability(
            $product,
            $rawAvailableFlag,
            $computedAvailable,
            $isInfinite,
            $stock,
        );
    }

    private function calculateAvailability(
        bool $rawAvailable,
        ?int $stock,
        bool $isInfinite
    ): bool {
        if ($rawAvailable === false) {
            return false;
        }

        if ($isInfinite) {
            return true;
        }

        if ($stock === null) {
            return true;
        }

        return $stock >= 0;
    }
}

The strategy has two methods:

  • accept() decides if the strategy can handle the provided availability context
  • getProductAvailability() returns an AvailabilityInterface object

When constructing the AvailabilityInterface object, provide the stock amount, the availability flag, and the result of your custom availability logic.

Register strategy as a service

If you're not using autowiring, tag the strategy service with ibexa.product_catalog.availability.strategy:

1
2
3
4
services:
    App\ProductCatalog\Availability\ProductAvailabilityPurchasableWithoutStockStrategy:
        tags:
            - { name: ibexa.product_catalog.availability.strategy }

Use custom context

To evaluate product availability using a custom strategy, pass the custom context as the second argument to ProductAvailabilityServiceInterface::getAvailability():

1
2
3
4
5
6
$availability = $this->productAvailabilityService->getAvailability(
    $product,
    new PurchasableWithoutStockAvailabilityContext()
);

$canBeOrdered = $availability->getComputedAvailability();