Od-do a jiné intervaly jako matematické value objecty

Velmi často se s intervalem pracuje tak, že se prostě předají dvě hodnoty: dva parametry do funkce, dvě proměnné, nebo prostě "pole" o dvou položkách.

Tento přístup ale trpí dvěma neduhy:

  1. Předávání dvou objektů nezaručuje validitu intervalu.
  2. Operace nad intervalem vyžadují vlastní netriviální implementaci.

Řešení je snadné: interval vždy reprezentovat jako value object.

cs 24. 6. 2017
PHP

Implementace už existuje

composer install achse/php-math-interval

  • Zapouzdřuje všechno o intervalu do jednoho objektu,
  • zajišťuje konzistenci a validitu dat,
  • poskytuje mocný tooling pro operace nad intervalem,
  • snadno rozšiřitelný pro nové typy.

Interval tak, jak jej znáte z hodin matematiky

Základní myšlenkou knihovny je poskytnout kompletní matematickou reprezentaci intervalu. Umí tedy:

  • Uzavřený/otevřený interval,
  • prázdný interval a jednoprvkový interval,
  • všemožné operace nad intervalem (průnik, rozdíl, …).

Datové typy

Knihovna přichází s těmito předpřipravenými typy:

  • DateTimeImmutableDateTime,
  • SingleDay (reprezentuje interval času v kontextu jednoho dne, užitečný pro otevírací hodiny),
  • Integer (převážně pro ukázky a testy).

Přidání dalšího typu je hračka

Protože PHP nemá generiky (které by se v tomto místě náramně hodily), je nutné každý typ naimplementovat zvlášť. Knihovna php-math-interval, ale poskytuje setup, kde je přidání nového typu hračka.

Architektura přináší abstraktní třídu intervalu s kompletní implementací všech operací nehledě na typ, jaký obsahuje. Jediné, co požaduje je to, aby byly porovnatelné (implementují rozhraní IComparable).

Mocný tooling

Knihovna disponuje bohatým toolingem.

Parsování intervalu ze stringu je šikovné pro ukázky a testy. Mnohem častěji se ale využije factory nebo konstruktor.

Test zda je element součátní intervalu:

$interval = Parser::parse('[1, 2]');
$interval->isContainingElement(new Integer(2)); // true
$interval->isContainingElement(new Integer(3)); // false

Průnik dvou intervalů:

// (1, 3) ∩ (2, 4) ⟺ (2, 3)
Parser::parse('(1, 3)')->getIntersection(Parser::parse('(2, 4)')); // (2, 3)

Sjednocení dvou intervalů

// Překrývající se intervaly
Parser::parse('[1, 3]')->getUnion(Parser::parse('[2, 4]')); // ['[1, 4]']
// Přesně navazující intervaly
Parser::parse('[1, 2)')->getUnion(Parser::parse('[3, 4]')); // ['[1, 4]']
// Sjednocení dvou vzdálených intervalů vrátí pole oněch dvou intervalů
Parser::parse('[1, 2]')->getUnion(Parser::parse('[3, 4]')); // ['[1, 2], [3, 4]']

Rozdíl dvou intervalů:

// [1, 4] \ [2, 3]
Parser::parse('[1, 4]')->getDifference(Parser::parse('[2, 3]')); // ['[1,2)', '(3, 4]']

Test zda jeden interval obsahuje druhý:

// [1, 4] contains [2, 3]
Parser::parse('[1, 4]')->isContaining(Parser::parse('[2, 3]')); // true
// [2, 3] NOT contains [1, 4]
Parser::parse('[2, 3]')->isContaining(Parser::parse('[1, 4]')); // false

Test zda na sebe intervaly zprava navazují:

Parser::parse('[1, 2]')->isOverlappedFromRightBy(Parser::parse('[2, 3]')); // true
Parser::parse('[2, 3]')->isOverlappedFromRightBy(Parser::parse('[1, 2]')); // false
// (1, 2) ~ [2, 3]
Parser::parse('(1, 2)')->isOverlappedFromRightBy(Parser::parse('[2, 3]')); // false

Test zda spolu intervaly kolidují (neprázdný průnik):

Parser::parse('[2, 3]')->isColliding(Parser::parse('[1, 2]')); // true
Parser::parse('[1, 2]')->isColliding(Parser::parse('(2, 3)')); // false

Pro DateTimeImmutable, DateTimeSingleDayTimeInterval knihovna disponuje metodou pro zjištění návaznosti intervalů:

$first = Parser::parse('[2014-12-31 00:00:00, 2014-12-31 23:59:59)');
$second = Parser::parse('[2015-01-01 00:00:00, 2015-01-01 02:00:00)');
$first->isFollowedBy($second, IntervalUtils::PRECISION_ON_SECOND); // true
$second->isFollowedBy($first, IntervalUtils::PRECISION_ON_SECOND); // false

Za zmínku ještě stojí metoda isFollowedByAtMidnight pro testování návaznosti intervalů mezi dny.

Závěrem

Máte nějaký dobrý postřeh nebo komentář k článku či knihovně?
Tweetněte mi o tom nebo zanechte issue v githubu .

Těším se na feedback.