Skip to content

Commit

Permalink
Merge pull request #3 from Safemood/class-based-conditions
Browse files Browse the repository at this point in the history
added  Class-Based Conditions
  • Loading branch information
Safemood committed Jan 21, 2024
2 parents 02c32ba + 0e9f00d commit 4cf8e65
Show file tree
Hide file tree
Showing 15 changed files with 180 additions and 20 deletions.
9 changes: 6 additions & 3 deletions README.md
Expand Up @@ -35,13 +35,16 @@ php artisan vendor:publish --tag="discountify-config"
This is the contents of the published config file:

```php
// config/discountify.php
return [
'global_discount' => 0,
'global_tax_rate' => 0,
'condition_namespace' => 'App\\Conditions',
'condition_path' => app_path('Conditions'),
'fields' => [
'quantity' => 'quantity',
'price' => 'price',
'quantity' => 'quantity',
],
'global_discount' => 0,
'global_tax_rate' => 0,
];
```

Expand Down
8 changes: 5 additions & 3 deletions config/discountify.php
@@ -1,10 +1,12 @@
<?php

return [
'global_discount' => 0,
'global_tax_rate' => 0,
'condition_namespace' => 'App\\Conditions',
'condition_path' => app_path('Conditions'),
'fields' => [
'quantity' => 'quantity',
'price' => 'price',
'quantity' => 'quantity',
],
'global_discount' => 0,
'global_tax_rate' => 0,
];
2 changes: 1 addition & 1 deletion database/migrations/create_discountify_table.php.stub
Expand Up @@ -16,4 +16,4 @@ return new class extends Migration
$table->timestamps();
});
}
};
};
Empty file removed resources/views/.gitkeep
Empty file.
2 changes: 1 addition & 1 deletion src/Concerns/HasCalculations.php
Expand Up @@ -31,7 +31,7 @@ public function calculateTotalWithTaxes(?float $globalTaxRate = null): float
/**
* Calculate total with the discount.
*/
public function calculateDiscount(?float $globalDiscount = null): float
public function calculateDiscount(?int $globalDiscount = null): float
{
$this->globalDiscount = $globalDiscount ?? $this->globalDiscount;

Expand Down
59 changes: 55 additions & 4 deletions src/ConditionManager.php
Expand Up @@ -2,8 +2,10 @@

namespace Safemood\Discountify;

use Illuminate\Support\Collection;
use InvalidArgumentException;
use Safemood\Discountify\Contracts\ConditionManagerInterface;
use Symfony\Component\Finder\Finder;

/**
* Class ConditionManager
Expand All @@ -29,13 +31,13 @@ public function add(array $conditions): self
*
* @return $this
*/
public function define(string $slug, callable $condition, float $discountPercentage): self
public function define(string $slug, callable $condition, float $discount): self
{
if (empty($slug)) {
throw new InvalidArgumentException('Slug must be provided.');
}

$this->conditions[] = compact('slug', 'condition', 'discountPercentage');
$this->conditions[] = compact('slug', 'condition', 'discount');

return $this;
}
Expand All @@ -45,9 +47,9 @@ public function define(string $slug, callable $condition, float $discountPercent
*
* @return $this
*/
public function defineIf(string $slug, bool $isAcceptable, float $discountPercentage): self
public function defineIf(string $slug, bool $isAcceptable, float $discount): self
{
return $this->define($slug, fn () => $isAcceptable, $discountPercentage);
return $this->define($slug, fn () => $isAcceptable, $discount);
}

/**
Expand All @@ -57,4 +59,53 @@ public function getConditions(): array
{
return $this->conditions;
}

/**
* Discover condition classes in a given namespace and path.
*
* @param string $namespace
* @param string|null $path
* @return $this
*/
/**
* Discover condition classes in a given namespace and path.
*
* @param string $namespace
* @param string|null $path
* @return $this
*/
public function discover($namespace = 'App\\Conditions', $path = null): self
{
$namespace = str()->finish($namespace, '\\');

$directory = $path ?? base_path('app/Conditions');

if (! is_dir($directory)) {
return $this;
}

Collection::make((new Finder)
->files()
->name('*.php')
->depth(0)
->in($directory))
->each(function ($file) use ($namespace) {
$class = $namespace.$file->getBasename('.php');
$conditionInstance = new $class();

if (method_exists($conditionInstance, '__invoke')) {
$slug = property_exists($conditionInstance, 'slug') ?
$conditionInstance->slug : strtolower(str_replace($namespace, '', $class));

$conditionCallback = fn ($items) => $conditionInstance->__invoke($items);

$discount = property_exists($conditionInstance, 'discount') ?
$conditionInstance->discount : 0;

$this->define($slug, $conditionCallback, $discount);
}
});

return $this;
}
}
11 changes: 11 additions & 0 deletions src/Contracts/ConditionInterface.php
@@ -0,0 +1,11 @@
<?php

namespace Safemood\Discountify\Contracts;

interface ConditionInterface
{
/**
* Invoke the condition logic.
*/
public function __invoke(array $items): bool;
}
4 changes: 2 additions & 2 deletions src/Contracts/ConditionManagerInterface.php
Expand Up @@ -8,9 +8,9 @@ interface ConditionManagerInterface
{
public function add(array $conditions): ConditionManager;

public function define(string $slug, callable $condition, float $discountPercentage): ConditionManager;
public function define(string $slug, callable $condition, float $discount): ConditionManager;

public function defineIf(string $slug, bool $condition, float $discountPercentage): ConditionManager;
public function defineIf(string $slug, bool $condition, float $discount): ConditionManager;

public function getConditions(): array;
}
2 changes: 1 addition & 1 deletion src/Discountify.php
Expand Up @@ -190,7 +190,7 @@ public function total(): float
/**
* Calculate the total with applied discount.
*/
public function totalWithDiscount(?float $globalDiscount = null): float
public function totalWithDiscount(?int $globalDiscount = null): float
{
return $this->calculateDiscount($globalDiscount);
}
Expand Down
12 changes: 10 additions & 2 deletions src/DiscountifyServiceProvider.php
Expand Up @@ -19,8 +19,16 @@ public function configurePackage(Package $package): void

public function packageRegistered()
{
$this->app->singleton(ConditionManager::class, function () {
return new ConditionManager();

$this->app->singleton(ConditionManager::class, function ($app) {
$conditionManager = new ConditionManager();

$conditionManager->discover(
config('discountify.condition_namespace'),
config('discountify.condition_path')
);

return $conditionManager;
});

$this->app->singleton(Discountify::class, function (Application $app) {
Expand Down
4 changes: 2 additions & 2 deletions src/Facades/Condition.php
Expand Up @@ -8,8 +8,8 @@
* Class Condition
*
* @method static \Safemood\Discountify\ConditionManager add(array $conditions)
* @method static \Safemood\Discountify\ConditionManager define(string $slug, callable $condition, float $discountPercentage)
* @method static \Safemood\Discountify\ConditionManager defineIf(string $slug,bool $condition, float $discountPercentage)
* @method static \Safemood\Discountify\ConditionManager define(string $slug, callable $condition, float $discount)
* @method static \Safemood\Discountify\ConditionManager defineIf(string $slug,bool $condition, float $discount)
* @method static array getConditions()
* @method static array getItems()
*
Expand Down
1 change: 0 additions & 1 deletion src/Facades/Discountify.php
Expand Up @@ -15,7 +15,6 @@
* @method static float getGlobalTaxRate()
* @method static array getItems()
* @method static float subtotal()
* @method static float tax()
* @method static float total()
* @method static float totalWithDiscount(?float $globalDiscount = null)
* @method static float tax(?float $globalTaxRate = null)
Expand Down
52 changes: 52 additions & 0 deletions tests/DiscountifyTest.php
Expand Up @@ -6,6 +6,8 @@
use Safemood\Discountify\Facades\Condition;
use Safemood\Discountify\Facades\Discountify as DiscountifyFacade;

use function Orchestra\Testbench\workbench_path;

beforeEach(function () {
$this->items = [
['id' => '1', 'quantity' => 2, 'price' => 50],
Expand Down Expand Up @@ -207,3 +209,53 @@
expect(fn () => Condition::defineIf('', true, 10))
->toThrow(Exception::class, 'Slug must be provided.');
});

it('can auto register condition classes', function () {

Condition::discover(
'Workbench\\App\\Conditions',
workbench_path('app/Conditions')
);

$definedConditions = Condition::getConditions();

expect($definedConditions)
->toBeArray()
->toHaveCount(2)
->each(function ($item) {
$item->toHaveKeys([
'slug',
'condition',
'discount',
]);
$item->slug->toBeString();
$item->condition->toBeInstanceOf(Closure::class);
$item->discount->toBeFloat();
});
});

it('can work correctly with class-based conditions', function () {

$items = [
['id' => '1', 'quantity' => 2, 'price' => 50],
['id' => '2', 'quantity' => 1, 'price' => 100, 'type' => 'special'],
];

Condition::discover(
'Workbench\\App\\Conditions',
workbench_path('app/Conditions')
);

DiscountifyFacade::setItems($items)
->setGlobalTaxRate(19);

$total = DiscountifyFacade::total();
$totalWithDiscount = DiscountifyFacade::totalWithDiscount();
$totalWithTaxes = DiscountifyFacade::tax();
$taxRate = DiscountifyFacade::taxAmout(19);

expect($total)->toBe(floatval(138));
expect($totalWithDiscount)->toBe(floatval(100));
expect($totalWithTaxes)->toBe(floatval(238));
expect($taxRate)->toBe(floatval(38));
});
17 changes: 17 additions & 0 deletions workbench/app/Conditions/MoreThan1ProductsCondition.php
@@ -0,0 +1,17 @@
<?php

namespace Workbench\App\Conditions;

use Safemood\Discountify\Contracts\ConditionInterface;

class MoreThan1ProductsCondition implements ConditionInterface
{
public string $slug = 'more_than_1_products_10';

public int $discount = 10;

public function __invoke(array $items): bool
{
return count($items) > 1;
}
}
17 changes: 17 additions & 0 deletions workbench/app/Conditions/SpecialTypeProductCondition.php
@@ -0,0 +1,17 @@
<?php

namespace Workbench\App\Conditions;

use Safemood\Discountify\Contracts\ConditionInterface;

class SpecialTypeProductCondition implements ConditionInterface
{
public string $slug = 'special_type_product_40';

public int $discount = 40;

public function __invoke(array $items): bool
{
return in_array('special', array_column($items, 'type'));
}
}

0 comments on commit 4cf8e65

Please sign in to comment.