AbstractParser::getContextFromToken()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 8
c 0
b 0
f 0
nc 3
nop 1
dl 0
loc 15
ccs 10
cts 10
cp 1
crap 3
rs 10
1
<?php
2
/**
3
 * This file is part of graze/unicontroller-client.
4
 *
5
 * Copyright (c) 2016 Nature Delivered Ltd. <https://www.graze.com>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 *
10
 * @license https://github.com/graze/unicontroller-client/blob/master/LICENSE.md
11
 * @link https://github.com/graze/unicontroller-client
12
 */
13
namespace Graze\UnicontrollerClient\Parser\Parser;
14
15
use Graze\UnicontrollerClient\Parser\Parser\ParserInterface;
16
use Graze\UnicontrollerClient\Entity\EntityHydrator;
17
use Graze\UnicontrollerClient\Parser\ArrayParser;
18
use Graze\UnicontrollerClient\Parser\BinaryDataParser;
19
20
abstract class AbstractParser implements ParserInterface
21
{
22
    const CONTEXT_NONE = 1;
23
    const CONTEXT_ANSWER = 2;
24
    const CONTEXT_FLUSH = 3;
25
    const CONTEXT_STRING = 4;
26
    const CONTEXT_ARRAY = 5;
27
    const CONTEXT_BINARY = 6;
28
29
    /**
30
     * @var EntityHydrator
31
     */
32
    private $entityHydrator;
33
34
    /**
35
     * @var ArrayParser
36
     */
37
    private $arrayParser;
38
39
    /**
40
     * @var BinaryDataParser
41
     */
42
    private $binaryDataParser;
43
44
    /**
45
     * @var []
0 ignored issues
show
Documentation Bug introduced by
The doc comment [] at position 0 could not be parsed: Unknown type name '[' at position 0 in [].
Loading history...
46
     */
47
    private $contextCurrentToTokensToContextNew = [
48
        self::CONTEXT_NONE => [
49
            ',' => self::CONTEXT_FLUSH,
50
            '=' => self::CONTEXT_ANSWER,
51
            "\x02" => self::CONTEXT_STRING,
52
            'BinaryData' => self::CONTEXT_BINARY,
53
            "\x02LineItem\x03" => self::CONTEXT_ARRAY,
54
            "\x02BoxItem\x03" => self::CONTEXT_ARRAY,
55
            "\x02TtfItem\x03" => self::CONTEXT_ARRAY,
56
            "\x02BarcodeItem\x03" => self::CONTEXT_ARRAY,
57
            "\x02PictureItem\x03" => self::CONTEXT_ARRAY,
58
            "\x02VarPrompt\x03" => self::CONTEXT_ARRAY,
59
            "\x02VarSeq\x03" => self::CONTEXT_ARRAY,
60
            "\x02VarRtc\x03" => self::CONTEXT_ARRAY,
61
            "\x02VarDatabase\x03" => self::CONTEXT_ARRAY,
62
            "\x02VarUserId\x03" => self::CONTEXT_ARRAY,
63
            "\x02VarShiftCode\x03" => self::CONTEXT_ARRAY,
64
            "\x02VarMachineId\x03" => self::CONTEXT_ARRAY,
65
            "\x02VarDatabaseField\x03" => self::CONTEXT_ARRAY,
66
            "\x02VarMacro\x03" => self::CONTEXT_ARRAY,
67
            "\x02VarMacroOutput\x03" => self::CONTEXT_ARRAY,
68
            "\x02VarSerial\x03" => self::CONTEXT_ARRAY,
69
            "\x02SettingsById\x03" => self::CONTEXT_ARRAY,
70
            "\x02ShiftDefinition\x03" => self::CONTEXT_ARRAY,
71
        ],
72
        self::CONTEXT_STRING => [
73
            "\x03" => self::CONTEXT_NONE,
74
        ],
75
        self::CONTEXT_ARRAY => [
76
            "\x02" => self::CONTEXT_STRING,
77
            "\r\n" => self::CONTEXT_NONE,
78
            'BinaryData' => self::CONTEXT_BINARY,
79
        ],
80
        self::CONTEXT_BINARY => [
81
            'BinaryEnd' => self::CONTEXT_NONE,
82
        ]
83
    ];
84
85
    /**
86
     * @var []
0 ignored issues
show
Documentation Bug introduced by
The doc comment [] at position 0 could not be parsed: Unknown type name '[' at position 0 in [].
Loading history...
87
     */
88
    private $contextStack = [
89
        self::CONTEXT_NONE
90
    ];
91
92
    /**
93
     * @var string
94
     */
95
    private $contextPrevious;
96
97
    /**
98
     * @var string
99
     */
100
    private $buffer = '';
101
102
    /**
103
     * @var bool
104
     */
105
    private $arrayInitialised = false;
106
107
    /**
108
     * @var string
109
     */
110
    private $arrayName;
111
112
    /**
113
     * @var int
114
     */
115
    private $arrayLength;
116
117
    /**
118
     * @var int
119
     */
120
    private $arrayItemCount;
121
122
    /**
123
     * @var []
0 ignored issues
show
Documentation Bug introduced by
The doc comment [] at position 0 could not be parsed: Unknown type name '[' at position 0 in [].
Loading history...
124
     */
125
    private $propertyValues = [];
126
127
    /**
128
     * @param EntityHydrator $entityHydrator
129
     * @param ArrayParser $arrayParser
130
     * @param BinaryDataParser $binaryDataParser
131
     */
132 19
    public function __construct(EntityHydrator $entityHydrator, ArrayParser $arrayParser, BinaryDataParser $binaryDataParser)
133
    {
134 19
        $this->entityHydrator = $entityHydrator;
135 19
        $this->arrayParser = $arrayParser;
136 19
        $this->binaryDataParser = $binaryDataParser;
137 19
    }
138
139
    /**
140
     * @param string $string
141
     * @return []
0 ignored issues
show
Documentation Bug introduced by
The doc comment [] at position 0 could not be parsed: Unknown type name '[' at position 0 in [].
Loading history...
142
     */
143 19
    public function parse($string)
144
    {
145 19
        for ($pointer = 0; $pointer < strlen($string); $pointer++) {
146 19
            $this->buffer .= $string[$pointer];
147
148 19
            while (true) {
149 19
                $contextNext = $this->getContextFromToken($this->buffer);
150 19
                if (!$contextNext) {
151
                    // nothing found in the string, move to next character
152 19
                    break;
153
                }
154
155
                // we found a token, attempt to change context
156 19
                $contextChanged = $this->changeContext($contextNext);
0 ignored issues
show
Bug introduced by
It seems like $contextNext can also be of type true; however, parameter $context of Graze\UnicontrollerClien...Parser::changeContext() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

156
                $contextChanged = $this->changeContext(/** @scrutinizer ignore-type */ $contextNext);
Loading history...
157 19
                if (!$contextChanged) {
158
                    // no change, move to next character
159 17
                    break;
160
                };
161
162
                // context has changed, repeat this loop in the new context
163 18
            }
164 19
        }
165
166
        // final flush
167 19
        $this->flushBuffer();
168
169 19
        $data = array_combine($this->getProperties(), $this->propertyValues);
170 19
        $this->propertyValues = [];
171
172 19
        return $this->entityHydrator->hydrate($this->getEntity(), $data);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->entityHydr...is->getEntity(), $data) returns the type Graze\UnicontrollerClien...\Entity\EntityInterface which is incompatible with the return type mandated by Graze\UnicontrollerClien...arserInterface::parse() of Graze\UnicontrollerClien...\Entity\EntityInterface.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
Bug introduced by
It seems like $data can also be of type false; however, parameter $data of Graze\UnicontrollerClien...tityHydrator::hydrate() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

172
        return $this->entityHydrator->hydrate($this->getEntity(), /** @scrutinizer ignore-type */ $data);
Loading history...
173
    }
174
175
    /**
176
     * @param string $string
177
     * @return string|bool
178
     */
179 19
    private function getContextFromToken($string)
180
    {
181 19
        $tokensToContextNew = $this->contextCurrentToTokensToContextNew[$this->getContextCurrent()];
182 19
        foreach ($tokensToContextNew as $token => $contextNew) {
183 19
            $offset = strlen($token) * -1;
184 19
            $subject = substr($string, $offset);
185
186 19
            if ($subject != $token) {
187 19
                continue;
188
            }
189
190 19
            return $contextNew;
191 19
        }
192
193 19
        return false;
194
    }
195
196
    /**
197
     * @return string
198
     */
199 19
    private function getContextCurrent()
200
    {
201 19
        return end($this->contextStack);
202
    }
203
204
    /**
205
     * @param string $context
206
     * @return bool
207
     */
208 19
    private function changeContext($context)
209
    {
210 19
        if ($context == self::CONTEXT_ANSWER) {
211
            $this->buffer = ''; // remove 'answerName='
212
            return false;
213
        }
214
215 19
        if ($context == self::CONTEXT_FLUSH) {
216 17
            $this->flushBuffer();
217 17
            $this->contextPrevious = self::CONTEXT_NONE;
218 17
            return false;
219
        }
220
221 18
        if ($context != self::CONTEXT_NONE) {
222
            // enter new context
223 18
            $this->contextStack[] = $context;
224 18
            return true;
225
        }
226
227 18
        if ($this->getContextCurrent() == self::CONTEXT_ARRAY) {
228 2
            if (!$this->arrayInitialised) {
229
                // array not yet initialised
230 2
                $this->arrayInit();
231 2
            }
232
233 2
            if ($this->arrayItemCount > 0) {
234 2
                $this->arrayItemCount--;
235
                // not enough items, do not leave context yet
236 2
                return false;
237
            }
238 2
        }
239
240
        // exit current context
241 18
        $this->contextPrevious = array_pop($this->contextStack);
242 18
        return true;
243
    }
244
245 19
    private function flushBuffer()
246
    {
247 19
        switch ($this->contextPrevious) {
248 19
            case self::CONTEXT_ARRAY:
249 2
                $propertyValue = $this->arrayParser->parse($this->arrayName, $this->arrayLength, $this->buffer);
250 2
                $this->arrayClear();
251 2
                break;
252
253 19
            case self::CONTEXT_BINARY:
254 2
                if ($this->getContextCurrent() != self::CONTEXT_NONE) {
255
                    break;
256
                }
257
258 2
                $propertyValue = $this->binaryDataParser->parse($this->buffer);
259 2
                break;
260
261 19
            default:
262 19
                $propertyValue = trim($this->buffer, "\x02\x03,");
263 19
        }
264
265 19
        $this->propertyValues[] = $propertyValue;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $propertyValue does not seem to be defined for all execution paths leading up to this point.
Loading history...
266 19
        $this->buffer = '';
267 19
    }
268
269 2
    private function arrayInit()
270
    {
271 2
        list($name, $length) = explode(',', $this->buffer, 2);
272
273 2
        $this->arrayName = trim($name, "\x02\x03");
274 2
        $this->arrayLength = trim($length, ",\r\n");
0 ignored issues
show
Documentation Bug introduced by
The property $arrayLength was declared of type integer, but trim($length, ', ') is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
275 2
        $this->arrayItemCount = $this->arrayLength;
0 ignored issues
show
Documentation Bug introduced by
The property $arrayItemCount was declared of type integer, but $this->arrayLength is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
276 2
        $this->arrayInitialised = true;
277
278 2
        $this->buffer = '';
279 2
    }
280
281 2
    private function arrayClear()
282
    {
283 2
        $this->arrayName = null;
284 2
        $this->arrayLength = 0;
285 2
        $this->arrayItemCount = 0;
286 2
        $this->arrayInitialised = null;
287 2
    }
288
289
    /**
290
     * @return array
291
     */
292
    abstract protected function getProperties();
293
294
    /**
295
     * @return Graze\UnicontrollerClient\Entity\Entity\EntityInterface
0 ignored issues
show
Bug introduced by
The type Graze\UnicontrollerClien...\Entity\EntityInterface was not found. Did you mean Graze\UnicontrollerClien...\Entity\EntityInterface? If so, make sure to prefix the type with \.
Loading history...
296
     */
297
    abstract protected function getEntity();
298
299
    /**
300
     * @return ParserInterface
301
     */
302 19
    public static function factory()
303
    {
304 19
        return new static(
305 19
            new EntityHydrator(),
306 19
            ArrayParser::factory(),
307 19
            new BinaryDataParser()
308 19
        );
309
    }
310
}
311