Completed
Push — feature/EVO-8123-rql-select-up... ( 826df9...4de408 )
by
unknown
15:44 queued 09:32
created

SelectExclusionStrategy::buildTreeFromArray()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 12
ccs 0
cts 10
cp 0
rs 9.4285
cc 3
eloc 8
nc 3
nop 2
crap 12
1
<?php
2
/**
3
 * Class for exclusion strategies.
4
 */
5
namespace Graviton\RestBundle\ExclusionStrategy;
6
7
use JMS\Serializer\Exclusion\ExclusionStrategyInterface;
8
use JMS\Serializer\Metadata\ClassMetadata;
9
use JMS\Serializer\Metadata\PropertyMetadata;
10
use JMS\Serializer\Context;
11
use Symfony\Component\HttpFoundation\RequestStack;
12
use Xiag\Rql\Parser\Query;
13
use Xiag\Rql\Parser\Node\SelectNode;
14
15
/**
16
 * In this Strategy we skip all properties on first level who are not selected if there is a select in rql.
17
 *
18
 * @author   List of contributors <https://github.com/libgraviton/graviton/graphs/contributors>
19
 * @license  http://opensource.org/licenses/gpl-license.php GNU Public License
20
 * @link     http://swisscom.ch
21
 */
22
class SelectExclusionStrategy implements ExclusionStrategyInterface
23
{
24
    /**
25
     * @var RequestStack $requestStack
26
     */
27
    protected $requestStack;
28
29
    /**
30
     * @var Boolean $isSelect
31
     */
32
    protected $isSelect;
33
34
    /**
35
     * @var array $currentPath
36
     */
37
    protected $currentPath;
38
39
    /**
40
     * @var array for selected tree level
41
     */
42
    protected $selectTree = [];
43
44
    /**
45
     * SelectExclusionStrategy constructor.
46
     * Comstructor Injection of the global request_stack to access the selected Fields via Query-Object
47
     * @param RequestStack $requestStack the global request_stack
48
     */
49 4
    public function __construct(RequestStack $requestStack)
50
    {
51 4
        $this->requestStack = $requestStack;
52 4
        $this->createSelectionTreeFromRQL();
53 4
    }
54
55
    /**
56
     * Convert dot string to array.
57
     *
58
     * @param array $array string dotted to array
59
     * @param array $tree  recursive arrayy
60
     *
61
     * @return array
62
     */
63
    private function buildTreeFromArray($array, $tree = [])
64
    {
65
        foreach ($array as $key => $value) {
66
            unset($array[$key]);
67
            $tree = [$value => []];
68
            if (!empty($array)) {
69
                $tree[$value] = $this->buildTreeFromArray($array, $tree);
70
            }
71
            break;
72
        }
73
        return $tree;
74
    }
75
76
    /**
77
     * Initializing $this->selectedFields and $this->isSelect
78
     * getting the fields that should be really serialized and setting the switch that there is actually a select
79
     * called once in the object, so shouldSkipProperty can use the information for every field
80
     * @return void
81
     */
82 4
    private function createSelectionTreeFromRQL()
83
    {
84 4
        $currentRequest = $this->requestStack->getCurrentRequest();
85 4
        $this->selectTree = [];
86 4
        $this->currentPath = [];
87 4
        $this->isSelect = false;
88
89
        /** @var SelectNode $select */
90 2
        if ($currentRequest
91 4
            && ($rqlQuery = $currentRequest->get('rqlQuery')) instanceof Query
92 4
            && $select = $rqlQuery->getSelect()
93 2
        ) {
94
            $this->isSelect = true;
95
            // Build simple selected field tree
96
            foreach ($select->getFields() as $field) {
97
                $field = str_replace('$', '', $field);
98
                $tree = $this->buildTreeFromArray(explode('.', $field));
99
                $this->selectTree = array_merge_recursive($this->selectTree, $tree);
100
            }
101
            $this->selectTree['id'] = true;
102
        }
103 4
    }
104
105
    /**
106
     * @InheritDoc: Whether the class should be skipped.
107
     * @param ClassMetadata $metadata         the ClassMetadata for the Class of the property to be serialized
108
     * @param Context       $navigatorContext the context for serialization
109
     * @return boolean
110
     */
111
    public function shouldSkipClass(ClassMetadata $metadata, Context $navigatorContext)
112
    {
113
        return false;
114
    }
115
116
    /**
117
     * @InheritDoc: Whether the property should be skipped.
118
     * Skipping properties who are not selected if there is a select in rql.
119
     * @param PropertyMetadata $property the property to be serialized
120
     * @param Context          $context  the context for serialization
121
     * @return boolean
122
     */
123
    public function shouldSkipProperty(PropertyMetadata $property, Context $context)
124
    {
125
        // nothing selected, default serialization
126
        if (! $this->isSelect) {
127
            return false;
128
        }
129
130
        // Level starts at 1, so -1 to have it level 0
131
        $depth = $context->getDepth()-1;
132
133
        // Here we build a level based array so we get them all
134
        $this->currentPath[$depth] = $property->name;
135
        $keyPath = [];
136
        foreach ($this->currentPath as $key => $path) {
137
            if ($key > $depth && array_key_exists($key, $this->currentPath)) {
138
                unset($this->currentPath[$key]);
139
            } else {
140
                $keyPath[] = $path;
141
            }
142
        }
143
144
        // check path and parent/son should be seen.
145
        $tree = $this->selectTree;
146
        foreach ($keyPath as $path) {
147
            if (empty($tree)) {
148
                break;
149
            }
150
            if (array_key_exists($path, $tree)) {
151
                $tree = $tree[$path];
152
            } else {
153
                return true;
154
            }
155
        }
156
        return false;
157
    }
158
}
159