假设有以下实体与Symfony应用程序。
class List
{
/**
* @ORM\OneToMany(targetEntity="ListItem", mappedBy="list")
* @ORM\OrderBy({"category.title" = "ASC"})
*/
protected $listItems;
}
class ListItem
{
/**
* @ORM\ManyToOne(targetEntity="List", inversedBy="listItems")
*/
protected $list;
/**
* @ORM\ManyToOne(targetEntity="Category", inversedBy="listItems")
*/
protected $category;
}
class Category
{
/**
* @ORM\OneToMany(targetEntity="ListItem", mappedBy="cateogory")
*/
protected $listItems;
protected $title;
}
不幸的是,orderBy参数category.title
在学说中不起作用。我的理解是,最常见的解决方案是在ListItem
实体上存储额外的属性,例如$categoryTitle
,并在orderBy注释中使用此新字段。例如;
class List
{
/**
* @ORM\OneToMany(targetEntity="ListItem", mappedBy="list")
* @ORM\OrderBy({"categoryTitle" = "ASC"})
*/
protected $listItems;
}
class ListItem
{
// --
protected $categoryTitle
}
这种方法的问题是通过set方法和/或监听器保持最新$categoryTitle
,以及显然是数据库数据的非规范化的额外开销。
我是否可以使用一种方法来订购与doctrine的关联,而不会降低数据库的质量?
答案 0 :(得分:2)
为了解决这个问题,我将以下方法添加到我们所有实体扩展的abstractEntity
,因此所有实体都可以对它们的集合进行排序。
以下代码尚未经过任何测试,但对于将来可能遇到此问题的任何人来说,它应该是一个很好的起点。
/**
* 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;
}
然后可以像下面那样调用这个方法来解决原始问题
$list->orderCollection('listItems', array('getCategory' => 'ASC', 'getASecondPropertyToSortBy' => 'DESC'))