Passed
Push — new-api ( 4bfe18...7ec1cc )
by Sebastian
05:06
created

Sort::performSort()   C

Complexity

Conditions 14
Paths 85

Size

Total Lines 58
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 35
CRAP Score 14.0042

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 14
eloc 37
c 1
b 0
f 0
nc 85
nop 2
dl 0
loc 58
ccs 35
cts 36
cp 0.9722
crap 14.0042
rs 6.2666

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/*
3
 * citeproc-php
4
 *
5
 * @link        http://github.com/seboettg/citeproc-php for the source repository
6
 * @copyright   Copyright (c) 2016 Sebastian Böttger.
7
 * @license     https://opensource.org/licenses/MIT
8
 */
9
10
namespace Seboettg\CiteProc\Style\Sort;
11
12
use Seboettg\CiteProc\CiteProc;
13
use Seboettg\CiteProc\Data\DataList;
14
use Seboettg\CiteProc\Exception\CiteProcException;
15
use Seboettg\CiteProc\Util\DateHelper;
16
use Seboettg\CiteProc\Util\Variables;
17
use Seboettg\Collection\ArrayList;
18
use Seboettg\Collection\ArrayList\ArrayListInterface;
19
use SimpleXMLElement;
20
21
/**
22
 * Class Sort
23
 *
24
 * cs:citation and cs:bibliography may include a cs:sort child element before the cs:layout element to specify the
25
 * sorting order of respectively cites within citations, and bibliographic entries within the bibliography.
26
 *
27
 * The cs:sort element must contain one or more cs:key child elements. The sort key, set as an attribute on cs:key, must
28
 * be a variable (see Appendix IV - Variables) or macro name. For each cs:key element, the sort direction can be set to
29
 * either “ascending” (default) or “descending” with the sort attribute.
30
 *
31
 * @package Seboettg\CiteProc\Style
32
 *
33
 * @author Sebastian Böttger <[email protected]>
34
 */
35
class Sort
36
{
37
    /**
38
     * ordered list contains sorting keys
39
     *
40
     * @var ArrayList
41
     */
42
    private $sortingKeys;
43
44 173
    public static function factory(?SimpleXMLElement $node): ?Sort
45
    {
46 173
        if ($node && $node->children()->count() > 0) {
47 60
            $sortingKeys = new ArrayList();
48
            /** @var SimpleXMLElement $child */
49 60
            foreach ($node->children() as $child) {
50 60
                if ("key" === $child->getName()) {
51 60
                    $sortingKeys->append(Key::factory($child));
52
                }
53
            }
54 60
            return new Sort($sortingKeys);
55
        }
56 138
        return null;
57
    }
58
59 60
    public function __construct(ArrayListInterface $sortingKeys)
60
    {
61 60
        $this->sortingKeys = $sortingKeys;
0 ignored issues
show
Documentation Bug introduced by
$sortingKeys is of type Seboettg\Collection\ArrayList\ArrayListInterface, but the property $sortingKeys was declared to be of type Seboettg\Collection\ArrayList. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
62 60
    }
63
64
    /**
65
     * Function in order to sort a set of csl items by one or multiple sort keys.
66
     * Sort keys are evaluated in sequence. A primary sort is performed on all items using the first sort key.
67
     * A secondary sort, using the second sort key, is applied to items sharing the first sort key value. A tertiary
68
     * sort, using the third sort key, is applied to items sharing the first and second sort key values. Sorting
69
     * continues until either the order of all items is fixed, or until the sort keys are exhausted. Items with an
70
     * empty sort key value are placed at the end of the sort, both for ascending and descending sorts.
71
     *
72
     * @param DataList|array $data reference
73
     */
74 50
    public function sort(&$data)
75
    {
76 50
        if (is_array($data)) {
77
            $data = new DataList(...$data);
78
        }
79 50
        $dataToSort = $data->toArray();
80
        try {
81 50
            $data->replace($this->performSort(0, $dataToSort));
82
        } catch (CiteProcException $e) {
83
            //nothing to do, because $data is passed by referenced
84
        }
85 50
    }
86
87
    /**
88
     * Recursive function in order to sort a set of csl items by one or multiple sort keys.
89
     * All items will be distributed by the value (defined in respective sort key) in an associative array (grouped).
90
     * Afterwards the array will be sorted by the array key. If a further sort key exist, each of these groups will be
91
     * sorted by a recursive function call. Finally the array will be flatted.
92
     *
93
     * @param $keyNumber
94
     * @param array $dataToSort
95
     * @return array
96
     * @throws CiteProcException
97
     */
98 50
    private function performSort($keyNumber, $dataToSort)
99
    {
100 50
        if (count($dataToSort) < 2) {
101 23
            return $dataToSort;
102
        }
103
104
        /** @var Key $key */
105 27
        $key = $this->sortingKeys->get($keyNumber);
106 27
        $variable = $key->getVariable();
107 27
        $groupedItems = [];
108
109 27
        if ($key->isDateVariable()) {
110 5
            if (DateHelper::hasDateRanges($dataToSort, $variable, "all")) {
111 1
                $newKey = clone $key;
112 1
                $newKey->setRangePart(2);
113 1
                $key->setRangePart(1);
114 1
                $this->sortingKeys->append($newKey);
115
            }
116
        }
117
118
        //grouping by value
119 27
        foreach ($dataToSort as $citationNumber => $dataItem) {
120 27
            if ($key->isNameVariable()) {
121 3
                $sortKey = Variables::nameHash($dataItem, $variable);
122 26
            } elseif ($key->isNumberVariable()) {
123
                $sortKey = $dataItem->{$variable};
124 26
            } elseif ($key->isDateVariable()) {
125 5
                $sortKey = DateHelper::getSortKeyDate($dataItem, $key);
126 21
            } elseif ($key->isMacro()) {
127 14
                $sortKey = mb_strtolower(strip_tags(CiteProc::getContext()->getMacro(
128 14
                    $key->getMacro()
129 14
                )->render($dataItem, $citationNumber)));
130 9
            } elseif ($variable === "citation-number") {
131 6
                $sortKey = $citationNumber + 1;
132
            } else {
133 3
                $sortKey = mb_strtolower(strip_tags($dataItem->{$variable}));
134
            }
135 27
            $groupedItems[$sortKey][] = $dataItem;
136
        }
137
138 27
        if ($this->sortingKeys->count() > ++$keyNumber) {
139 13
            foreach ($groupedItems as $group => &$array) {
140 13
                if (count($array) > 1) {
141 13
                    $array = $this->performSort($keyNumber, $array);
142
                }
143
            }
144
        }
145
146
        //sorting by array keys
147 27
        if ($key->getSort() === "ascending") {
148 25
            ksort($groupedItems); //ascending
149
        } else {
150 4
            krsort($groupedItems); //reverse
151
        }
152
153
        //the flattened array is the result
154 27
        $sortedDataGroups = array_values($groupedItems);
155 27
        return $this->flatten($sortedDataGroups);
156
    }
157
158 27
    public function flatten($array)
159
    {
160 27
        $returnArray = [];
161
        array_walk_recursive($array, function ($a) use (&$returnArray) {
162 27
            $returnArray[] = $a;
163 27
        });
164 27
        return $returnArray;
165
    }
166
167
    /**
168
     * @return ArrayList
169
     */
170
    public function getSortingKeys()
171
    {
172
        return $this->sortingKeys;
173
    }
174
}
175