Completed
Push — master ( 367ba0...cb743d )
by
unknown
12:47
created

SelectExclusionStrategy::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 5
ccs 4
cts 4
cp 1
rs 9.4285
cc 1
eloc 3
nc 1
nop 1
crap 1
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 string $path string dotted array
59
     *
60
     * @return array
61
     */
62
    private function createArrayByPath($path)
63
    {
64
        $keys = explode('.', $path);
65
        $val = true;
66
        $localArray = [];
67
        for ($i=count($keys)-1; $i>=0; $i--) {
68
            $localArray = [$keys[$i]=>$val];
69
            $val = $localArray;
70
        }
71
        return $localArray;
72
    }
73
74
    /**
75
     * Initializing $this->selectedFields and $this->isSelect
76
     * getting the fields that should be really serialized and setting the switch that there is actually a select
77
     * called once in the object, so shouldSkipProperty can use the information for every field
78
     * @return void
79
     */
80 4
    private function createSelectionTreeFromRQL()
81
    {
82 4
        $currentRequest = $this->requestStack->getCurrentRequest();
83 4
        $this->selectTree = [];
84 4
        $this->currentPath = [];
85 4
        $this->isSelect = false;
86
87
        /** @var SelectNode $select */
88 4
        if ($currentRequest
89
            && ($rqlQuery = $currentRequest->get('rqlQuery')) instanceof Query
90
            && $select = $rqlQuery->getSelect()
91
        ) {
92
            $this->isSelect = true;
93
            // Build simple selected field tree
94
            foreach ($select->getFields() as $field) {
95
                $field = str_replace('$', '', $field);
96
                $arr = $this->createArrayByPath($field);
97
                $this->selectTree = array_merge_recursive($this->selectTree, $arr);
98
            }
99
            $this->selectTree['id'] = true;
100
        }
101 4
    }
102
103
    /**
104
     * @InheritDoc: Whether the class should be skipped.
105
     * @param ClassMetadata $metadata         the ClassMetadata for the Class of the property to be serialized
106
     * @param Context       $navigatorContext the context for serialization
107
     * @return boolean
108
     */
109
    public function shouldSkipClass(ClassMetadata $metadata, Context $navigatorContext)
110
    {
111
        return false;
112
    }
113
114
    /**
115
     * @InheritDoc: Whether the property should be skipped.
116
     * Skipping properties who are not selected if there is a select in rql.
117
     * @param PropertyMetadata $property the property to be serialized
118
     * @param Context          $context  the context for serialization
119
     * @return boolean
120
     */
121
    public function shouldSkipProperty(PropertyMetadata $property, Context $context)
122
    {
123
        // nothing selected, default serialization
124
        if (! $this->isSelect) {
125
            return false;
126
        }
127
128
        // Level starts at 1, so -1 to have it level 0
129
        $depth = $context->getDepth()-1;
130
131
        // Here we build a level based array so we get them all
132
        $this->currentPath[$depth] = $property->name;
133
        $keyPath = [];
134
        foreach ($this->currentPath as $key => $path) {
135
            if ($key > $depth && array_key_exists($key, $this->currentPath)) {
136
                unset($this->currentPath[$key]);
137
            } else {
138
                $keyPath[] = $path;
139
            }
140
        }
141
142
        // check path and parent/son should be seen.
143
        $tree = $this->selectTree;
144
        foreach ($keyPath as $path) {
145
            if (!is_array($tree)) {
146
                break;
147
            }
148
            if (array_key_exists($path, $tree)) {
149
                $tree = $tree[$path];
150
            } else {
151
                return true;
152
            }
153
        }
154
        return false;
155
    }
156
}
157