如何在 Doctrine 自定义类型 convertToDatabaseValue 函数中获取列属性?
How to get column properties in Doctrine custom type convertToDatabaseValue function?
如标题所示,我在 Doctrine 中创建自己的 Type
并且该类型在 getSQLDeclaration()
函数中有其 precision
和 scale
选项。我也需要以某种方式从 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()
函数中的 scale
或 precision
部分。
如我评论中所述,不支持您尝试执行的操作。虽然有一些方法可以强制执行类似类型的功能,例如生命周期回调,但它们被认为是 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 框架的其余部分,而无需额外的解决方法。
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();
}
如标题所示,我在 Doctrine 中创建自己的 Type
并且该类型在 getSQLDeclaration()
函数中有其 precision
和 scale
选项。我也需要以某种方式从 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()
函数中的 scale
或 precision
部分。
如我评论中所述,不支持您尝试执行的操作。虽然有一些方法可以强制执行类似类型的功能,例如生命周期回调,但它们被认为是 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 框架的其余部分,而无需额外的解决方法。
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();
}