krixon/rules

View on GitHub
src/Specification/NumberMatches.php

Summary

Maintainability
A
1 hr
Test Coverage
<?php

namespace Krixon\Rules\Specification;

use Krixon\Rules\Operator;
use Krixon\Rules\Specification\Exception\UnsupportedOperator;
use Krixon\Rules\Specification\Exception\UnsupportedValue;
use function abs;
use function is_float;
use const PHP_FLOAT_EPSILON;

class NumberMatches implements Specification
{
    private $number;
    private $operator;
    private $epsilon;


    public function __construct(float $number, ?Operator $operator = null, float $epsilon = PHP_FLOAT_EPSILON)
    {
        $operator = $operator ?? Operator::equals();

        if (!$this->supportsOperator($operator)) {
            throw new UnsupportedOperator($this, $operator);
        }

        $this->number   = $number;
        $this->operator = $operator;
        $this->epsilon  = $epsilon;
    }


    public function isSatisfiedBy($value) : bool
    {
        $value = $this->extract($value);

        if (null === $value) {
            return false;
        }

        $equal = abs($value - $this->number) < $this->epsilon;

        switch (true) {
            case $this->operator->isEquals():
                return $equal;
            case $this->operator->isLessThan():
                return !$equal && ($value < $this->number);
            case $this->operator->isLessThanOrEqualTo():
                return $equal || $value < $this->number;
            case $this->operator->isGreaterThan():
                return !$equal && ($value > $this->number);
            case $this->operator->isGreaterThanOrEqualTo():
                return $equal || $value > $this->number;
        }

        // @codeCoverageIgnoreStart
        // Already validated in the constructor that the operator is supported. This line cannot be reached
        // in a bug-free implementation.
        throw new UnsupportedOperator($this, $this->operator);
        // @codeCoverageIgnoreEnd
    }


    /**
     * Extract the value to test from the input passed to isSatisfiedBy().
     *
     * By default, this returns the input itself assuming it is of the correct type. This can be overridden to perform
     * custom extraction logic if the input is not the correct type.
     *
     * @param mixed $value
     *
     * @return int|float|null
     * @throws UnsupportedValue
     */
    protected function extract($value)
    {
        if (is_int($value) || is_float($value)) {
            return $value;
        }

        return null;
    }


    protected function supportsOperator(Operator $operator) : bool
    {
        return $operator->isEquals()
            || $operator->isLessThan()
            || $operator->isLessThanOrEqualTo()
            || $operator->isGreaterThan()
            || $operator->isGreaterThanOrEqualTo();
    }
}