Passed
Push — master ( 443842...86f0b2 )
by Joshua
05:55 queued 01:59
created

Model::count()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 9
ccs 0
cts 7
cp 0
crap 6
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SeamsCMS\Delivery\Model;
6
7
use Symfony\Component\PropertyAccess\PropertyAccess;
8
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
9
10
class Model
11
{
12
    const TYPE_STRING = "string";
13
    const TYPE_INTEGER = "integer";
14
    const TYPE_BOOLEAN = "boolean";
15
    const TYPE_DATETIME = "datetime";
16
    const TYPE_COLLECTION = "collection";
17
    const TYPE_ARRAY = "array";
18
    const TYPE_CONTENT = "content";
19
20
    /** @var PropertyAccessorInterface */
21
    protected $accessor;
22
23
    /** @var array */
24
    protected $data;
25
26
    /** @var array */
27
    public static $model = [];
28
29
    /**
30
     * Model constructor.
31
     *
32
     * @param string $json
33
     */
34
    public function __construct(string $json)
35
    {
36
        $data = json_decode($json, true);
37
        if ($data === false) {
38
            throw new \LogicException("Data is not valid JSON");
39
        }
40
41
        $this->verify(static::$model, $data);
42
43
        $this->accessor = PropertyAccess::createPropertyAccessorBuilder()
44
            ->disableExceptionOnInvalidIndex()
45
            ->disableMagicCall()
46
            ->getPropertyAccessor()
47
        ;
48
49
        $this->data = $data;
50
    }
51
52
    /**
53
     * @param string $path
54
     * @return int
55
     */
56
    public function count(string $path): int
57
    {
58
        $var = $this->accessor->getValue($this->data, $path);
59
60
        if (! is_array($var)) {
61
            return 1;
62
        }
63
64
        return count($var);
65
    }
66
67
    /**
68
     * @param string $path
69
     * @return bool
70
     */
71
    public function has(string $path): bool
72
    {
73
        return $this->accessor->isReadable($this->data, $path);
74
    }
75
76
    /**
77
     * @param string $path
78
     * @param null $default
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $default is correct as it would always require null to be passed?
Loading history...
79
     * @return mixed
80
     */
81
    public function get(string $path, $default = null)
82
    {
83
        if (! $this->accessor->isReadable($this->data, $path)) {
84
            return $default;
85
        }
86
87
        return $this->accessor->getValue($this->data, $path);
88
    }
89
90
    /**
91
     * @param array $model
92
     * @param array $data
93
     * @param string $currentPath
94
     */
95
    private function verify(array $model, array $data, string $currentPath = "")
96
    {
97
        foreach ($model as $id => $field) {
98
            $this->verifyField($id, $field, $data, $currentPath);
99
        }
100
    }
101
102
    /**
103
     * @SuppressWarnings(PHPMD.NPathComplexity)
104
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
105
     * @param string $id
106
     * @param array $field
107
     * @param array $data
108
     * @param string $currentPath
109
     */
110
    private function verifyField(string $id, array $field, array $data, string $currentPath)
111
    {
112
        if (! isset($field['type'])) {
113
            $field['type'] = self::TYPE_STRING;
114
        }
115
116
        if (! isset($data[$id])) {
117
            throw new \LogicException("Data is not found: $currentPath.$id");
118
        }
119
120
121
        // Check if type and data[id] match up
122
        $valid = false;
123
        switch ($field['type']) {
124
            case self::TYPE_ARRAY:
125
                $valid = is_array($data[$id]);
126
                break;
127
            case self::TYPE_COLLECTION:
128
                $valid = is_array($data[$id]);
129
                break;
130
            case self::TYPE_BOOLEAN:
131
                $valid = is_bool($data[$id]);
132
                break;
133
            case self::TYPE_INTEGER:
134
                $valid = is_int($data[$id]);
135
                break;
136
            case self::TYPE_STRING:
137
                $valid = is_string($data[$id]);
138
                break;
139
            case self::TYPE_CONTENT:
140
                $valid = true;
141
                break;
142
            case self::TYPE_DATETIME:
143
                try {
144
                    new \DateTime($data[$id]);
145
                    $valid = true;
146
                } catch (\Exception $e) {
147
                    $valid = false;
148
                }
149
                break;
150
        }
151
152
        if (! $valid) {
153
            throw new \LogicException("Data is not valid according to model: $currentPath.$id");
154
        }
155
156
        // Check collections recursively
157
        if ($field['type'] === self::TYPE_COLLECTION) {
158
            foreach ($data[$id] as $entry) {
159
                $this->verify($field['model'] ?? [], $entry, $currentPath . "." . $id);
160
            }
161
        }
162
163
        // Check arrays recursively
164
        if ($field['type'] === self::TYPE_ARRAY) {
165
            $this->verify($field['model'] ?? [], $data[$id], $currentPath . "." . $id);
166
        }
167
    }
168
}
169