Passed
Push — master ( dc8b1e...536096 )
by Povilas
04:01
created

JoinMapper::replaceOptions()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

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