Completed
Push — master ( 22fd50...798002 )
by Neomerx
03:53
created

BlockSerializer::addBlock()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 17
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 17
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 12
nc 5
nop 1
1
<?php namespace Limoncello\Validation\Execution;
2
3
/**
4
 * Copyright 2015-2017 [email protected]
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 * http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 */
18
19
use Limoncello\Validation\Contracts\Blocks\AndExpressionInterface;
20
use Limoncello\Validation\Contracts\Blocks\ExecutionBlockInterface;
21
use Limoncello\Validation\Contracts\Blocks\IfExpressionInterface;
22
use Limoncello\Validation\Contracts\Blocks\OrExpressionInterface;
23
use Limoncello\Validation\Contracts\Blocks\ProcedureBlockInterface;
24
use Limoncello\Validation\Contracts\Execution\BlockSerializerInterface;
25
use Limoncello\Validation\Exceptions\UnknownExecutionBlockType;
26
27
/**
28
 * @package Limoncello\Validation
29
 */
30
final class BlockSerializer implements BlockSerializerInterface
31
{
32
    /**
33
     * Index of the first block.
34
     */
35
    const FIRST_BLOCK_INDEX = 0;
36
37
    /**
38
     * @var int
39
     */
40
    private $currentBlockIndex = 0;
41
42
    /**
43
     * @var array
44
     */
45
    private $serializedBlocks = [];
46
47
    /**
48
     * @var int[]
49
     */
50
    private $blocksWithStart = [];
51
52
    /**
53
     * @var int[]
54
     */
55
    private $blocksWithEnd = [];
56
57
    /**
58
     * @inheritdoc
59
     */
60
    public function serialize(ExecutionBlockInterface $block): BlockSerializerInterface
61
    {
62
        $this->reset()->addBlock($block);
63
64
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (Limoncello\Validation\Execution\BlockSerializer) is incompatible with the return type declared by the interface Limoncello\Validation\Co...zerInterface::serialize of type self.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
65
    }
66
67
    /**
68
     * @inheritdoc
69
     *
70
     * @SuppressWarnings(PHPMD.ElseExpression)
71
     */
72
    public function addBlock(ExecutionBlockInterface $block): int
73
    {
74
        if ($block instanceof ProcedureBlockInterface) {
75
            $index = $this->serializeProcedure($block);
76
        } elseif ($block instanceof IfExpressionInterface) {
77
            $index = $this->serializeIfExpression($block);
78
        } elseif ($block instanceof AndExpressionInterface) {
79
            $index = $this->serializeAndExpression($block);
80
        } elseif ($block instanceof OrExpressionInterface) {
81
            $index = $this->serializeOrExpression($block);
82
        } else {
83
            // unknown execution block type
84
            throw new UnknownExecutionBlockType();
85
        }
86
87
        return $index;
88
    }
89
90
    /**
91
     * @return array
92
     */
93
    public function getSerializedBlocks(): array
94
    {
95
        return $this->serializedBlocks;
96
    }
97
98
    /**
99
     * @inheritdoc
100
     */
101
    public function getBlocksWithStart(): array
102
    {
103
        return $this->blocksWithStart;
104
    }
105
106
    /**
107
     * @inheritdoc
108
     */
109
    public function getBlocksWithEnd(): array
110
    {
111
        return $this->blocksWithEnd;
112
    }
113
114
    /**
115
     * @inheritdoc
116
     */
117
    public function clearBlocks(): BlockSerializerInterface
118
    {
119
        $this->currentBlockIndex = 0;
120
        $this->serializedBlocks  = [];
121
122
123
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (Limoncello\Validation\Execution\BlockSerializer) is incompatible with the return type declared by the interface Limoncello\Validation\Co...rInterface::clearBlocks of type self.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
124
    }
125
126
    /**
127
     * @inheritdoc
128
     */
129
    public function clearBlocksWithStart(): BlockSerializerInterface
130
    {
131
        $this->blocksWithStart = [];
132
133
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (Limoncello\Validation\Execution\BlockSerializer) is incompatible with the return type declared by the interface Limoncello\Validation\Co...e::clearBlocksWithStart of type self.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
134
    }
135
136
    /**
137
     * @inheritdoc
138
     */
139
    public function clearBlocksWithEnd(): BlockSerializerInterface
140
    {
141
        $this->blocksWithEnd = [];
142
143
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (Limoncello\Validation\Execution\BlockSerializer) is incompatible with the return type declared by the interface Limoncello\Validation\Co...ace::clearBlocksWithEnd of type self.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
144
    }
145
146
    /**
147
     * @inheritdoc
148
     */
149
    public function get(): array
150
    {
151
        return [
152
            static::SERIALIZATION_BLOCKS            => $this->getSerializedBlocks(),
153
            static::SERIALIZATION_BLOCKS_WITH_START => $this->getBlocksWithStart(),
154
            static::SERIALIZATION_BLOCKS_WITH_END   => $this->getBlocksWithEnd(),
155
        ];
156
    }
157
158
    /**
159
     * @inheritdoc
160
     */
161
    public static function unserializeBlocks(array $serialized): array
162
    {
163
        assert(count($serialized) === 3); // blocks, starts, ends
164
165
        return static::readProperty(static::SERIALIZATION_BLOCKS, $serialized);
0 ignored issues
show
Comprehensibility introduced by
Since Limoncello\Validation\Execution\BlockSerializer is declared final, using late-static binding will have no effect. You might want to replace static with self instead.

Late static binding only has effect in subclasses. A final class cannot be extended anymore so late static binding cannot occurr. Consider replacing static:: with self::.

To learn more about late static binding, please refer to the PHP core documentation.

Loading history...
166
    }
167
168
    /**
169
     * @inheritdoc
170
     */
171
    public static function unserializeBlocksWithStart(array $serialized): array
172
    {
173
        assert(count($serialized) === 3); // blocks, starts, ends
174
175
        return static::readProperty(static::SERIALIZATION_BLOCKS_WITH_START, $serialized);
0 ignored issues
show
Comprehensibility introduced by
Since Limoncello\Validation\Execution\BlockSerializer is declared final, using late-static binding will have no effect. You might want to replace static with self instead.

Late static binding only has effect in subclasses. A final class cannot be extended anymore so late static binding cannot occurr. Consider replacing static:: with self::.

To learn more about late static binding, please refer to the PHP core documentation.

Loading history...
176
    }
177
178
    /**
179
     * @inheritdoc
180
     */
181
    public static function unserializeBlocksWithEnd(array $serialized): array
182
    {
183
        assert(count($serialized) === 3); // blocks, starts, ends
184
185
        return static::readProperty(static::SERIALIZATION_BLOCKS_WITH_END, $serialized);
0 ignored issues
show
Comprehensibility introduced by
Since Limoncello\Validation\Execution\BlockSerializer is declared final, using late-static binding will have no effect. You might want to replace static with self instead.

Late static binding only has effect in subclasses. A final class cannot be extended anymore so late static binding cannot occurr. Consider replacing static:: with self::.

To learn more about late static binding, please refer to the PHP core documentation.

Loading history...
186
    }
187
188
    /**
189
     * @param int   $key
190
     * @param array $properties
191
     *
192
     * @return mixed
193
     */
194
    private static function readProperty(int $key, array $properties)
195
    {
196
        assert(array_key_exists($key, $properties));
197
198
        return $properties[$key];
199
    }
200
201
    /**
202
     * @return BlockSerializerInterface
203
     */
204
    private function reset(): BlockSerializerInterface
205
    {
206
        return $this->clearBlocks()->clearBlocksWithStart()->clearBlocksWithEnd();
207
    }
208
209
    /**
210
     * @param ProcedureBlockInterface $procedure
211
     *
212
     * @return int
213
     */
214
    private function serializeProcedure(ProcedureBlockInterface $procedure): int
215
    {
216
        $index = $this->allocateIndex();
217
218
        // TODO add signature checks for callable
219
220
        $serialized = [
221
            static::TYPE                       => static::TYPE__PROCEDURE,
222
            static::PROCEDURE_EXECUTE_CALLABLE => $procedure->getExecuteCallable(),
223
        ];
224
        if ($procedure->getStartCallable() !== null) {
225
            $serialized[static::PROCEDURE_START_CALLABLE] = $procedure->getStartCallable();
226
            $this->blocksWithStart[]                      = $index;
227
        }
228
        if ($procedure->getEndCallable() !== null) {
229
            $serialized[static::PROCEDURE_END_CALLABLE] = $procedure->getEndCallable();
230
            $this->blocksWithEnd[]                      = $index;
231
        }
232
        if (empty($procedure->getProperties()) === false) {
233
            $serialized[static::PROPERTIES] = $procedure->getProperties();
234
        }
235
236
        $this->serializedBlocks[$index] = $serialized;
237
238
        return $index;
239
    }
240
241
    /**
242
     * @param IfExpressionInterface $ifExpression
243
     *
244
     * @return int
245
     */
246
    private function serializeIfExpression(IfExpressionInterface $ifExpression): int
247
    {
248
        $index = $this->allocateIndex();
249
250
        // TODO add signature checks for callable
251
252
        $serialized = [
253
            static::TYPE                             => static::TYPE__IF_EXPRESSION,
254
            static::IF_EXPRESSION_CONDITION_CALLABLE => $ifExpression->getConditionCallable(),
255
        ];
256
257
        assert($ifExpression->getOnTrue() !== null || $ifExpression->getOnFalse() !== null);
258
259
        if ($ifExpression->getOnTrue() !== null) {
260
            $serialized[static::IF_EXPRESSION_ON_TRUE_BLOCK] = $this->addBlock($ifExpression->getOnTrue());
261
        }
262
        if ($ifExpression->getOnFalse() !== null) {
263
            $serialized[static::IF_EXPRESSION_ON_FALSE_BLOCK] = $this->addBlock($ifExpression->getOnFalse());
264
        }
265
        if (empty($ifExpression->getProperties()) === false) {
266
            $serialized[static::PROPERTIES] = $ifExpression->getProperties();
267
        }
268
269
        $this->serializedBlocks[$index] = $serialized;
270
271
        return $index;
272
    }
273
274
    /**
275
     * @param AndExpressionInterface $andExpression
276
     *
277
     * @return int
278
     */
279 View Code Duplication
    private function serializeAndExpression(AndExpressionInterface $andExpression): int
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
280
    {
281
        $index = $this->allocateIndex();
282
283
        $serialized = [
284
            static::TYPE                     => static::TYPE__AND_EXPRESSION,
285
            static::AND_EXPRESSION_PRIMARY   => $this->addBlock($andExpression->getPrimary()),
286
            static::AND_EXPRESSION_SECONDARY => $this->addBlock($andExpression->getSecondary()),
287
        ];
288
        if (empty($andExpression->getProperties()) === false) {
289
            $serialized[static::PROPERTIES] = $andExpression->getProperties();
290
        }
291
292
        $this->serializedBlocks[$index] = $serialized;
293
294
        return $index;
295
    }
296
297
    /**
298
     * @param OrExpressionInterface $orExpression
299
     *
300
     * @return int
301
     */
302 View Code Duplication
    private function serializeOrExpression(OrExpressionInterface $orExpression): int
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
303
    {
304
        $index = $this->allocateIndex();
305
306
        $serialized = [
307
            static::TYPE                    => static::TYPE__OR_EXPRESSION,
308
            static::OR_EXPRESSION_PRIMARY   => $this->addBlock($orExpression->getPrimary()),
309
            static::OR_EXPRESSION_SECONDARY => $this->addBlock($orExpression->getSecondary()),
310
        ];
311
        if (empty($orExpression->getProperties()) === false) {
312
            $serialized[static::PROPERTIES] = $orExpression->getProperties();
313
        }
314
315
        $this->serializedBlocks[$index] = $serialized;
316
317
        return $index;
318
    }
319
320
    /**
321
     * @return int
322
     */
323
    private function allocateIndex(): int
324
    {
325
        $index = $this->currentBlockIndex++;
326
327
        return $index;
328
    }
329
}
330