gpt4 book ai didi

php - 当不可能使用@orderBy 注解时,基于关联的实体对 Doctrine Collection 进行排序

转载 作者:IT王子 更新时间:2023-10-29 00:18:32 26 4
gpt4 key购买 nike

我想了解基于关联实体订购 Doctrine Collection 的最佳方式。在这种情况下,无法使用@orderBy 注释。

我在互联网上找到了 5 个解决方案。

1) 向 AbstractEntity 添加方法(根据 Ian Belter https://stackoverflow.com/a/22183527/1148260 )

/**
* This method will change the order of elements within a Collection based on the given method.
* It preserves array keys to avoid any direct access issues but will order the elements
* within the array so that iteration will be done in the requested order.
*
* @param string $property
* @param array $calledMethods
*
* @return $this
* @throws \InvalidArgumentException
*/
public function orderCollection($property, $calledMethods = array())
{
/** @var Collection $collection */
$collection = $this->$property;

// If we have a PersistentCollection, make sure it is initialized, then unwrap it so we
// can edit the underlying ArrayCollection without firing the changed method on the
// PersistentCollection. We're only going in and changing the order of the underlying ArrayCollection.
if ($collection instanceOf PersistentCollection) {
/** @var PersistentCollection $collection */
if (false === $collection->isInitialized()) {
$collection->initialize();
}
$collection = $collection->unwrap();
}

if (!$collection instanceOf ArrayCollection) {
throw new InvalidArgumentException('First argument of orderCollection must reference a PersistentCollection|ArrayCollection within $this.');
}

$uaSortFunction = function($first, $second) use ($calledMethods) {

// Loop through $calledMethods until we find a orderable difference
foreach ($calledMethods as $callMethod => $order) {

// If no order was set, swap k => v values and set ASC as default.
if (false == in_array($order, array('ASC', 'DESC')) ) {
$callMethod = $order;
$order = 'ASC';
}

if (true == is_string($first->$callMethod())) {

// String Compare
$result = strcasecmp($first->$callMethod(), $second->$callMethod());

} else {

// Numeric Compare
$difference = ($first->$callMethod() - $second->$callMethod());
// This will convert non-zero $results to 1 or -1 or zero values to 0
// i.e. -22/22 = -1; 0.4/0.4 = 1;
$result = (0 != $difference) ? $difference / abs($difference): 0;
}

// 'Reverse' result if DESC given
if ('DESC' == $order) {
$result *= -1;
}

// If we have a result, return it, else continue looping
if (0 !== (int) $result) {
return (int) $result;
}
}

// No result, return 0
return 0;
};

// Get the values for the ArrayCollection and sort it using the function
$values = $collection->getValues();
uasort($values, $uaSortFunction);

// Clear the current collection values and reintroduce in new order.
$collection->clear();
foreach ($values as $key => $item) {
$collection->set($key, $item);
}

return $this;
}

2) 创建一个 Twig 扩展,如果你只需要在模板中排序(根据 Kris https://stackoverflow.com/a/12505347/1148260 )

use Doctrine\Common\Collections\Collection;

public function sort(Collection $objects, $name, $property = null)
{
$values = $objects->getValues();
usort($values, function ($a, $b) use ($name, $property) {
$name = 'get' . $name;
if ($property) {
$property = 'get' . $property;
return strcasecmp($a->$name()->$property(), $b->$name()->$property());
} else {
return strcasecmp($a->$name(), $b->$name());
}
});
return $values;
}

3) 将集合转换为数组然后对其进行排序(根据 Benjamin Eberlei https://groups.google.com/d/msg/doctrine-user/zCKG98dPiDY/oOSZBMabebwJ )

public function getSortedByFoo()
{
$arr = $this->arrayCollection->toArray();
usort($arr, function($a, $b) {
if ($a->getFoo() > $b->getFoo()) {
return -1;
}
//...
});
return $arr;
}

4)使用ArrayIterator对集合进行排序(根据nifr https://stackoverflow.com/a/16707694/1148260)

$iterator = $collection->getIterator();
$iterator->uasort(function ($a, $b) {
return ($a->getPropery() < $b->getProperty()) ? -1 : 1;
});
$collection = new ArrayCollection(iterator_to_array($iterator));

5) 创建一个服务来收集有序集合,然后替换无序集合(我没有示例,但我认为它很清楚)。我认为这是最丑陋的解决方案。

根据您的经验,哪个是最佳解决方案?您是否有其他建议以更有效/优雅的方式订购系列?

非常感谢。

最佳答案

前提

您提出了 5 个有效/体面的解决方案,但我认为所有这些都可以简化为两种情况,并有一些小的变体。

我们知道排序总是O(NlogN),所以理论上所有的解决方案都具有相同的性能。但由于这是 Doctrine,SQL 查询的数量和 Hydration 方法(即将数据从数组转换为对象实例)是瓶颈。

因此您需要选择“最佳方法”,具体取决于您何时需要加载实体以及您将如何处理它们。

这些是我的“最佳解决方案”,在一般情况下我更喜欢我的解决方案 A)

A) 加载器/存储库服务中的 DQL

类似于

没有你的情况(不知何故有 5,见最后的注释)。阿尔贝托·费尔南德斯在评论中为您指明了正确的方向。

最佳时机

DQL 是(可能)最快的方法,因为将排序委托(delegate)给为此高度优化的 DBMS。 DQL 还可以完全控制在单个查询中获取哪些实体以及水化模式。

缺点

不可能(AFAIK)通过配置修改由 Doctrine Proxy 类生成的查询,因此您的应用程序需要使用存储库并在每次加载实体(或覆盖默认实体)时调用正确的方法。

示例

class MainEntityRepository extends EntityRepository
{
public function findSorted(array $conditions)
{
$qb = $this->createQueryBuilder('e')
->innerJoin('e.association', 'a')
->orderBy('a.value')
;
// if you always/frequently read 'a' entities uncomment this to load EAGER-ly
// $qb->select('e', 'a');

// If you just need data for display (e.g. in Twig only)
// return $qb->getQuery()->getResult(Query::HYDRATE_ARRAY);

return $qb->getQuery()->getResult();
}
}

B) 在 PHP 中预先加载和排序

类似案例

情况 2)、3) 和 4) 只是在不同地方完成的同一件事。我的版本是一般情况,只要获取实体就适用。如果您必须选择其中之一,那么我认为解决方案 3) 是最方便的,因为不会弄乱实体并且始终可用,但使用 EAGER 加载(继续阅读)。

最佳时机

如果关联的实体总是被读取,但不可能(或不方便)添加服务,那么所有实体都应该 EAGER-ly 加载。然后排序可以由 PHP 完成,只要它对应用程序有意义:在事件监听器中,在 Controller 中,在 Twig 模板中......如果应该始终加载实体,那么事件监听器是最佳选择。

缺点

不如 DQL 灵活,当集合很大时,PHP 中的排序可能是一个缓慢的操作。此外,实体需要作为 Object 进行水合,这很慢,如果集合不用于其他目的,则太过分了。提防延迟加载,因为这会为每个实体触发一个查询。

示例

主实体.orm.xml:

<?xml version="1.0" encoding="utf-8"?>
<doctrine-mapping>
<entity name="MainEntity">
<id name="id" type="integer" />
<one-to-many field="collection" target-entity="LinkedEntity" fetch="EAGER" />
<entity-listeners>
<entity-listener class="MainEntityListener"/>
</entity-listeners>
</entity>
</doctrine-mapping>

主实体.php:

class MainEntityListener
{
private $id;

private $collection;

public function __construct()
{
$this->collection = new ArrayCollection();
}

// this works only with Doctrine 2.5+, in previous version association where not loaded on event
public function postLoad(array $conditions)
{
/*
* From your example 1)
* Remember that $this->collection is an ArryCollection when constructor is called,
* but a PersistentCollection when are loaded from DB. Don't recreate the instance!
*/

// Get the values for the ArrayCollection and sort it using the function
$values = $this->collection->getValues();

// sort as you like
asort($values);

// Clear the current collection values and reintroduce in new order.
$collection->clear();
foreach ($values as $key => $item) {
$collection->set($key, $item);
}
}
}

最后的笔记

  • 我不会按原样使用案例 1),因为它非常复杂并且引入了减少封装的继承。此外,我认为它与我的示例具有相同的复杂性和性能。
  • 情况 5) 不一定是坏事。如果“服务”是应用程序存储库,并且它使用 DQL 进行排序,那么是我的第一个最佳案例。如果自定义服务只是对集合进行排序,那么我认为绝对不是一个好的解决方案。
  • 我在这里写的所有代码都不适合“复制粘贴”,因为我的目的是展示我的观点。希望这是一个好的起点。

免责声明

这些是“我的”最佳解决方案,正如我在作品中所做的那样。希望对您和其他人有所帮助。

关于php - 当不可能使用@orderBy 注解时,基于关联的实体对 Doctrine Collection 进行排序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22828669/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com