php Doctrine是否可以使用自定义标量值(例如,聚合,CASE结果等)对我的实体进行水合?

bkkx9g8r  于 2023-03-28  发布在  PHP
关注(0)|答案(2)|浏览(107)

如果我有一个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 serializer,你可以轻松地将一个数据数组应用于一个属性与数组键同名的对象。
getScalarResult会将每条记录中的所有字段都打包在一起,但我不想在这里使用数组。我想要一个对象数组,就像一个普通的getResult会给予我的那样,如果我没有使用上面的语句。
我想我可以循环遍历结果(当处于“混合”形式时)并直接调用setter,或者使用getScalarResult并以某种方式手动水合,但这些选项看起来很笨拙。所以:
1.是否有一个内置的选项/config/hydrator/trick我错过了?或..
1.我可以/应该为这种情况创建一个自定义水合器吗?或者
1.我是否应该只是吸收它,循环数据,应用悬空的额外字段,并以预期的格式返回数据?
谢谢:)

编辑:使用PHP后搜索是不可行的,因为draft字段(上面的例子)也需要成为一组搜索过滤器的一部分(通过一个也有分页的UI-所以所有这些都需要在DB中完成,而不是作为事后的PHP代码。

cu6pst1q

cu6pst1q1#

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

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

l7mqbcuq2#

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.自定义补水器

(Ugly但不需要MySQL支持)
我能找到的唯一能让它在PHP中工作的东西是一个“丑陋”的水化代码黑客,这个黑客被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视图,让你的实体Map到它。这样,视图就做了CASE WHEN,从你的实体的Angular 来看,它只是另一个列。

相关问题