Тип и доктрина денежного поля Symfony
Какова ваша стратегия хранения денежных ценностей с помощью Doctrine? Поле Symfony money довольно удобно, но как сопоставить это с столбцом Doctrine? Есть ли комплект для этого, который предоставляет тип DBAL?
Типы столбцов
float
или int
недостаточны, потому что, когда вы имеете дело с деньгами, вы часто имеете дело с валютой. Я использую для этого два поля, но неловко обращаться с ними вручную.
Ответы
Ответ 1
Рассмотрим тип decimal
:
/**
* @ORM\Column(type="decimal", precision=7, scale=2)
*/
protected $price = 0;
Обратите внимание, что существуют валюты, которые имеют три десятичных позиции. Если вы намерены использовать такие валюты, параметр scale
должен быть 3
. Если вы намерены смешивать валюты с двумя и тремя десятичными позициями, добавьте конечный 0
, если есть только две десятичные позиции.
Внимание: $price
будет строкой в PHP. Вы можете либо перевести его на float
, либо умножить на 100 (или 1000, в случае валют с тремя десятичными позициями) и направить его на int
.
Сама валюта - отдельное поле; это может быть строка с трехбуквенным кодом валюты. Или - чистый путь - вы можете создать таблицу со всеми используемыми вами валютами, а затем создать отношение ManyToOne
для ввода валюты.
Ответ 2
Я рекомендую использовать объект стоимости как Деньги\Деньги.
# 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;
}
}
Ответ 3
Вы можете определить собственный тип поля, если вы укажете доктрине, как справиться с этим. Чтобы объяснить это, я составил "магазин" и "порядок", где используется "деньги" - ValueObject.
Для начала нам нужен объект Entity и другой ValueObject, который будет использоваться в сущности:
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;
}
}
Пока ничего особенного. Здесь "волшебство":
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);
}
}
Затем вы можете использовать следующий код:
// 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();
Вы можете написать карту, которая отображает ваш ввод, поступающий из денежного поля Symfony, в объект Money-ValueObject, чтобы упростить это.
Здесь объясняется еще пара деталей: http://doctrine-orm.readthedocs.org/en/latest/cookbook/advanced-field-value-conversion-using-custom-mapping-types.html
Неподтвержденный, но я использовал эту концепцию раньше, и она сработала. Дайте мне знать, если у вас есть вопросы.
Ответ 4
Я занимался поиском решения этой проблемы и, посмотрев на Google, попал на эту страницу.
Там показано поле Embeddable, доступное с Doctrine 2.5.
С чем-то вроде этого вы можете управлять значениями как денежными, которые имеют больше "параметров".
Пример:
/** @Entity */
class Order
{
/** @Id */
private $id;
/** @Embedded(class = "Money") */
private $money;
}
/** @Embeddable */
class Money
{
/** @Column(type = "int") */ // better than decimal see the mathiasverraes/money documentation
private $amount;
/** @Column(type = "string") */
private $currency;
}
Надеюсь, это поможет.
ОБНОВИТЬ
Я написал библиотеку PHP, которая содержит несколько полезных объектов значения.
Существует также объект-значение для управления денежными значениями (который включает в себя большую библиотеку MoneyPHP) и сохранения их в базе данных с использованием типа Doctrine.
Этот тип сохраняет значение в базе данных в виде 100-EUR
что означает 1 евро.