Completed
Push — feature/EVO-7278-security-and-... ( ec06f5...19a53c )
by
unknown
29:18 queued 24:23
created

SelectExclusionStrategy::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 7
rs 9.4285
ccs 6
cts 6
cp 1
cc 1
eloc 5
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
14
/**
15
 * In this Strategy we skip all properties on first level who are not selected if there is a select in rql.
16
 *
17
 * @author   List of contributors <https://github.com/libgraviton/graviton/graphs/contributors>
18
 * @license  http://opensource.org/licenses/gpl-license.php GNU Public License
19
 * @link     http://swisscom.ch
20
 */
21
class SelectExclusionStrategy implements ExclusionStrategyInterface
22
{
23
    /**
24
     * @var RequestStack $requestStack
25
     */
26
    protected $requestStack;
27
28
    /**
29
     * @var Array $selectedFields contains all the selected fields and its combinations with nested fields
30
     */
31
    protected $selectedFields;
32
33
    /**
34
     * @var Array $selectedLeafs contains the leafs of the selection "tree"
35
     */
36
    protected $selectedLeafs;
37
38
    /**
39
     * @var Boolean $isSelect
40
     */
41
    protected $isSelect;
42
43
    /**
44
     * @var array $currentPath
45
     */
46
    protected $currentPath;
47
48
    /**
49
     * @var Integer $currentDepth
50
     */
51
    protected $currentDepth;
52
53
    /**
54
     * SelectExclusionStrategy constructor.
55
     * Comstructor Injection of the global request_stack to access the selected Fields via Query-Object
56
     * @param RequestStack $requestStack the global request_stack
57
     */
58 4
    public function __construct(RequestStack $requestStack)
59
    {
60 4
        $this->requestStack = $requestStack;
61 4
        $this->currentDepth = 0;
62 4
        $this->currentPath = [];
63 4
        $this->getSelectedFieldsFromRQL();
64 4
    }
65
66
    /**
67
     * Initializing $this->selectedFields and $this->isSelect
68
     * getting the fields that should be really serialized and setting the switch that there is actually a select
69
     * called once in the object, so shouldSkipProperty can use the information for every field
70
     * @return void
71
     */
72 4
    private function getSelectedFieldsFromRQL()
73
    {
74 4
        $currentRequest = $this->requestStack->getCurrentRequest();
75 4
        $this->selectedFields = [];
76 4
        $this->selectedLeafs = [];
77 4
        $this->isSelect = false;
78 2
        if ($currentRequest
79 4
            && ($rqlQuery = $currentRequest->get('rqlQuery')) instanceof Query
80 4
            && $select = $rqlQuery->getSelect()
81 2
        ) {
82
            $this->isSelect = true;
83
            // the selected fields are the leafs
84
            $this->selectedLeafs = $select->getFields();
85
            // get all combinations of leaf with nested fields
86
            foreach ($this->selectedLeafs as $selectedLeaf) {
87
                //clean up $
88
                $selectedLeaf = str_replace('$', '', $selectedLeaf);
89
                $this->selectedFields[] = $selectedLeaf;
90
                if (strstr($selectedLeaf, '.')) {
91
                    $nestedFields = explode('.', $selectedLeaf);
92
                    for ($i = 1; $i < count($nestedFields); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
93
                        $this->selectedFields[] = implode('.', array_slice($nestedFields, 0, $i));
94
                    }
95
                }
96
            }
97
            // id is always included in response (bug/feature)?
98
            if (!in_array('id', $this->selectedFields)) {
99
                $this->selectedFields[] = 'id';
100
            };
101
        }
102 4
    }
103
104
    /**
105
     * @InheritDoc: Whether the class should be skipped.
106
     * @param ClassMetadata $metadata         the ClassMetadata for the Class of the property to be serialized
107
     * @param Context       $navigatorContext the context for serialization
108
     * @return boolean
109
     */
110
    public function shouldSkipClass(ClassMetadata $metadata, Context $navigatorContext)
111
    {
112
        return false;
113
    }
114
115
    /**
116
     * @InheritDoc: Whether the property should be skipped.
117
     * Skipping properties who are not selected if there is a select in rql.
118
     * @param PropertyMetadata $property the property to be serialized
119
     * @param Context          $context  the context for serialization
120
     * @return boolean
121
     */
122
    public function shouldSkipProperty(PropertyMetadata $property, Context $context)
123
    {
124
        // nothing selected, default serialization
125
        if (! $this->isSelect) {
126
            return false;
127
        }
128
129
        // calculate the currentPath in the "tree" of the document to be serialized
130
        $depth = $context->getDepth() - 1;
131
        if ($depth <= $this->currentDepth) {
132
            // reduce the currentPath by one step
133
            array_pop($this->currentPath);
134
            // start a new currentPath on depth 0
135
            if ($depth == 0) {
136
                $this->currentPath = [];
137
            }
138
        }
139
        $this->currentPath[] = $property->name;
140
        $this->currentDepth = $depth;
0 ignored issues
show
Documentation Bug introduced by
It seems like $depth can also be of type double. However, the property $currentDepth is declared as type integer. Maybe add an additional type 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 mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
141
        $currentPath = implode('.', $this->currentPath);
142
143
        // test the currentpath
144
        $skip = ! in_array($currentPath, $this->selectedFields);
145
        // give it a second chance if its a nested path, go through all the selectedLeafs
146
        if ($this->currentDepth>0 && $skip) {
147
            foreach ($this->selectedLeafs as $leaf) {
148
                if (strstr($currentPath, $leaf)) {
149
                    $skip = false;
150
                    break;
151
                }
152
            }
153
        }
154
        return $skip;
155
    }
156
}
157