Quelle est votre stratégie pour stocker des valeurs monétaires avec Doctrine? Le champ money de Symfony est assez pratique, mais comment faire correspondre cela à la colonne Doctrine? Y at-il un paquet pour cela qui fournit le type DBAL?
Les types de colonne float
ou int
__ sont insuffisants car, lorsque vous traitez avec de l'argent, vous traitez souvent avec de la monnaie. J'utilise deux champs pour cela, mais c'est difficile à manipuler manuellement.
Pensez à utiliser le type decimal
:
/**
* @ORM\Column(type="decimal", precision=7, scale=2)
*/
protected $price = 0;
Notez qu'il existe des devises qui ont trois positions décimales. Si vous avez l'intention d'utiliser de telles devises, le paramètre scale
doit être 3
. Si vous avez l'intention de mélanger des devises avec deux et trois décimales, ajoutez un 0
final s'il n'y a que deux décimales.
Attention: $price
sera une chaîne en PHP. Vous pouvez soit le convertir en float
, soit le multiplier par 100 (ou 1000, dans le cas de devises à trois décimales) et le convertir en int
.
La monnaie elle-même est un champ séparé; il peut s'agir d'une chaîne avec le code de devise à trois lettres. Vous pouvez également créer une table avec toutes les devises que vous utilisez, puis créer une relation ManyToOne
pour la saisie de devise.
Je recommande d'utiliser un objet de valeur comme Money\Money .
# app/Resources/Money/doctrine/Money.orm.yml
Money\Money:
type: embeddable
fields:
amount:
type: integer
embedded:
currency:
class: Money\Currency
# app/Resources/Money/doctrine/Currency.orm.yml
Money\Currency:
type: embeddable
fields:
code:
type: string
length: 3
# app/config.yml
doctrine:
orm:
mappings:
Money:
type: yml
dir: "%kernel.root_dir%/../app/Resources/Money/doctrine"
prefix: Money
class YourEntity
{
/**
* @ORM\Embedded(class="\Money\Money")
*/
private $value;
public function __construct(string $currencyCode)
{
$this->value = new \Money\Money(0, new \Money\Currency($currencyCode));
}
public function getValue(): \Money\Money
{
return $this->value;
}
}
Vous pouvez définir un type de champ propre à condition que vous expliquiez à la doctrine comment gérer cela. Pour expliquer cela, j'ai créé un '' magasin '' et un '' ordre '' où un '' argent '' - ValueObject est utilisé.
Pour commencer, nous avons besoin d'une entité et d'un autre ValueObject, qui sera utilisé dans l'entité:
Order.php:
<?php
namespace Shop\Entity;
/**
* @Entity
*/
class Order
{
/**
* @Column(type="money")
*
* @var \Shop\ValueObject\Money
*/
private $money;
/**
* ... other variables get defined here
*/
/**
* @param \Shop\ValueObject\Money $money
*/
public function setMoney(\Shop\ValueObject\Money $money)
{
$this->money = $money;
}
/**
* @return \Shop\ValueObject\Money
*/
public function getMoney()
{
return $this->money;
}
/**
* ... other getters and setters are coming here ...
*/
}
Money.php:
<?php
namespace Shop\ValueObject;
class Money
{
/**
* @param float $value
* @param string $currency
*/
public function __construct($value, $currency)
{
$this->value = $value;
$this->currency = $currency;
}
/**
* @return float
*/
public function getValue()
{
return $this->value;
}
/**
* @return string
*/
public function getCurrency()
{
return $this->currency;
}
}
Jusqu'ici rien de spécial. La "magie" vient ici:
MoneyType.php:
<?php
namespace Shop\Types;
use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Shop\ValueObject\Money;
class MoneyType extends Type
{
const MONEY = 'money';
public function getName()
{
return self::MONEY;
}
public function getSqlDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
{
return 'MONEY';
}
public function convertToPHPValue($value, AbstractPlatform $platform)
{
list($value, $currency) = sscanf($value, 'MONEY(%f %d)');
return new Money($value, $currency);
}
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
if ($value instanceof Money) {
$value = sprintf('MONEY(%F %D)', $value->getValue(), $value->getCurrency());
}
return $value;
}
public function canRequireSQLConversion()
{
return true;
}
public function convertToPHPValueSQL($sqlExpr, AbstractPlatform $platform)
{
return sprintf('AsText(%s)', $sqlExpr);
}
public function convertToDatabaseValueSQL($sqlExpr, AbstractPlatform $platform)
{
return sprintf('PointFromText(%s)', $sqlExpr);
}
}
Ensuite, vous pouvez utiliser le code suivant:
// preparing everything for example getting the EntityManager...
// Store a Location object
use Shop\Entity\Order;
use Shop\ValueObject\Money;
$order = new Order();
// set whatever needed
$order->setMoney(new Money(99.95, 'EUR'));
// other setters get called here.
$em->persist($order);
$em->flush();
$em->clear();
Vous pouvez écrire un mappeur qui mappe votre entrée provenant du champ money de Symfony dans un objet Money-ValueObject pour simplifier davantage cette opération.
Quelques détails supplémentaires sont expliqués ici: http://doctrine-orm.readthedocs.org/en/latest/cookbook/advanced-field-value-conversion-using-custom-mapping-types.html
Non testé, mais j'ai utilisé ce concept avant et cela a fonctionné. Faites-moi savoir si vous avez des questions.
Je cherchais une solution à ce problème et, sur Google, j'ai atterri sur cette page .
Ici, il est illustré le champ incorporable disponible depuis Doctrine 2.5.
Avec quelque chose comme ça, vous pouvez gérer les valeurs comme des valeurs monétaires qui ont plus de "paramètres".
Un exemple:
/** @Entity */
class Order
{
/** @Id */
private $id;
/** @Embedded(class = "Money") */
private $money;
}
/** @Embeddable */
class Money
{
/** @Column(type = "int") */ // better then decimal see the mathiasverraes/money documentation
private $amount;
/** @Column(type = "string") */
private $currency;
}
J'espère que cela aidera.
METTRE À JOUR
J'ai écrit une PHP bibliothèque qui contient des objets de valeur utiles .
Il existe également un objet valeur pour gérer les valeurs monétaires (qui enveloppe la grande bibliothèque MoneyPHP ) et les conserve dans la base de données à l'aide d'un type Doctrine .
Ce type enregistre la valeur dans la base de données sous la forme de 100-EUR
, qui correspond à 1 euro.