Skip to content

Commit

Permalink
Improve decimal cast fix
Browse files Browse the repository at this point in the history
  • Loading branch information
timacdonald committed Jan 3, 2023
1 parent 2b16766 commit 516c89e
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 4 deletions.
13 changes: 10 additions & 3 deletions src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
use ReflectionClass;
use ReflectionMethod;
use ReflectionNamedType;
use RuntimeException;
use TypeError;

trait HasAttributes
Expand Down Expand Up @@ -1313,21 +1314,27 @@ public function fromFloat($value)
/**
* Return a decimal as string.
*
* @param float $value
* @param float|string $value
* @param int $decimals
* @return string
*/
protected function asDecimal($value, $decimals)
{
$value = (string) $value;
if (extension_loaded('bcmath')) {
return bcadd($value, 0, $decimals);
}

if (! is_numeric($value)) {
throw new TypeError('$value must be numeric.');
}

if (is_string($value) && Str::contains($value, 'e', true)) {
throw new RuntimeException('The "decimal" model cast is unable to handle string based floats with exponents.');
}

[$int, $fraction] = explode('.', $value) + [1 => ''];

return $int.'.'.Str::of($fraction)->limit($decimals, '')->padLeft($decimals, '0');
return Str::of($int)->padLeft('1', '0').'.'.Str::of($fraction)->limit($decimals, '')->padRight($decimals, '0');
}

/**
Expand Down
78 changes: 77 additions & 1 deletion tests/Integration/Database/EloquentModelDecimalCastingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Illuminate\Support\Facades\Schema;
use Illuminate\Tests\Integration\Database\DatabaseTestCase;
use TypeError;
use ValueError;

class EloquentModelDecimalCastingTest extends DatabaseTestCase
{
Expand All @@ -19,6 +20,59 @@ protected function defineDatabaseMigrationsAfterDatabaseRefreshed()
});
}

public function testItHandlesExponent()
{
$model = new class extends Model
{
public $timestamps = false;

protected $casts = [
'amount' => 'decimal:20',
];
};

$model->amount = 0.123456789e3;
$this->assertSame('123.45678900000000000000', $model->amount);
}

public function testItThrowsWhenPassingExponentAsString()
{
$model = new class extends Model
{
public $timestamps = false;

protected $casts = [
'amount' => 'decimal:20',
];
};
$model->amount = '0.1e3';

if (extension_loaded('bcmath')) {
$this->expectException(ValueError::class);
} else {
$this->expectException(RuntimeException::class);
$this->expectExceptionMessage('The "decimal" model cast is unable to handle string based floats with exponents.'); // when bcmath is not available

}

$model->amount;
}

public function testItHandlesIntegersWithUnderscores()
{
$model = new class extends Model
{
public $timestamps = false;

protected $casts = [
'amount' => 'decimal:2',
];
};

$model->amount = 1_234.5;
$this->assertSame('1234.50', $model->amount);
}

public function testItThrowsOnNonNumericValues()
{
$model = new class extends Model
Expand All @@ -31,11 +85,33 @@ public function testItThrowsOnNonNumericValues()
};
$model->amount = 'foo';

$this->expectException(TypeError::class);
if (extension_loaded('bcmath')) {
$this->expectException(ValueError::class);
} else {
$this->expectException(TypeError::class);
}

$model->amount;
}

public function testItHandlesMissingIntegers()
{
$model = new class extends Model
{
public $timestamps = false;

protected $casts = [
'amount' => 'decimal:2',
];
};

$model->amount = .8;
$this->assertSame('0.80', $model->amount);

$model->amount = '.8';
$this->assertSame('0.80', $model->amount);
}

public function testItHandlesLargeNumbers()
{
$model = new class extends Model
Expand Down

0 comments on commit 516c89e

Please sign in to comment.