Comment gérer les valeurs monétaires en PHP et MySql?

16

J'ai hérité d'une énorme pile de code hérité écrit en PHP au-dessus d'une base de données MySQL. Ce que j'ai remarqué, c'est que l'application utilise doublespour le stockage et la manipulation des données.

Maintenant, je suis tombé sur de nombreux messages mentionnant comment doublene conviennent pas aux opérations monétaires en raison des erreurs d'arrondi. Cependant, je n'ai pas encore trouvé de solution complète sur la façon dont les valeurs monétaires doivent être traitées dans le code PHP et stockées dans une base de données MySQL.

Existe-t-il une meilleure pratique lorsqu'il s'agit de gérer de l'argent spécifiquement en PHP?

Les choses que je recherche sont:

  1. Comment les données doivent-elles être stockées dans la base de données? type de colonne? Taille?
  2. Comment les données doivent-elles être gérées en addition, soustraction normales? multiplication ou division?
  3. Quand dois-je arrondir les valeurs? Combien d'arrondi est acceptable le cas échéant?
  4. Existe-t-il une différence entre la gestion de valeurs monétaires élevées et de valeurs faibles?

Remarque: Un exemple de code TRÈS simplifié de la façon dont je pourrais rencontrer des valeurs monétaires dans la vie quotidienne (divers problèmes de sécurité ont été ignorés pour la simplification. Bien sûr, dans la vraie vie, je n'utiliserais jamais mon code comme celui-ci):

$a= $_POST['price_in_dollars']; //-->(ex: 25.06) will be read as a string should it be cast to double?
$b= $_POST['discount_rate'];//-->(ex: 0.35) value will always be less than 1
$valueToBeStored= $a * $b; //--> any hint here is welcomed 

$valueFromDatabase= $row['price']; //--> price column in database could be double, decimal,...etc.

$priceToPrint=$valueFromDatabase * 0.25; //again cast needed or not?

J'espère que vous utilisez cet exemple de code comme un moyen de faire ressortir plus de cas d'utilisation et de ne pas le prendre à la lettre bien sûr.

Question bonus Si je dois utiliser un ORM tel que Doctrine ou PROPEL, à quel point sera-t-il différent d'utiliser de l'argent dans mon code.

Songo
la source
1
Je ne connais pas PHP mais la connaissance de la terminologie est inestimable dans ces scénarios pour google-fu, le terme que vous recherchez est "précision arbitraire" à laquelle google répond avec php.net/manual/en/book.bc.php
Jimmy Hoffa
1
@Jimmy Hoffa: la précision arbitraire n'est généralement pas ce dont vous avez besoin, ce qui peut représenter avec précision et travailler avec des fractions décimales. Bien sûr, vous trouvez souvent les deux combinés, mais par exemple, le decimaltype en C # a une précision limitée mais convient parfaitement aux valeurs monétaires.
Michael Borgwardt
1
@MichaelBorgwardt à droite, j'oublie les types rationnels car tant de langues n'en ont pas. Bon appel.
Jimmy Hoffa
Ayant beaucoup travaillé avec les devises et le code hérité, je dirais que je vais le stocker en entier et en utilisant des cents au lieu de dollars. Attention cependant, 32 entiers peuvent contenir une quantité étonnamment petite.4) un entier 32 bits ne peut contenir qu'un montant de 21 millions s'il n'est pas signé et utilise des cents.
Pieter B
En passant, votre exemple de code qui montre les prix et les remises en cours de publication me terrifie. J'espère que c'est tellement simplifié qu'il ne reflète pas l'utilisation réelle, mais à première vue, il semble que vous faites confiance au navigateur pour vous dire quel est le prix d'un article en POSTANT un champ masqué ou un autre.
Carson63000

Réponses:

6

Il peut être assez difficile de gérer les nombres avec PHP / MySQL. Si vous utilisez décimal (10,2) et que votre nombre est plus long ou plus précis, il sera tronqué sans erreur (sauf si vous définissez le mode approprié pour votre serveur db).

Pour gérer de grandes valeurs ou des valeurs de haute précision, vous pouvez utiliser une bibliothèque comme BCMath, elle vous permettra d'effectuer des opérations de base sur les grands nombres et de conserver la précision requise.

Je ne sais pas exactement quels calculs vous ferez, mais vous devez également garder à l'esprit que (0,22 * 0,4576) + (0,78 * 0,4576) ne sera pas égal à 0,4576 si vous n'utilisez pas la précision appropriée tout au long du processus.

La taille maximale de DECIMAL dans MySQL est de 65 donc elle devrait être plus que suffisante pour n'importe quel but. Si vous utilisez le type de champ DECIMAL, il sera renvoyé sous forme de chaîne indépendamment de l'utilisation d'un ORM ou simplement de PDO / mysql (i).

Comment les données doivent-elles être stockées dans la base de données? type de colonne? Taille?

DECIMAL avec la précision dont vous avez besoin. Si vous utilisez des taux de change, vous aurez besoin d'au moins quatre décimales

Comment les données doivent-elles être gérées en addition, soustraction normales? multiplication ou division?

Utilisez BCMath pour être du côté de la sauvegarde et pourquoi l'utilisation de float peut ne pas être une bonne idée

Quand dois-je arrondir les valeurs? Combien d'arrondi est acceptable le cas échéant?

Pour les valeurs monétaires, deux décimales normales sont acceptables, mais vous pouvez en avoir besoin de plus si, par exemple, vous utilisez des taux de change.

Existe-t-il une différence entre la gestion de valeurs monétaires élevées et de valeurs faibles?

Cela dépend de ce que vous entendez en gros. Il y a certainement une différence entre la manipulation des nombres avec une grande précision.

onlineapplab.com
la source
9

Une solution simple consiste à les stocker sous forme d'entiers. 99,99 stocké en tant que 9999. Si cela ne fonctionne pas (et il y a plusieurs raisons pour lesquelles cela pourrait être un mauvais choix), vous pouvez utiliser le type Decimal. http://dev.mysql.com/doc/refman/5.0/en/precision-math-decimal-changes.html du côté mysql. Du côté php, j'ai trouvé ce /programming/3244094/decimal-type-in-php qui pourrait être ce que vous recherchez.

Question bonus: difficile à dire. L'ORM va fonctionner en fonction des types de données choisis. Je dirais que vous pouvez faire des choses avec abstraction pour aider, mais ce problème spécifique n'est pas résolu simplement en passant à un ORM.

Ominus
la source
1
FWIW, Drupal Commerce utilise l'astuce 9999 pour stocker ses prix.
Florian Margaine
1
"et il y a de nombreuses raisons pour lesquelles cela pourrait être un mauvais choix" Souhaitez-vous partager certains des problèmes de cette pratique?
Songo
2
@Songo: voir flottant-point-gui.de/formats/integer
Michael Borgwardt
@MichaelBorgwardt Merci pour l'info, Songo désolé pour le retard, l'ouragan nous a fait sortir :)
Ominus
3

Je vais essayer de mettre mon expérience dans ceci:

Les choses que je recherche sont:

Comment les données doivent-elles être stockées dans la base de données? type de colonne? Taille?

J'ai utilisé DECIMAL(10,2)pour mysql sans problème (8 complète et 2 décimales == 99.999.999,99 == énorme quantité), mais cela dépend de la fourchette d'argent que vous devrez couvrir. Des quantités énormes doivent être prises avec un soin particulier (valeurs flottantes OS max par exemple). Sur la partie décimale, j'utilise 2 valeurs pour éviter de tronquer ou d'arrondir les valeurs. En prenant de l'argent, il y a peu de cas où vous avez besoin de plus de décimales (dans ce cas, vous devez vous assurer que l'utilisateur travaillera avec tous, sinon les données sont inutiles)

Comment les données doivent-elles être gérées en addition, soustraction normales? multiplication ou division?

Travaillez avec une devise et une table de change (avec dates). De cette façon, vous vous assurez que vous aurez toujours le montant correct enregistré. Un plus: enregistrez les valeurs complètes et créez une vue avec les résultats des calculs. Cela vous aidera à fixer les valeurs à la volée

Quand dois-je arrondir les valeurs? Combien d'arrondi est acceptable le cas échéant?

Encore une fois, cela dépend de la fourchette d'argent de votre système. Pensez toujours en termes KISS sauf si vous devez tomber dans le pétrin du bureau de change

Existe-t-il une différence entre la gestion de valeurs monétaires élevées et de valeurs faibles?

Selon votre système d'exploitation et vos langages de programmation, vous devez toujours vérifier vos valeurs max et min

Alwin Kesler
la source
Merci pour la réponse, mais si je dois gérer quelque chose comme $valueToBeStored= $a * $b;if $aet que $bles deux sont lus comme des décimales de la base de données, je pense qu'ils seront convertis doubleen PHP, n'est-ce pas? cela affectera-t-il les chiffres?
Songo
$aet $bsont tirés de db? ainsi, dans mon exemple, vous n'avez jamais besoin de stocker $valueToBeStoredcar vous aurez toujours la source $aet les $bdonnées. Ainsi, vous pouvez travailler par programmation la valeur dans une fonction ou ainsi ou créer une vue mysql avec le résultat de la colonne. De cette façon, si une valeur doit être changée, vous n'avez pas à vous soucier de modifier plusieurs endroits (sujet aux erreurs)
Alwin Kesler