Doctrine 能否用开箱即用的自定义标量值(例如聚合、CASE 结果等)滋润我的实体?

问题描述 投票:0回答:2

如果我有一个 Querybuilder 实例并应用这样的东西:

$queryBuilder->addSelect("
   CASE
      WHEN c.modifyStamp > c.createStamp THEN 'draft'
      ELSE 'published'
   END AS state
");

然后,使用

getResult
,我会得到这样的东西:

[
   0 => [
      0 => \App\Entity\Test instance,
      'state' => 'draft'
   ],
   1 => [
      0 => \App\Entity\Test instance,
      'state' => 'published'
   ]
]

这不是很理想(但我知道默认情况下是预期的)。对于我的实体,我尝试添加这个:

private $state;

public function getState(): string
{
   return $this->state;
}

public function setState(string $state)
{
   $this->state = $state;
}

...希望 hydrator 可以看到正确名称的属性(或 setter),像任何其他属性一样填充它,然后只给我一个常规的对象数组(纯),而不是数组数组(混合)。例如,如果您使用 Symfony 序列化程序,您可以轻松地获取数据数组并将其应用于具有与数组键同名的属性的对象。

getScalarResult
会将每条记录中的所有字段捆绑在一起,但我不想在这里使用数组。我想要一组对象,就像一个普通的
getResult
如果我没有使用上面的语句会给我。

我想我可以遍历结果(当处于“混合”形式时)并直接调用设置器,或者使用

getScalarResult
并以某种方式手动补水,但这些选项看起来很笨重。所以:

  1. 我缺少内置选项/配置/水化器/技巧吗?或者..
  2. 我可以/应该为这种情况创建一个定制的保湿器吗?或者
  3. 我应该接受它,循环数据,应用悬空的额外字段,并以预期的格式返回数据吗?

谢谢:)

EDIT:使用 PHP 后搜索是不可行的,因为

draft
字段(上面的示例)也需要成为一组搜索过滤器的一部分(通过也有分页的 UI - 所以所有需要做的数据库而不是事后的 PHP 代码。)

php doctrine-orm doctrine
2个回答
0
投票

您可以使用 Doctrine postLoad 事件 为您的实体添加“真实”属性并按原样使用它

/**
 * Test
 *
 * @ORM\Entity
 * @ORM\HasLifecycleCallbacks
 */

class Test
{

    ...

    private $state;

    public function getState(): string
    {
       return $this->state;
    }

    /**
     * @ORM\PostLoad()
     */
    public function initState()
    {
        $this->state = $this->modifyStamp > $this->createStamp ? 'draft' : 'published';
    }
}

0
投票

1。 MySQL (>= 5.7) 生成的列

(我还没有测试过这个方法)

namespace Entity;

use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
class Test {
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column(type: 'integer')]
    private int|null $id = null;

    #[ORM\Column(type: 'integer')]
    private int|null $modifyStamp = null;

    #[ORM\Column(type: 'integer')]
    private int|null $createStamp = null;

    #[ORM\Column(
        type: "string",
        insertable: false,
        updatable: false,
        generated: "ALWAYS",
        columnDefinition: "GENERATED ALWAYS AS (CASE WHEN modifyStamp > createStamp THEN 'draft' ELSE 'published' END)"
    )]
    private string|null $state = null;
}
$queryBuilder = $entityManager->createQueryBuilder();
$queryBuilder->select('c')->from('Entity\Test', 'c');
echo '<pre>';
var_export($queryBuilder->getQuery()->getResult());
echo '</pre>';

参见:https://www.doctrine-project.org/projects/doctrine-orm/en/2.14/reference/attributes-reference.html#column和:https://dev.mysql.com/doc /refman/5.7/en/create-table-generated-columns.html

2。定制保湿器

(丑陋但不需要 MySQL 支持)

我唯一能找到让它在 PHP 中工作的东西是对水合代码的“丑陋”破解。而这个 hack 因 Doctrine

ObjectHydrator
.

的一些内部约定而变得更加丑陋。
use Doctrine\ORM\Internal\Hydration\ObjectHydrator;

class CustomHydrator extends ObjectHydrator {

    private function remapScalars($mixedResultRow) {
        $object = $mixedResultRow[0];
        $remainingScalars = [];
        foreach($mixedResultRow as $name => $value) {
            if ($name == 0) {
                continue;
            }
            $setter = 'set' . ucfirst($name);
            if (method_exists($object, $setter)) {
                $object->$setter($value);
            } else {
                $remainingScalars[$name] = $value;
            }
        }
        return count($remainingScalars) == 0 ?
            $object :
            array_merge([$object], $remainingScalars);
    }

    protected function hydrateRowData(array $row, array &$result) {
        parent::hydrateRowData($row, $result);
        $latestKey = array_key_last($result);
        $latestItem = $result[$latestKey];
        if (is_array($latestItem)) {
            $result[$latestKey] = $this->remapScalars($latestItem);
        }
    }
}

实体几乎就是你拥有它的样子:

namespace Entity;

use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
class Test {
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column(type: 'integer')]
    private int|null $id = null;

    #[ORM\Column(type: 'integer')]
    private int|null $modifyStamp = null;

    #[ORM\Column(type: 'integer')]
    private int|null $createStamp = null;

    private string|null $state = null;

    public function setState(string $newState) {
        $this->state = $newState;
    }
}

我在控制器中使用以下内容:

$entityManager->getConfiguration()->addCustomHydrationMode(
    'CustomHydrator',
    'CustomHydrator'
);

$queryBuilder = $entityManager->createQueryBuilder();

$queryBuilder->select('c')->from('Entity\Test', 'c')->addSelect(
    "CASE WHEN c.modifyStamp > c.createStamp
        THEN 'draft' ELSE 'published' END AS state"
);

echo '<pre>';
var_export($queryBuilder->getQuery()->getResult('CustomHydrator'));
echo '</pre>';

3。数据库视图

如果一切都失败了,你可以使用 MySQL 视图并使你的实体映射到它。这样视图就完成了

CASE WHEN
,从您实体的角度来看,它只是另一列。

© www.soinside.com 2019 - 2024. All rights reserved.