Skip to main content

Black Friday 2025! Only until December 1st: coupon FRIDAY25 for 40% off Yearly/Lifetime membership!

Read more here
Premium Members Only
Join to unlock this tutorial and all of our courses.
Tutorial Premium Tutorial

Dealing With Money in Laravel/PHP: Best Practices

November 22, 2022
11 min read

When working with money in your Laravel projects, whether it's product prices or invoice total order amounts, we need to be extremely careful not to miscalculate something. Luckily, there are best practices and tools to help us with that, let's explore them in this article.

What we'll cover:

  • Typical (and wrong) Money Behavior: Floats
  • One Step Better: Integers
  • Special PHP Packages For Money
  • Laravel Way: Custom Casts
  • Currency Conversion
  • Specific Laravel Packages/Wrappers

Let's get into it!


Typical (and wrong) Money Behavior: Floats

How do you save data in the database when you need to work with things like product price, or order total?

Since the amount of money is a float - like $3.45, the logical way would be to store it the same way in the database.

1$table->decimal('price', 8, 2);
2// 8 is total digits, 2 is decimal digits

And then, whenever you need to show the price in Blade, you do something like:

1Total price: ${{ number_format($product->price, 2) }}

In the code above, we need number_format() so that 9.1 would be shown as 9.10, with two digits.

In most cases, this approach should work fine, as long as you have one currency in your project and you're not doing a lot of calculations with those fields.

But the way how programming languages and database engines work with calculating floats, you may encounter a rounding problem. Meaning, you may have incorrect calculations by 0.01 if the wrong roundings add up.

Here are a few articles to read more about this problem:

If you are not in the mood to read those articles, and if you want to just trust me, I will simplify it for you:

NEVER STORE MONEY VALUES AS FLOATS IN THE DATABASE.

There, I said it.

Here's another quote for you:

Money rounding

Yes, the possibility of that rounding error happening is very low, but still, it's better to be on the safe side, right? So here are the solutions below...

Premium Members Only

This advanced tutorial is available exclusively to Laravel Daily Premium members.

Premium membership includes:

Access to all premium tutorials
Video and Text Courses
Private Discord Channel

Comments & Discussion

DC
David Carr ✓ Link copied!

great article, I've being sting in the past storing money as a string or floats then wondering why calulations are out with rounding.

HA
Haytham Abdulla ✓ Link copied!

Thank Mr. Povilas. Great article.

I did the casting, the display is working fine, I used this:

use Brick\Money\Money as BrickMoney;
function priceShow(): Attribute
    {
        return Attribute::make(
            get: function () {
                $price = BrickMoney::ofMinor($this->product_price, $this->product_currency)->formatTo('en_US');
                return $price;
            }
        );
    }

So I'm getting the price in my blade directly.

But for saving the price, I didn't figure out how to do it, I did the casting and my table price column is integer. For example when I save 27.8, it is saved in the database as 28.

HA
Haytham Abdulla ✓ Link copied!

Got it:


// to get the price formated 
function priceShow(): Attribute
    {
        return Attribute::make(
            get: function () {
                return BrickMoney::of($this->product_price, $this->product_currency)->formatTo('en_US');
            }
        );
    }

// to set and get the price
    protected function productPrice(): Attribute
    {
        return Attribute::make(
            get: fn ($value) => BrickMoney::ofMinor($value, $this->product_currency)->getAmount()->toFloat(),
            set: fn ($value) => BrickMoney::of($value, $this->product_currency)->getMinorAmount()->toInt(),
        );
    }
HA
Haytham Abdulla ✓ Link copied!

sorry for keeping posting. I'm using Nova, I'm trying to make this work with my frontend and my backend which is Nova admin panel.

Now I'm using: in the Order modle:

function priceShow(): Attribute
    {
        return Attribute::make(
            get: function () {
                return BrickMoney::ofMinor($this->product_price, $this->product_currency)->formatTo('en_US');
            }
        );
    }


    function priceClean(): Attribute
    {
        return Attribute::make(
            get: function () {
                return BrickMoney::ofMinor($this->product_price, $this->product_currency)
                ->getAmount()->toFloat();
            }
        );
    }

in the observer:

public function saving(Order $order)
    {
        $order->product_price = BrickMoney::of($order->product_price, $order->product_currency)->getMinorAmount()->toInt();
    }
PK
Povilas Korop ✓ Link copied!

Sorry I'm not a Nova user so I'm not sure what that tool does with the fields automatically, that may affect the code and may be different from my example.

I think you should look at Nova docs and how it deals with Money, and follow my article only for theoretical knowledge how it works under the hood.

HA
Haytham Abdulla ✓ Link copied!

Thanks! For any one looking to display the curreny in the right format, the currency field withen Nova have functions to do that:

Currency::make('Estimated Reward')
                    ->hideFromIndex()
                    ->rules('required', 'numeric')
                    ->currency('BHD')
                    ->asMinorUnits()

you can do somthing like this also:

->displayUsing(function ($value) {
                        return BrickMoney::ofMinor($this->product_price, $this->product_currency)->formatTo('en_US');
                    })
                    ->resolveUsing(function ($value) {
                        return BrickMoney::ofMinor($value, $this->product_currency)->getAmount()->toFloat();
                    })
										
A
Akaunting ✓ Link copied!

I'd be more relaxed with saving floats in the database. It depends. For example, you don't show arbitrary precision amounts in the accounting industry but always round them. Of course, you don't do rounding on the database side but in PHP.

And in the case of Akaunting, because users can change the precision from UI whenever they want, you can't save it as an integer. That's why we made the akaunting/laravel-money package, which works like a charm for 200K+ Akaunting users ;)

Best regards, Denis Duliçi

HK
Huthaifah Kholi ✓ Link copied!

Great,

what about DB migration , what is the column type of price at database ?

PK
Povilas Korop ✓ Link copied!

Integer.

RB
Rodrigo Borges ✓ Link copied!

Very cool this article. How do you suggest using integrated with livewire? For example, how would the livewire property look to show the formatted value in the input and then change it to an integer to persist in the base?

PK
Povilas Korop ✓ Link copied!

Interesting question. I would probably do the conversion on Eloquent level, with Casts and accessors/mutators if needed, Livewire would be the layer just for presenting the data.

HN
Huy Nguyen ✓ Link copied!

Great article! Looking forward to more articles that dives deep more about how to use these packages for complex calculations, currency conversion based on various business requirement scenarios

A
Alexander ✓ Link copied!

Great article! But, while I agree that the FLOAT type should not be used for storing money, the DECIMAL type is perfectly suitable in my opinion, as these types are not the same. In MySQL, there are actually two groups of types to consider:

Fixed-Point Types (Exact Value) - DECIMAL, NUMERIC

Floating-Point Types (Approximate Value) - FLOAT, DOUBLE

So, DECIMAL and NUMERIC types are advised to be used with monetary data by the documentation as they use Precision Math for calculations.

I don't think there is any difference between using INTEGER and DECIMAL, except that you need to manually transform values before displaying and storing them in the database in the case of an INTEGER type, but I'm curious if anyone has an example where DECIMAL type will give incorrect results compared to INTEGER.

D
dodofix ✓ Link copied!

I'm using brick/money package for life insurance comparator app (to find best life insurance to customer). DB tables - decimal columns, no currency conversions. No problems with casting, storing, computing, ... and it's used with spatie/laravel-data and Laraval Nova packages. To deal with money try brick/money package at first :)

MS
Mustafa Selman Yıldırım ✓ Link copied!

In my case, I read prices from a 3rd party api as a string of the value and a currency code. To store it as an integer, I need to multiply it by 100 but if I'm not wrong, even this causes rounding errors. For example:

(int)('0.29'*100) // produces 28

So I tried to parse the string manually, but for many currencies with different locales, this may be a headache. Am I wrong with the above statement? If not, how can I supply integer values to those packages that use integers with their constructors?

D
DodoFix ✓ Link copied!

Try this, some explanation here

(int) round((float) "0.29" * 100, 0);

Or you can use brick/money package:

Money::of("0.29", "EUR")->getMinorAmount()->toInt();

MS
Mustafa Selman Yıldırım ✓ Link copied!

Thanks, I knew there shouldn't be such big rounding errors =)
So I can assume that rounding errors won't be high enough to change a digit when multiplying with 100. In my case, the problem was direct casting to int which only takes the whole part into consideration.
I liked brick/money as well, which accepts non-integer arguments to factory method. Probably going to use it to handle multi-currencies. It also has many strong methods.

PV
Paul van Vulpen ✓ Link copied!

Just wanted to let you know that they stole this post. https://medium.com/@laravelprotips/handling-money-in-laravel-php-essential-tips-014b5ee83336#:~:text=Integers%3A%20A%20Smarter%20Approach%20for%20Money%20in%20Laravel&text=Rather%20than%20keeping%20a%20float,this%20method%20offers%20better%20precision.

M
Modestas ✓ Link copied!

This is sad, but not a lot we can do... Especially on medium as they don't really care what posts are in there.

We'd Love Your Feedback

Tell us what you like or what we can improve

Feel free to share anything you like or dislike about this page or the platform in general.