如何在 Doctrine 自定义类型 convertToDatabaseValue 函数中获取列属性?

How to get column properties in Doctrine custom type convertToDatabaseValue function?

如标题所示,我在 Doctrine 中创建自己的 Type 并且该类型在 getSQLDeclaration() 函数中有其 precisionscale 选项。我也需要以某种方式从 convertToDatabaseValue() 访问这些,因为我需要以给定的精度对数字进行舍入。

<?php

namespace App\DBAL\Types;

use Decimal\Decimal;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\DecimalType as DoctrineDecimalType;

class DecimalType extends DoctrineDecimalType
{
    public function getSQLDeclaration(array $column, AbstractPlatform $platform): string
    {
        $column['precision'] = empty($column['precision'])
            ? 10 : $column['precision'];
        $column['scale']     = empty($column['scale'])
            ? 0 : $column['scale'];

        return 'DECIMAL(' . $column['precision'] . ', ' . $column['scale'] . ')' .
            (!empty($column['unsigned']) ? ' UNSIGNED' : '');
    }

    public function convertToPHPValue($value, AbstractPlatform $platform)
    {
        if (is_null($value)) {
            return null;
        }

        return new Decimal($value);
    }

    public function convertToDatabaseValue($value, AbstractPlatform $platform)
    {
        if ($value instanceof Decimal) {
            // HERE!! This is the line where I need to specify precision based on column declaration
            return $value->toFixed(????);
        }

        return parent::convertToDatabaseValue($value, $platform); // TODO: Change the autogenerated stub
    }
}

所以实体列看起来像:

    #[ORM\Column(type: 'decimal', precision: 10, scale: 4)]
    private Decimal $subtotal;

我需要获取 convertToDatabaseValue() 函数中的 scaleprecision 部分。

如我评论中所述,不支持您尝试执行的操作。虽然有一些方法可以强制执行类似类型的功能,例如生命周期回调,但它们被认为是 extremely bad-practice,因为它们指向持久性问题。
一般原则是,实体应该 不依赖 ORM 或数据库层 工作,实施强制行为的变通办法可能会导致应用程序中断。

在实践中,提供给实体的值应该根据模式规则按需要格式化,就像向本地 SQL 查询提供值一样。
这确保实体数据的完整性和一致性在整个应用程序中得到维护,并且每个实体持有的基础数据始终有效,而不需要 ORM 或数据库层“修复” 数据。

$decimal = new Decimal('1337.987654321');
$entity = new Entity();
$entity->setSubtotal($decimal->toFixed(4));

echo $entity->getSubtotal(); // 1337.9876

与上面的例子相反,当依赖ORM或数据库层时,数据会无效直到$em->flush();被调用到"根据ORM规范和调用convertToDatabaseValue()"修复"值格式。因此,在 $em->flush() 之前调用 $entity->getSubtotal(); 将改为 return 1337.987654321,这不符合列规范,导致依赖该值的业务逻辑被破坏。

对象转换示例

由于您依赖的是单独的库包,不适合修改或切换到自定义对象。
另一种方法是利用默认的 Doctrine decimal type 规范,该规范适用于 string data-type 值,并在实体内使用转换。绕过对自定义 DecimalType 或解决方法的需求,实体将更容易适用于 Symfony 框架的其余部分,而无需额外的解决方法。

3V4L Example

class Entity
{
    #[ORM\Column(type: 'decimal', precision: 10, scale: 4)]
    private string $subtotal = '0.0';

    // name as desired
    public function getSubtotalDecimal(): Decimal
    {
        return new Decimal($this->subtotal);
    }

    public function getSubtotal(): string
    {
        return $this->subtotal;
    }

    // see Union Types
    public function setSubtotal(string|Decimal $subtotal): self
    {
        if ($subtotal instanceof Decimal) {
            $subtotal = $subtotal->toFixed(4);
        }
        $this->subtotal = $subtotal;

        return $this;
    }
}
$entity = new Entity();
$entity->setSubtotal(new Decimal('1337.987654321'));

echo $entity->getSubtotal(); // 1337.9876
echo $entity->getSubtotalDecimal()->toFixed(4); // 1337.9876

recommended to use a DTO(data-transfer 对象)在应用于实体之前执行数据的验证和格式化(参见下面的 DTO 示例)。


DTO 示例

Symfony 的基本用法通常允许直接操作实体,例如在使用表单和 EntityType 字段时。但是,根据实体 始终有效 的原则,应该不需要验证由包含 user-supplied 数据的表单注入其中的实体数据。

为了防止实体内出现无效数据并促进关注点分离,可以使用 DTO 根据对数据用途的直接关注点来构造数据。

为了使 DTO 更易于使用,Doctrine ORM 支持 generation of DTOs,它可以用作 Forms 中的模型。
同样重要的是要注意 Doctrine 已经弃用 partial objects support in the future in favor of using a DTO for partial objects [sic]

class OrderSubtotalDTO
{
    private string $company;
    private string $subtotal;

    public function __construct(string $company, string $subtotal)
    {
        $this->company = $company;
        $this->subtotal = $subtotal;
    }

     // ...

    public function setSubtotal(string $value): self
    {
        $this->subtotal = $value;

        return $this;
    }

    public function apply(Entity $entity): self
    {
        // apply the formatting being supplied to the entity
        $entity->setSubtotal(new Decimal($this->subtotal)->toFixed(4));

        return $this;
    }
}
// use a Query service to reduce code duplication - this is for demonstration only
$q = $em->createQuery('SELECT NEW OrderSubtotalDTO(e.company, e.subtotal) FROM Entity e WHERE e.id = ?1');
$q->setParameter(1, $entity->getId());
$orderSubtotalDTO = $q->getOneOrNull();

$form = $formFactory->create(OrderSubtotalForm::class, $orderSubtotalDTO);
$form->handleRequest($request);

if ($form->isSubmitted() && $form->isValid()) {
    $orderSubtotalDTO->apply($entity);
    $em->flush();
}