The type of the interval I rendered to ValueObject and the verification operation was done through it. The result is such an interface:
interface IntervalTypeInterface { public function intervalContains(IntervalInterface $interval, $point); }
The interval itself has an interface:
interface IntervalInterface { public function contains($point); public function start(); public function end(); }
An interesting idea is to do it through the decalators, but this is not appropriate since the interval itself must know its type (closed / open) and the code that will work at this interval is expected by a specific type of interval. Ie, the user code will wait for DateTimeInterval and an attempt to pass it IPInterval should result in an error. And if we are going to create specific IPClosedInterval type IPClosedInterval , then such kind of IPClosedInterval need to be created for each type of interval, which is already redundant and complicates the configuration of intervals.
Here is an example of the implementation of the date interval:
class DateTimeInterval implements IntervalInterface { private $type; private $start; private $end; public function __construct(IntervalTypeInterface $type, \DateTime $start, \DateTime $end) { // это должен быть корректный интервал if ($start >= $end) { throw IncorrectIntervalException::create(); } $this->type = $type; $this->start = clone $start; $this->end = clone $end; } public function contains($point) { // здесь я вынужден проверять тип данных точки // ожидается \DateTime, но может прийти все что угодно $this->checkPointType($point); return $this->type->intervalContains($this, $point); } // ... }
That was actually the problem. I have to explicitly check the data type of the point. PHP does not allow you to override the parameter type in the inherited class. Ie I have to stop using IntervalInterface and, accordingly, there are problems in using the IntervalTypeInterface interface.
There was another idea. Just wrap the value of the interval point in the class and all the comparison operations to shift to it. Points are better known for comparing them. The same IP is a string and for comparison in PHP there is a function that will help with this.
But we have a problem associated with the fact that each point must be wrapped in order to use in the interval. For IP this is the only possible option, but for numbers and dates (PHP allows you to perform comparison operations for date objects) is not necessary. Then you can only wrap an IP point into an object, but then we get a discrepancy in style.
And this does not solve the problem with the type of interval. There are only 4 interval types. No more and no less. They perform the same comparison actions for all types of points. In this case, it is logical to make a common interface for points:
interface PointInterface { public function isEqualTo(PointInterface $point): bool; public function isGreaterThan(PointInterface $point): bool; public function isLessThan(PointInterface $point): bool; }
The type will have an interface:
interface IntervalTypeInterface { public function intervalContains(IntervalInterface $interval, PointInterface $point); }
Well, an example of the implementation of an open interval
class OpenInterval implements IntervalTypeInterface { public function intervalContains(IntervalInterface $interval, PointInterface $point) { return $interval->start()->isLessThan($point) && $interval->end()->isGreaterThan($point); } }
It seems to be better, but PHP does not allow overriding the type of the parameters of the methods, so you can not do this:
interface IPPointInterface extends PointInterface { public function isEqualTo(IPPointInterface $ip): bool; public function isGreaterThan(IPPointInterface $ip): bool; public function isLessThan(IPPointInterface $ip): bool; }
And it turns out inside the point we will have to explicitly check the data type, which is not very good. And it turns out the only option that I see so far is the use of switch / case in the interval:
class IPInterval { private $type; private $start; private $end; const TYPE_CLOSED = 'closed'; const TYPE_OPEN = 'open'; const TYPE_HALF_CLOSED = 'half-closed'; const TYPE_HALF_OPEN = 'half-open'; public function __construct(IPPointInterface $start, IPPointInterface $end, $type = self::TYPE_CLOSED) { // проверка допустимости типа интервала if (!in_array($type, self::types())) { throw IncorrectIntervalTypeException::create(); } // это должен быть корректный интервал if ($start->isEqualTo($end) || $start->isGreaterThan($end)) { throw IncorrectIntervalException::create(); } $this->type = $type; $this->start = $start; $this->end = $end; } public start function types() // можно вынести в базовый класс { return [ self::TYPE_CLOSED, self::TYPE_OPEN, self::TYPE_HALF_CLOSED, self::TYPE_HALF_OPEN, ]; } public function contains(IPPointInterface $point) { // этот код дублируется для всех типов интервалов если мы будем оборачивать точки в классы // если будем оборачивать только IP то дублироваться будет в интервалах дат, времени и числовых интервалах switch ($this->type) { case self::TYPE_OPEN: return $this->start->isLessThan($point) && $this->end->isGreaterThan($point); // дальше по аналогии } } }
It seems to be a working version, but some kind of clumsy one.
You can make a generic ValueObject interval type for each interval type:
class IPIntervalType { const TYPE_CLOSED = 'closed'; const TYPE_OPEN = 'open'; const TYPE_HALF_CLOSED = 'half-closed'; const TYPE_HALF_OPEN = 'half-open'; private function __construct($type) { // тип можно не проверять так как тип определяется только через фабричные методы $this->type = $type; } // фабричный метод public static function closed() { return new self(self::TYPE_CLOSED); } // другие фабричные методы // .. // все что выше можно вынести в базовый класс public function intervalContains(IPIntervalInterface $interval, IPPointInterface $point) { return $interval->start()->isLessThan($point) && $interval->end()->isGreaterThan($point); } }
then in the interval we get the simplification
class IPInterval { // .. public function contains(IPPointInterface $point) { return $this->type->intervalContains($this, $point); } }