ext-decimal icon indicating copy to clipboard operation
ext-decimal copied to clipboard

Allow Decimal fields traversion

Open speller opened this issue 2 years ago • 1 comments

Currently, while the Decimal has properties, they can not be traversed with foreach or any other way:

$num = new Decimal(10);
$a = [];
foreach ($num as $name => $value) {
    $a[$name] = $value;
}
print_r($a);
print_r((array)(new Decimal(10)));
print_r(get_object_vars(new Decimal(10)));

All of the code above will print empty arrays. This prevents from using PHPUnit assertions for number comparisons because PHPUnit converts objects to arrays for the detailed comparison. It is not a big issue to convert decimals to strings when we compare them directly:

$this->assertEquals('1.3', $foo->bar()->toString);

But it IS an issue when we compare objects or arrays containing decimals.

$this->assertEquals(
    new Baz(new Decimal(1), new Decimal(2)),
    $foo->bar() // returns a Baz instance { value1: Decimal(1), value2: Decimal(2) }
);

PHPUnit will detect empty objects and return true always, no matter wat are actual values in compared objects.

speller avatar Jul 12 '23 02:07 speller

As a workaround, I created the follwing PHPUnit extension (for v9):

<?php
namespace App\Tests\Utils;

use PHPUnit\Runner\BeforeFirstTestHook;

/**
 * Register custom comparator for the Decimal class.
 */
class DecimalExtension implements BeforeFirstTestHook
{
    public function executeBeforeFirstTest(): void
    {
        \SebastianBergmann\Comparator\Factory::getInstance()->register(new DecimalComparator());
    }
}
<?php
namespace App\Tests\Utils;

use Decimal\Decimal;
use SebastianBergmann\Comparator\Comparator;
use SebastianBergmann\Comparator\ComparisonFailure;

class DecimalComparator extends Comparator
{
    public function accepts($expected, $actual)
    {
        return $expected instanceof Decimal && $actual instanceof Decimal;
    }

    /**
     * @param \Decimal\Decimal $expected
     * @param \Decimal\Decimal $actual
     * @param $delta
     * @param $canonicalize
     * @param $ignoreCase
     * @return void
     */
    public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false)
    {
        $expectedAsString = sprintf('Decimal(%s)', $expected->toString());
        $actualAsString = sprintf('Decimal(%s)', $actual->toString());

        if ($expectedAsString != $actualAsString) {
            throw new ComparisonFailure(
                $expected,
                $actual,
                $expectedAsString,
                $actualAsString,
                false,
                'Failed asserting that two Decimals are equal.'
            );
        }
    }
}

phpunit.xml:

<?xml version="1.0" encoding="UTF-8"?>

<!-- https://phpunit.readthedocs.io/en/latest/configuration.html -->
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
...
>
    <php>
        ...
    </php>

    <testsuites>
        ...
    </testsuites>

    <extensions>
        <extension class="App\Tests\Utils\DecimalExtension" />
    </extensions>
</phpunit>

speller avatar Jul 12 '23 02:07 speller