2014-12-04 132 views
11

您的货币价值与原则存储的策略是什么? Symfony的money字段非常方便,但如何将其映射到Doctrine的专栏?有没有为此提供DBAL类型的包?Symfony货币类型和原则

floatint列类型不足,因为当您处理货币时,您还经常处理货币。我为此使用了两个字段,但手动处理很尴尬。

+0

你需要存储的整数或浮点数?我想你会有一个字段类型的限制器数量。 – 2014-12-04 17:46:59

+0

@ A.L没关系。我总是可以使用[divisor](http://symfony.com/doc/2.5/reference/forms/types/money.html#divisor)选项将int整合成浮动。现在我使用'int'(金额)+'string'(货币),但它很尴尬。 – SiliconMind 2014-12-04 17:52:12

+0

你用“+”号做什么?你有一个领域的货币数量和一个领域?如果你必须使用不同的货币,我建议你在你的问题中加上一个注释,因为它比只有一种货币更复杂一点。 – 2014-12-04 17:54:13

回答

2

只要您告诉教义如何处理,您可以定义自己的字段类型。为了解释这一点,我编了一个“商店”和“订单”,在那里使用了一个“金钱”--ValueObject。

首先我们需要一个实体和另一个的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

未经检验的,但我之前用这个概念和它的工作。如果你有问题,请告诉我。

+1

谢谢。这几乎是我正在考虑的事情。但是我会用'int'来存储它的最小单位数量(例如欧元的美分)。这种情况下的不利之处在于,您无法通过货币或金额轻松筛选或排序表中的数据。这对我来说是最大的表演赛。 – SiliconMind 2014-12-05 10:23:08

+0

这也可以在这里找到 - https://github.com/leaphly/price – qooplmao 2014-12-05 11:03:27

+0

与SQL中的浮点列类型相比,有哪些优点? – 2014-12-06 12:13:13

1

我正在为解决这个问题提供解决方案,并使用Google搜索登陆this page

在那里,说明了自Doctrine 2.5以来可用的Embeddable field

有了这样的事情,你可以将价值管理为具有更多“参数”的货币价值。

一个例子:

/** @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; 
} 

希望这会有所帮助。

UPDATE

我写了一个PHP library that contains some useful value objects

还有一个值对象来管理货币值(包装伟大的MoneyPHP library)并使用Doctrine type将它们保存到数据库。

此类型以100-EUR的形式将值保存到数据库,该值表示1欧元。

9

考虑使用decimal类型:

/** 
* @ORM\Column(type="decimal", precision=7, scale=2) 
*/ 
protected $price = 0; 

注意,有哪些有三个小数位的货币。如果您打算使用此类货币,则scale参数应为3。如果您打算混合两位和三位小数位的货币,如果只有两位小数位,则添加尾随0

注意:$price将是PHP中的字符串。您可以将它投射到float或将其乘以100(或1000,如果是具有三位小数位的货币),并将其投射到int


货币本身是一个单独的字段;它可以是具有三字母货币代码的字符串。或者 - 干净的方式 - 您可以使用您正在使用的所有货币创建一个表格,然后为货币条目创建一个ManyToOne关系。

+2

不要考虑使用'decimal'类型! PHP不能准确地表示小数,这对于钱来说肯定是一个问题!始终以最小的单位存储货币(美分,便士等)。 – Jonny 2016-11-06 16:56:57

+1

如果你需要证明这不是个好主意*,在你的shell中执行:'echo“<?php var_dump((。1 + .1 + .1 === .3));” | php' – Jonny 2016-11-07 10:16:59

+1

@Jonny:是的,但如果直接比较浮点值,这只是一个问题。一个认真的程序员会知道他们会检查绝对或相对的epsilon。此外,Doctrine为您提供了数值字符串的值,因此您可以自由地将字符串乘以1000以获得int。 (是的,在字符串上应用数学运算是凌乱的,但这就是你的PHP。) – lxg 2016-11-07 20:27:00

4

我推荐使用像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; 
    } 
} 
+0

此解决方案很好,但前提是您不需要可空嵌入式,因为不幸的Doctrine2不支持可空嵌入式。 – baris1892 2018-02-17 16:57:30