Passed
Push — master ( 6a3704...3c7f73 )
by Povilas
03:13
created

JoinMapper   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 247
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 3
Bugs 1 Features 0
Metric Value
wmc 43
eloc 81
c 3
b 1
f 0
dl 0
loc 247
ccs 85
cts 85
cp 1
rs 8.96

12 Methods

Rating   Name   Duplication   Size   Complexity  
A getByPath() 0 8 5
A __construct() 0 7 1
A add() 0 5 1
B getFields() 0 9 8
A addJoin() 0 26 5
A replaceOptions() 0 9 3
A buildListJoins() 0 18 5
A getField() 0 13 3
A build() 0 6 1
A buildFilterJoins() 0 10 6
A getPath() 0 11 2
A buildJoins() 0 13 3

How to fix   Complexity   

Complex Class

Complex classes like JoinMapper often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use JoinMapper, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Povs\ListerBundle\Mapper;
4
5
use Doctrine\Common\Collections\ArrayCollection;
6
7
/**
8
 * @author Povilas Margaiatis <[email protected]>
9
 *
10
 * @property JoinField[]|ArrayCollection $fields
11
 * @method JoinField get(string $id)
12
 */
13
class JoinMapper extends AbstractMapper
14
{
15
    private const REPLACEABLE_OPTIONS = [
16
        JoinField::OPTION_JOIN_TYPE,
17
        JoinField::OPTION_CONDITION_TYPE,
18
        JoinField::OPTION_CONDITION,
19
        JoinField::OPTION_CONDITION_PARAMETERS
20
    ];
21
22
    /**
23
     * @var ListMapper
24
     */
25
    private $listMapper;
26
27
    /**
28
     * @var FilterMapper
29
     */
30
    private $filterMapper;
31
32
    /**
33
     * JoinMapper constructor.
34
     *
35
     * @param ListMapper   $listMapper   fully built list mapper
36
     * @param FilterMapper $filterMapper fully build filter mapper
37
     */
38 9
    public function __construct(
39
        ListMapper $listMapper,
40
        FilterMapper $filterMapper
41
    ) {
42 9
        parent::__construct();
43 9
        $this->listMapper = $listMapper;
44 9
        $this->filterMapper = $filterMapper;
45 9
    }
46
47
    /**
48
     * @param string $path   ORM path to join
49
     * @param string $alias  join as alias
50
     * @param array $options
51
     *
52
     * @return JoinMapper
53
     */
54 8
    public function add(string $path, string $alias, array $options = []): JoinMapper
55
    {
56 8
        $this->addJoin($path, $options, $alias);
57
58 8
        return $this;
59
    }
60
61
    /**
62
     * @param string $path
63
     * @param bool   $lazy
64
     *
65
     * @return JoinField|null
66
     */
67 11
    public function getByPath(string $path, bool $lazy = false): ?JoinField
68
    {
69
        $field = $this->fields->filter(static function (JoinField $field) use ($path, $lazy) {
70 7
            return ($field->getAlias() === $path || $field->getPath() === $path || $field->getJoinPath(null) === $path)
71 7
                && $field->getOption(JoinField::OPTION_LAZY) === $lazy;
72 11
        })->first();
73
74 11
        return $field ?: null;
75
    }
76
77
    /**
78
     * @param string|null $path
79
     * @param bool|null   $lazy
80
     *
81
     * @return ArrayCollection|JoinField[]
82
     */
83 8
    public function getFields(?string $path = null, ?bool $lazy = null): ArrayCollection
84
    {
85 8
        if (null === $path && null === $lazy) {
86 6
            return $this->fields;
87
        }
88
89
        return $this->fields->filter(static function (JoinField $field) use ($path, $lazy) {
90 4
            return (null === $path || ($field->getAlias() === $path || $field->getPath() === $path || $field->getJoinPath(null) === $path))
91 4
                && (null === $lazy || $field->getOption(JoinField::OPTION_LAZY) === $lazy);
92 4
        });
93
    }
94
95
    /**
96
     * @return JoinMapper
97
     */
98 2
    public function build(): self
99
    {
100 2
        $this->buildListJoins();
101 2
        $this->buildFilterJoins();
102
103 2
        return $this;
104
    }
105
106 2
    private function buildListJoins(): void
107
    {
108 2
        foreach ($this->listMapper->getFields() as $field) {
109 1
            $paths = $field->getPaths();
110 1
            $joinType = $field->getOption(ListField::OPTION_JOIN_TYPE);
111 1
            $lazy = $field->getOption(ListField::OPTION_LAZY);
112
113 1
            $this->buildJoins($paths, $joinType, $lazy);
114
115
            if (
116 1
                $field->getOption(ListField::OPTION_SORTABLE) &&
117 1
                $field->getOption(ListField::OPTION_SORT_VALUE)
118
            ) {
119 1
                if ($sortPath = $field->getOption(ListField::OPTION_SORT_PATH)) {
120 1
                    $paths = (array) $sortPath;
121
                }
122
123 1
                $this->buildJoins($paths, $joinType, false);
124
            }
125
        }
126 2
    }
127
128 2
    private function buildFilterJoins(): void
129
    {
130 2
        foreach ($this->filterMapper->getFields() as $field) {
131 1
            if ($field->hasValue()) {
132 1
                $paths = $field->getPaths();
133 1
                $joinType = $field->getOption(FilterField::OPTION_JOIN_TYPE);
134 1
                $mapped = $field->getOption(FilterField::OPTION_MAPPED);
135
136 1
                if (!empty($paths) && $joinType && $mapped) {
137 1
                    $this->buildJoins($paths, $joinType, false);
138
                }
139
            }
140
        }
141 2
    }
142
143
    /**
144
     * @param array  $paths
145
     * @param string $joinType
146
     * @param bool   $lazy
147
     */
148 2
    private function buildJoins(array $paths, string $joinType, bool $lazy): void
149
    {
150 2
        foreach ($paths as $path) {
151 2
            if (!$path = $this->getPath($path)) {
152 2
                continue;
153
            }
154
155
            $options = [
156 2
                JoinField::OPTION_JOIN_TYPE => $joinType,
157 2
                JoinField::OPTION_LAZY => $lazy
158
            ];
159
160 2
            $this->addJoin($path, $options, null);
161
        }
162 2
    }
163
164
    /**
165
     * @param string      $path         join path
166
     * @param array       $options      JoinField options
167
     * @param string|null $alias        join alias. If null - alias will be auto generated by replacing "." with "_"
168
     *                                  For example path = entity.parentEntity => alias = entity_parentEntity
169
     *
170
     * @return JoinField|null if nothing was joined
171
     */
172 10
    private function addJoin(string $path, array $options, ?string $alias = null): ?JoinField
173
    {
174 10
        if ($joinField = $this->getField($path, $options, $alias)) {
175 4
            return $joinField;
176
        }
177
178 9
        $pathElements = explode('.', $path);
179 9
        $pathCount = count($pathElements);
180 9
        $prop = array_pop($pathElements);
181
182 9
        if ($pathCount > 1) {
183 3
            $parent = $this->addJoin(implode('.', $pathElements), $options, null);
184
        } else {
185 9
            $parent = null;
186
        }
187
188 9
        $path = $parent ? sprintf('%s.%s', $parent->getPath(), $prop) : $prop;
189
190 9
        if (!$alias) {
191 3
            $alias = sprintf('%s_a', str_replace('.', '_', $path));
192
        }
193
194 9
        $joinField = new JoinField($path, $prop, $alias, $options, $parent);
195 9
        $this->addField($joinField);
196
197 9
        return $joinField;
198
    }
199
200
    /**
201
     * Removes last element (attribute) from path string
202
     * For example order.user.name => order.user
203
     *
204
     * @param string $fullPath
205
     *
206
     * @return string|null
207
     */
208 2
    private function getPath(string $fullPath): ?string
209
    {
210 2
        $pathElements = explode('.', $fullPath);
211
212 2
        if (count($pathElements) === 1) {
213 2
            return null;
214
        }
215
216 2
        array_pop($pathElements);
217
218 2
        return implode('.', $pathElements);
219
    }
220
221
    /**
222
     * Finds whether field already exists and if so sets it's alias and options
223
     *
224
     * @param string      $path
225
     * @param array       $options
226
     * @param string|null $alias
227
     *
228
     * @return JoinField|null
229
     */
230 10
    private function getField(string $path, array $options, ?string $alias): ?JoinField
231
    {
232 10
        $joinField = $this->getByPath($path, $options[JoinField::OPTION_LAZY] ?? false);
233
234 10
        if (!$joinField) {
235 9
            return null;
236
        }
237
238 4
        if ($alias) {
239 1
            $joinField->setAlias($alias);
240
        }
241
242 4
        return $this->replaceOptions($joinField, $options);
243
    }
244
245
    /**
246
     * @param JoinField $field
247
     * @param array     $options
248
     *
249
     * @return JoinField
250
     */
251 4
    private function replaceOptions(JoinField $field, array $options): JoinField
252
    {
253 4
        foreach (self::REPLACEABLE_OPTIONS as $optionToReplace) {
254 4
            if (isset($options[$optionToReplace])) {
255 4
                $field->setOption($optionToReplace, $options[$optionToReplace]);
256
            }
257
        }
258
259 4
        return $field;
260
    }
261
}
262