Тип и доктрина денежного поля 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 евро.