Skip to content

Commit

Permalink
- Added PromotionRule and PromotionRuleTypes with registry
Browse files Browse the repository at this point in the history
- Added CartQuantity rule
- Test
  • Loading branch information
kedves committed Jul 11, 2024
1 parent 3bb84ec commit 9f5edd6
Show file tree
Hide file tree
Showing 20 changed files with 629 additions and 1 deletion.
2 changes: 2 additions & 0 deletions src/Promotion/Contracts/Promotion.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,6 @@
interface Promotion
{
public function isValid(?\DateTimeInterface $at = null): bool;

public function addRule(PromotionRuleType $ruleType): self;
}
14 changes: 14 additions & 0 deletions src/Promotion/Contracts/PromotionRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

declare(strict_types=1);

namespace Vanilo\Promotion\Contracts;

use Vanilo\Contracts\Configurable;

interface PromotionRule extends Configurable
{
public function getRuleType(): PromotionRuleType;

public function isRuleTypPassing(object $subject): bool;
}
22 changes: 22 additions & 0 deletions src/Promotion/Contracts/PromotionRuleType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace Vanilo\Promotion\Contracts;

use Nette\Schema\Schema;

interface PromotionRuleType
{
public static function getName(): string;

public static function getID(): string;

public function isPassing(object $subject): bool;

public function getSchema(): ?Schema;

public function setConfiguration(array $configuration): self;

public function getConfiguration(): ?array;
}
11 changes: 11 additions & 0 deletions src/Promotion/Exceptions/InexistentRuleException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace Vanilo\Promotion\Exceptions;

use RuntimeException;

class InexistentRuleException extends RuntimeException
{
}
1 change: 1 addition & 0 deletions src/Promotion/Models/Coupon.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

/**
* @property int $id
* @property int $promotion_id
* @property string $code
* @property ?int $usage_limit
* @property ?int $per_customer_usage_limit
Expand Down
16 changes: 16 additions & 0 deletions src/Promotion/Models/Promotion.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Vanilo\Promotion\Contracts\Promotion as PromotionContract;
use Vanilo\Promotion\Contracts\PromotionRuleType;

/**
* @property int $id
Expand Down Expand Up @@ -47,6 +48,11 @@ public function coupons(): HasMany
return $this->hasMany(CouponProxy::modelClass());
}

public function rules(): HasMany
{
return $this->hasMany(PromotionRuleProxy::modelClass());
}

public function isValid(?\DateTimeInterface $at = null): bool
{
if ($this->usage_count >= $this->usage_limit) {
Expand All @@ -63,4 +69,14 @@ public function isValid(?\DateTimeInterface $at = null): bool

return $this->ends_at->isFuture();
}

public function addRule(PromotionRuleType $ruleType): PromotionContract
{
$this->rules()->create([
'type' => $ruleType::getID(),
'configuration' => $ruleType->getConfiguration(),
]);

return $this;
}
}
49 changes: 49 additions & 0 deletions src/Promotion/Models/PromotionRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

declare(strict_types=1);

namespace Vanilo\Promotion\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Vanilo\Promotion\Contracts\Promotion;
use Vanilo\Promotion\Contracts\PromotionRule as PromotionRuleContract;
use Vanilo\Promotion\Contracts\PromotionRuleType;
use Vanilo\Promotion\PromotionRuleTypes;
use Vanilo\Support\Traits\ConfigurableModel;
use Vanilo\Support\Traits\ConfigurationHasNoSchema;

/**
* @property int $id
* @property int $promotion_id
* @property string $type
* @property ?array $configuration
*
* @property Promotion $promotion
*/
class PromotionRule extends Model implements PromotionRuleContract
{
use ConfigurableModel;
use ConfigurationHasNoSchema;

protected $guarded = ['id', 'created_at', 'updated_at'];

protected $casts = [
'configuration' => 'array',
];

public function promotion(): BelongsTo
{
return $this->belongsTo(PromotionProxy::modelClass());
}

public function getRuleType(): PromotionRuleType
{
return PromotionRuleTypes::make($this->type)->setConfiguration($this->configuration);
}

public function isRuleTypPassing(object $subject): bool
{
return $this->getRuleType()->isPassing($subject);
}
}
11 changes: 11 additions & 0 deletions src/Promotion/Models/PromotionRuleProxy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace Vanilo\Promotion\Models;

use Konekt\Concord\Proxies\ModelProxy;

class PromotionRuleProxy extends ModelProxy
{
}
72 changes: 72 additions & 0 deletions src/Promotion/PromotionRuleTypes.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

declare(strict_types=1);

namespace Vanilo\Promotion;

use Vanilo\Promotion\Contracts\PromotionRuleType;
use Vanilo\Promotion\Exceptions\InexistentRuleException;

final class PromotionRuleTypes
{
private static array $registry = [];

public static function register(string $id, string $class)
{
if (array_key_exists($id, self::$registry)) {
return;
}

if (!in_array(PromotionRuleType::class, class_implements($class))) {
throw new \InvalidArgumentException(
sprintf(
'The class you are trying to register (%s) as promotion rule, '.
'must implement the %s interface.',
$class,
PromotionRuleType::class
)
);
}

self::$registry[$id] = $class;
}

public static function make(string $id): PromotionRuleType
{
$gwClass = self::getClass($id);

if (null === $gwClass) {
throw new InexistentRuleException(
"No rule is registered with the id `$id`."
);
}

return app()->make($gwClass);
}

public static function reset(): void
{
self::$registry = [];
}

public static function getClass(string $id): ?string
{
return self::$registry[$id] ?? null;
}

public static function ids(): array
{
return array_keys(self::$registry);
}

public static function choices(): array
{
$result = [];

foreach (self::$registry as $type => $class) {
$result[$type] = $class::getName();
}

return $result;
}
}
11 changes: 11 additions & 0 deletions src/Promotion/Providers/ModuleServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,22 @@
use Konekt\Concord\BaseModuleServiceProvider;
use Vanilo\Promotion\Models\Coupon;
use Vanilo\Promotion\Models\Promotion;
use Vanilo\Promotion\Models\PromotionRule;
use Vanilo\Promotion\Rules\CartQuantity;
use Vanilo\Promotion\PromotionRuleTypes;

class ModuleServiceProvider extends BaseModuleServiceProvider
{
protected $models = [
Promotion::class,
Coupon::class,
PromotionRule::class,
];

public function boot()
{
parent::boot();

PromotionRuleTypes::register(CartQuantity::getID(), CartQuantity::class);
}
}
66 changes: 66 additions & 0 deletions src/Promotion/Rules/CartQuantity.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

declare(strict_types=1);

namespace Vanilo\Promotion\Rules;

use Nette\Schema\Expect;
use Nette\Schema\Processor;
use Nette\Schema\Schema;
use Vanilo\Cart\Contracts\Cart;
use Vanilo\Promotion\Contracts\PromotionRuleType;

class CartQuantity implements PromotionRuleType
{
private ?array $configuration = null;

public static function getName(): string
{
return __('Cart quantity');
}

public static function getID(): string
{
return 'cart_quantity';
}

public function setConfiguration(array $configuration): self
{
if ($this->getSchema()) {
$configuration = (new Processor())->process($this->getSchema(), $configuration);
}

$this->configuration = (array)$configuration;

return $this;
}

public function getConfiguration(): ?array
{
$configuration = $this->configuration;

if ($this->getSchema()) {
$configuration = (new Processor())->process($this->getSchema(), $configuration);
}

return (array)$configuration;
}

public function getSchema(): ?Schema
{
return Expect::structure(['count' => Expect::int(0)->required()]);
}

public function isPassing(object $subject): bool
{
if (!$subject instanceof Cart) {
throw new \InvalidArgumentException('Subject must be an instance of Vanilo\Cart\Contracts\Cart');
}

if (!$this->getConfiguration()) {
return false;
}

return $subject->itemCount() <= $this->configuration['count'];
}
}
2 changes: 1 addition & 1 deletion src/Promotion/Tests/CouponTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public function can_return_coupon_by_code()
}

/** @test */
public function cat_return_promotion()
public function can_return_promotion()
{
$promotion = PromotionFactory::new(['name' => 'Test promo'])->create();
$coupon = CouponFactory::new(['promotion_id' => $promotion->id])->create();
Expand Down
41 changes: 41 additions & 0 deletions src/Promotion/Tests/Examples/CartTotalRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace Vanilo\Promotion\Tests\Examples;

use Nette\Schema\Schema;
use Vanilo\Promotion\Contracts\PromotionRuleType;

class CartTotalRule implements PromotionRuleType
{
public static function getName(): string
{
return 'Cart Total';
}

public static function getID(): string
{
// TODO: Implement getID() method.
}

public function isPassing(object $subject): bool
{
// TODO: Implement isPassing() method.
}

public function getSchema(): ?Schema
{
// TODO: Implement getSchema() method.
}

public function setConfiguration(array $configuration): PromotionRuleType
{
// TODO: Implement setConfiguration() method.
}

public function getConfiguration(): ?array
{
// TODO: Implement getConfiguration() method.
}
}
Loading

0 comments on commit 9f5edd6

Please sign in to comment.