diff --git a/src/DateRules/DateRangeRule.php b/src/DateRules/DateRangeRule.php index d31d976..fccf218 100644 --- a/src/DateRules/DateRangeRule.php +++ b/src/DateRules/DateRangeRule.php @@ -6,20 +6,13 @@ class DateRangeRule extends DateRule { - public ?Carbon $from; public ?Carbon $to; - public function __construct(array $nodeData, array $rules) - { - parent::__construct($nodeData, $rules); - - } - /** * @inheritDoc */ - function validate(Carbon $date): bool + public function validate(Carbon $date): bool { if (isset($this->from) && $date->lt($this->from)) { return false; @@ -28,6 +21,7 @@ function validate(Carbon $date): bool if (isset($this->to) && $date->gte($this->to)) { return false; } + return true; } } diff --git a/src/DateRules/DateRule.php b/src/DateRules/DateRule.php index 62341c2..46592af 100644 --- a/src/DateRules/DateRule.php +++ b/src/DateRules/DateRule.php @@ -2,13 +2,21 @@ namespace Netflex\RuleBuilder\DateRules; +use ReflectionClass; +use ReflectionException; + use Carbon\Carbon; +use Illuminate\Contracts\Validation\Rule; +use Netflex\RuleBuilder\RuleCollection; use Netflex\RuleBuilder\ExplainerNode; use Netflex\RuleBuilder\InvalidConfigurationException; use Netflex\RuleBuilder\UnknownNodeType; abstract class DateRule { + /** + * @var DateRule[] + */ public static array $rules = [ 'group' => GroupDateRule::class, 'dayOfWeek' => DayOfWeekDateRule::class, @@ -16,46 +24,70 @@ abstract class DateRule 'not' => NotDateRule::class ]; + /** @var string */ public string $name; - public array $children = []; + + /** @var RuleCollection|null */ + public ?RuleCollection $children; /** + * @param array $nodeData + * @param DateRule[] $rules * @throws UnknownNodeType */ public function __construct(array $nodeData, array $rules) { - $r = new \ReflectionClass($this); + $reflectionClass = new ReflectionClass($this); foreach ($nodeData as $key => $value) { try { - $prop = $r->getProperty($key); - if ($prop->hasType() && $prop->getType()->getName() == Carbon::class) { - $value = $value ? Carbon::parse($value) : null; - } else if($prop->hasType() && $prop->getType()->getName() == DateRule::class) { - $value = $value ? DateRule::parse($value, $rules) : null; + $property = $reflectionClass->getProperty($key); + + if ($property->hasType()) { + $type = $property->getType()->getName(); + + if (is_subclass_of($type, Carbon::class) || $type === Carbon::class) { + $value = $value ? Carbon::parse($value) : null; + } + + if (is_subclass_of($type, DateRule::class) || $type === DateRule::class) { + $value = $value ? DateRule::parse($value, $rules) : null; + } + + if (is_subclass_of($type, RuleCollection::class) || $type === RuleCollection::class) { + $value = ($value ? RuleCollection::make($value) : RuleCollection::make([]))->map(function ($child) use ($rules) { + if (!($child instanceof DateRule)) { + return static::parse($child, $rules); + } + + return $child; + }); + } } - } catch (\ReflectionException $e) { + } catch (ReflectionException $e) { + // Unknown property, ignore it + continue; } - $this->{$key} = $value; - } - foreach ($this->children as $key => $child) { - $this->children[$key] = static::parse($child, $rules); + $this->{$key} = $value; } } /** + * @param array $node + * @param DateRule[]|null $rules * @throws UnknownNodeType + * @return DateRule */ public static function parse(array $node, ?array $rules = null): self { $rules = $rules ?? static::$rules; - if ($rule = $rules[$node['type']]) { + if ($rule = $rules[$node['type']] ?: null) { return new $rule($node, $rules); - } else { - throw new UnknownNodeType("DateRule of type {$node['type']} is not known"); } + + throw new UnknownNodeType("DateRule of type {$node['type']} is not known"); } /** @@ -65,7 +97,7 @@ public static function parse(array $node, ?array $rules = null): self * @return ExplainerNode * @throws InvalidConfigurationException */ - function explain(Carbon $date): ExplainerNode + public function explain(Carbon $date): ExplainerNode { $children = []; @@ -84,9 +116,12 @@ function explain(Carbon $date): ExplainerNode * @return bool * @throws InvalidConfigurationException */ - abstract function validate(Carbon $date): bool; + abstract public function validate(Carbon $date): bool; - function settings(): array + /** + * @return array + */ + public function settings(): array { return [ 'name' => $this->name diff --git a/src/DateRules/DayOfWeekDateRule.php b/src/DateRules/DayOfWeekDateRule.php index dae5505..c09e79b 100644 --- a/src/DateRules/DayOfWeekDateRule.php +++ b/src/DateRules/DayOfWeekDateRule.php @@ -6,12 +6,13 @@ class DayOfWeekDateRule extends DateRule { + /** @var int[] */ public array $days; /** * @inheritDoc */ - function validate(Carbon $date): bool + public function validate(Carbon $date): bool { return collect($this->days)->some(function (int $day) use ($date) { return $date->isDayOfWeek($day); diff --git a/src/DateRules/GroupDateRule.php b/src/DateRules/GroupDateRule.php index 675f86a..2ab82e9 100644 --- a/src/DateRules/GroupDateRule.php +++ b/src/DateRules/GroupDateRule.php @@ -3,48 +3,48 @@ namespace Netflex\RuleBuilder\DateRules; use Carbon\Carbon; -use Illuminate\Contracts\Validation\DataAwareRule; -use Netflex\RuleBuilder\ExplainerNode; +use Netflex\RuleBuilder\RuleCollection; use Netflex\RuleBuilder\InvalidConfigurationException; /** * Validates a group of rules against the same date and returns a unified answer * - * $count is required, can be "all", "any" or an integer that determines how many rules must pass the check for the + * $count is required, can be 'all', 'any' or an integer that determines how many rules must pass the check for the * group value to be true */ class GroupDateRule extends DateRule { - + /** @var string|int */ public $count; - public array $children; + /** @var RuleCollection */ + public ?RuleCollection $children; /** * @inheritDoc * @throws InvalidConfigurationException */ - function validate(Carbon $date): bool + public function validate(Carbon $date): bool { - if($this->count == "all") { + if ($this->count === 'all') { return collect($this->children) - ->every(function(DateRule $rule) use ($date) { + ->every(function (DateRule $rule) use ($date) { return $rule->validate($date); }); } - if($this->count == "any") { + if ($this->count === 'any') { return collect($this->children) - ->first(function(DateRule $rule) use ($date) { + ->first(function (DateRule $rule) use ($date) { return $rule->validate($date); }) != null; } - if(is_int($this->count)) { + if (is_int($this->count)) { $ch = collect($this->children); $minLevel = min($ch->count(), $this->count); - return $ch->filter(function(DateRule $rule) use ($date) { + return $ch->filter(function (DateRule $rule) use ($date) { return $rule->validate($date); })->count() >= $minLevel; } @@ -52,7 +52,10 @@ function validate(Carbon $date): bool throw new InvalidConfigurationException("[count] is not a valid value on rule with name {$this->name}. Must be 'any', 'all' or int"); } - function settings(): array + /** + * @return array + */ + public function settings(): array { return array_merge(parent::settings(), [ 'count' => $this->count diff --git a/src/DateRules/NotDateRule.php b/src/DateRules/NotDateRule.php index edc24f2..5aed795 100644 --- a/src/DateRules/NotDateRule.php +++ b/src/DateRules/NotDateRule.php @@ -3,16 +3,16 @@ namespace Netflex\RuleBuilder\DateRules; use Carbon\Carbon; -use Netflex\RuleBuilder\InvalidConfigurationException; class NotDateRule extends DateRule { - + /** @var DateRule */ public DateRule $child; + /** * @inheritDoc */ - function validate(Carbon $date): bool + public function validate(Carbon $date): bool { return !$this->child->validate($date); } diff --git a/src/ExplainerNode.php b/src/ExplainerNode.php index 2f1e84f..dd994b3 100644 --- a/src/ExplainerNode.php +++ b/src/ExplainerNode.php @@ -4,12 +4,17 @@ class ExplainerNode { - + /** @var bool */ public bool $result; + + /** @var array */ public array $children; + + /** @var array */ public array $settings; - function __construct(bool $result, array $settings = [], array $children = []) { + public function __construct(bool $result, array $settings = [], array $children = []) + { $this->result = $result; $this->settings = $settings; $this->children = $children; diff --git a/src/InvalidConfigurationException.php b/src/InvalidConfigurationException.php index 6a7bc92..c840085 100644 --- a/src/InvalidConfigurationException.php +++ b/src/InvalidConfigurationException.php @@ -2,12 +2,8 @@ namespace Netflex\RuleBuilder; -use Throwable; +use Exception; -class InvalidConfigurationException extends \Exception +class InvalidConfigurationException extends Exception { - function __construct($message = "", $code = 0, Throwable $previous = null) - { - parent::__construct($message, 0, null); - } } diff --git a/src/RuleCollection.php b/src/RuleCollection.php new file mode 100644 index 0000000..eecc468 --- /dev/null +++ b/src/RuleCollection.php @@ -0,0 +1,9 @@ +_bootstrapRule($from, $to); $this->assertTrue($rule->validate($from)); } - public function testNotIncludesEndDate() { + public function testNotIncludesEndDate() + { - $from = \Carbon\Carbon::parse("2021-02-01"); - $to = \Carbon\Carbon::parse("2021-03-01"); + $from = Carbon::parse('2021-02-01'); + $to = Carbon::parse('2021-03-01'); $rule = $this->_bootstrapRule($from, $to); $this->assertFalse($rule->validate($to)); } - public function testMissingFromMeansAnyPreviousDate() { - - $from = \Carbon\Carbon::parse("2021-02-01"); - $to = \Carbon\Carbon::parse("2021-03-01"); + public function testMissingFromMeansAnyPreviousDate() + { + $to = Carbon::parse('2021-03-01'); $rule = $this->_bootstrapRule(null, $to); - - $this->assertFalse($rule->validate($to->copy()->addMillennia()), ""); - $this->assertFalse($rule->validate($to->copy()), ""); - $this->assertTrue($rule->validate($to->copy()->subMillennia()), "Date between 1970 and to-date passes"); - - + $this->assertFalse($rule->validate($to->copy()->addMillennia()), ''); + $this->assertFalse($rule->validate($to->copy()), ''); + $this->assertTrue($rule->validate($to->copy()->subMillennia()), 'Date between 1970 and to-date passes'); } - public function testMissingToMeansAnyDateInFuture() { - - $from = \Carbon\Carbon::parse("2021-02-01"); - $to = \Carbon\Carbon::parse("2021-03-01"); + public function testMissingToMeansAnyDateInFuture() + { + $from = Carbon::parse('2021-02-01'); $rule = $this->_bootstrapRule($from, null); - $this->assertTrue($rule->validate($from->copy()->addMillennia()), "Any date in future passes"); - $this->assertTrue($rule->validate($from->copy()), "Same date/time passes"); - $this->assertFalse($rule->validate($from->copy()->subMillennia()), "Any date before to fails"); + $this->assertTrue($rule->validate($from->copy()->addMillennia()), 'Any date in future passes'); + $this->assertTrue($rule->validate($from->copy()), 'Same date/time passes'); + $this->assertFalse($rule->validate($from->copy()->subMillennia()), 'Any date before to fails'); } - private function _bootstrapRule(?\Carbon\Carbon $from, ?\Carbon\Carbon $to): \Netflex\RuleBuilder\DateRules\DateRangeRule { - return new \Netflex\RuleBuilder\DateRules\DateRangeRule(['name' => "yay", 'from' => $from ? $from->toIso8601String() : null, 'to' => $to ? $to->toIso8601String() : null], []); + private function _bootstrapRule(?Carbon $from, ?Carbon $to): DateRangeRule + { + return new DateRangeRule(['name' => 'yay', 'from' => $from ? $from->toIso8601String() : null, 'to' => $to ? $to->toIso8601String() : null], []); } } diff --git a/tests/DateRuleTest.php b/tests/DateRuleTest.php index faa26c6..f51464a 100644 --- a/tests/DateRuleTest.php +++ b/tests/DateRuleTest.php @@ -1,52 +1,63 @@ "test1", - 'type' => "group", - 'count' => "any", + 'name' => 'test1', + 'type' => 'group', + 'count' => 'any', 'children' => [] ]; $this->assertInstanceOf( - \Netflex\RuleBuilder\DateRules\GroupDateRule::class, - \Netflex\RuleBuilder\DateRules\DateRule::parse($data) + GroupDateRule::class, + DateRule::parse($data) ); } - public function testResolvingDateRange(): void { - $rule = \Netflex\RuleBuilder\DateRules\DateRule::parse([ - 'name' => "dateRule", - 'type' => "dateRange", - 'from' => "2021-01-01", - 'to' => "2022-01-01" + public function testResolvingDateRange(): void + { + $rule = DateRule::parse([ + 'name' => 'dateRule', + 'type' => 'dateRange', + 'from' => '2021-01-01', + 'to' => '2022-01-01' ]); $this->assertInstanceOf( - \Netflex\RuleBuilder\DateRules\DateRangeRule::class, - $rule); + DateRangeRule::class, + $rule + ); $this->assertInstanceOf( - \Carbon\Carbon::class, + Carbon::class, $rule->from, ); - $this->assertTrue($rule->from->isSameDay(\Carbon\Carbon::parse("2021-01-01"))); + $this->assertTrue($rule->from->isSameDay(Carbon::parse('2021-01-01'))); } - public function testResolvingDayOfWeek(): void { - $rule = \Netflex\RuleBuilder\DateRules\DateRule::parse([ - 'name' => "dateRule", - 'type' => "dayOfWeek", - 'days' => [0,1,2,3,4,5], + public function testResolvingDayOfWeek(): void + { + $rule = DateRule::parse([ + 'name' => 'dateRule', + 'type' => 'dayOfWeek', + 'days' => [0, 1, 2, 3, 4, 5], ]); $this->assertInstanceOf( - \Netflex\RuleBuilder\DateRules\DayOfWeekDateRule::class, + DayOfWeekDateRule::class, $rule ); } diff --git a/tests/DayOfWeekDateRuleTest.php b/tests/DayOfWeekDateRuleTest.php index 3980956..110dc7b 100644 --- a/tests/DayOfWeekDateRuleTest.php +++ b/tests/DayOfWeekDateRuleTest.php @@ -2,17 +2,21 @@ use PHPUnit\Framework\TestCase; +use Carbon\CarbonPeriod; +use Carbon\Carbon; + +use Netflex\RuleBuilder\DateRules\DayOfWeekDateRule; + final class DayOfWeekDateRuleTest extends TestCase { - public function testDayFilter() { - $today = \Carbon\Carbon::today(); + $today = Carbon::today(); for ($i = 0; $i < 365; $i++) { $in = $today->addDay()->copy(); - $period = new \Carbon\CarbonPeriod( + $period = new CarbonPeriod( $in, $in->copy()->addWeek()->subDay(), '1 day' @@ -20,18 +24,16 @@ public function testDayFilter() $rule = $this->_bootstrapRule(); - $this->assertEquals(1, collect($period->toArray())->filter(function(\Carbon\Carbon $date) use ($rule) { + $this->assertEquals(1, collect($period->toArray())->filter(function (Carbon $date) use ($rule) { return $rule->validate($date); })->count()); - } - } - private function _bootstrapRule(): \Netflex\RuleBuilder\DateRules\DayOfWeekDateRule + private function _bootstrapRule(): DayOfWeekDateRule { - return new \Netflex\RuleBuilder\DateRules\DayOfWeekDateRule([ - 'name' => "Day of week", + return new DayOfWeekDateRule([ + 'name' => 'Day of week', 'days' => [0] ], []); } diff --git a/tests/GroupDateRuleTest.php b/tests/GroupDateRuleTest.php index 3f3951b..fcfd56c 100644 --- a/tests/GroupDateRuleTest.php +++ b/tests/GroupDateRuleTest.php @@ -2,116 +2,121 @@ use Netflex\RuleBuilder\DateRules\GroupDateRule; -class _AlwaysTrueDateRule extends \Netflex\RuleBuilder\DateRules\DateRule { +use PHPUnit\Framework\TestCase; +use Carbon\Carbon; +use Netflex\RuleBuilder\DateRules\DateRule; +use Netflex\RuleBuilder\RuleCollection; - function __construct() +trait MockDateRule +{ + public static function make(): self { - parent::__construct([], []); + return new static([], []); } +} + +class AlwaysTrueDateRule extends DateRule +{ + use MockDateRule; - function validate(\Carbon\Carbon $date): bool + function validate(Carbon $date): bool { return true; } } -class _AlwaysFalseDateRule extends \Netflex\RuleBuilder\DateRules\DateRule { - - function __construct() - { - parent::__construct([], []); - } +class AlwaysFalseDateRule extends DateRule +{ + use MockDateRule; - function validate(\Carbon\Carbon $date): bool + public function validate(Carbon $date): bool { return false; } } -class GroupDateRuleTest extends \PHPUnit\Framework\TestCase +class GroupDateRuleTest extends TestCase { - - public function testAnyCase() { + public function testAnyCase() + { $rule = new GroupDateRule(['children' => [], 'name' => "test"], []); $rule->count = "any"; - $rule->children = [ - new _AlwaysTrueDateRule(), - new _AlwaysFalseDateRule(), - new _AlwaysFalseDateRule(), - ]; - $this->assertTrue($rule->validate(\Carbon\Carbon::today())); - - $rule->children = [ - new _AlwaysFalseDateRule(), - new _AlwaysFalseDateRule(), - ]; - - $this->assertFalse($rule->validate(\Carbon\Carbon::today())); - + $rule->children = RuleCollection::make([ + AlwaysTrueDateRule::make(), + AlwaysFalseDateRule::make(), + AlwaysFalseDateRule::make(), + ]); + $this->assertTrue($rule->validate(Carbon::today())); + + $rule->children = RuleCollection::make([ + AlwaysFalseDateRule::make(), + AlwaysFalseDateRule::make(), + ]); + + $this->assertFalse($rule->validate(Carbon::today())); } - public function testAllCase() { + public function testAllCase() + { $rule = new GroupDateRule(['children' => [], 'name' => "test"], []); $rule->count = "all"; - $rule->children = [ - new _AlwaysTrueDateRule(), - new _AlwaysFalseDateRule(), - new _AlwaysFalseDateRule(), - ]; - $this->assertFalse($rule->validate(\Carbon\Carbon::today())); - - $rule->children = [ - new _AlwaysTrueDateRule(), - new _AlwaysTrueDateRule() - ]; - - $this->assertTrue($rule->validate(\Carbon\Carbon::today())); - + $rule->children = RuleCollection::make([ + AlwaysTrueDateRule::make(), + AlwaysFalseDateRule::make(), + AlwaysFalseDateRule::make(), + ]); + $this->assertFalse($rule->validate(Carbon::today())); + + $rule->children = RuleCollection::make([ + AlwaysTrueDateRule::make(), + AlwaysTrueDateRule::make() + ]); + + $this->assertTrue($rule->validate(Carbon::today())); } - public function testIntCase() { + public function testIntCase() + { $rule = new GroupDateRule(['children' => [], 'name' => "test"], []); $rule->count = 2; - $rule->children = [ - new _AlwaysTrueDateRule(), - new _AlwaysFalseDateRule(), - new _AlwaysFalseDateRule(), - ]; - $this->assertFalse($rule->validate(\Carbon\Carbon::today())); - - $rule->children = [ - new _AlwaysTrueDateRule(), - new _AlwaysTrueDateRule(), - new _AlwaysTrueDateRule(), - new _AlwaysTrueDateRule(), - new _AlwaysTrueDateRule(), - new _AlwaysTrueDateRule(), - new _AlwaysFalseDateRule(), - ]; + $rule->children = RuleCollection::make([ + AlwaysTrueDateRule::make(), + AlwaysFalseDateRule::make(), + AlwaysFalseDateRule::make(), + ]); + $this->assertFalse($rule->validate(Carbon::today())); + + $rule->children = RuleCollection::make([ + AlwaysTrueDateRule::make(), + AlwaysTrueDateRule::make(), + AlwaysTrueDateRule::make(), + AlwaysTrueDateRule::make(), + AlwaysTrueDateRule::make(), + AlwaysTrueDateRule::make(), + AlwaysFalseDateRule::make(), + ]); $rule->count = 7; - $this->assertFalse($rule->validate(\Carbon\Carbon::today())); + $this->assertFalse($rule->validate(Carbon::today())); $rule->count = 6; - $this->assertTrue($rule->validate(\Carbon\Carbon::today())); + $this->assertTrue($rule->validate(Carbon::today())); $rule->count = 1; - $this->assertTrue($rule->validate(\Carbon\Carbon::today())); - + $this->assertTrue($rule->validate(Carbon::today())); } - public function testIntCountLargerThanChildrenCountToStillPass() { + public function testIntCountLargerThanChildrenCountToStillPass() + { $rule = new GroupDateRule(['children' => [], 'name' => "test"], []); $rule->count = 2; - $rule->children = [ - new _AlwaysTrueDateRule(), - new _AlwaysTrueDateRule(), - new _AlwaysTrueDateRule(), - new _AlwaysTrueDateRule(), - new _AlwaysTrueDateRule(), - new _AlwaysTrueDateRule(), - ]; + $rule->children = RuleCollection::make([ + AlwaysTrueDateRule::make(), + AlwaysTrueDateRule::make(), + AlwaysTrueDateRule::make(), + AlwaysTrueDateRule::make(), + AlwaysTrueDateRule::make(), + AlwaysTrueDateRule::make(), + ]); $rule->count = 666; - $this->assertTrue($rule->validate(\Carbon\Carbon::today())); + $this->assertTrue($rule->validate(Carbon::today())); } - - }