• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

plank / laravel-metable / 8842761288

26 Apr 2024 03:37AM UTC coverage: 94.939% (-3.8%) from 98.76%
8842761288

Pull #102

github

frasmage
use prefix index on value instead of separate string_value column
Pull Request #102: V6

373 of 402 new or added lines in 25 files covered. (92.79%)

3 existing lines in 1 file now uncovered.

544 of 573 relevant lines covered (94.94%)

192.47 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.5
/src/Meta.php
1
<?php
2

3
namespace Plank\Metable;
4

5
use Illuminate\Contracts\Encryption\Encrypter;
6
use Illuminate\Database\Eloquent\Model;
7
use Illuminate\Database\Eloquent\Relations\MorphTo;
8
use Illuminate\Support\Facades\Crypt;
9
use Plank\Metable\DataType\Registry;
10
use Plank\Metable\Exceptions\SecurityException;
11

12
/**
13
 * Model for storing meta data.
14
 *
15
 * @property int $id
16
 * @property string $metable_type
17
 * @property int $metable_id
18
 * @property string $type
19
 * @property string $key
20
 * @property mixed $value
21
 * @property string $raw_value
22
 * @property null|string $string_value
23
 * @property null|int|float $numeric_value
24
 * @property null|string $hmac
25
 * @property Model $metable
26
 */
27
class Meta extends Model
28
{
29
    public const ENCRYPTED_PREFIX = 'encrypted:';
30

31
    /**
32
     * {@inheritdoc}
33
     */
34
    public $timestamps = false;
35

36
    /**
37
     * {@inheritdoc}
38
     */
39
    protected $table = 'meta';
40

41
    /**
42
     * {@inheritdoc}
43
     */
44
    protected $guarded = [
45
        'id',
46
        'metable_type',
47
        'metable_id',
48
        'type',
49
        'string_value',
50
        'numeric_value',
51
        'hmac'
52
    ];
53

54
    /**
55
     * {@inheritdoc}
56
     */
57
    protected $attributes = [
58
        'type' => 'null',
59
        'value' => '',
60
    ];
61

62
    /**
63
     * Cache of unserialized value.
64
     *
65
     * @var mixed
66
     */
67
    protected mixed $cachedValue;
68

69
    /**
70
     * Metable Relation.
71
     *
72
     * @return MorphTo
73
     */
74
    public function metable(): MorphTo
75
    {
76
        return $this->morphTo();
6✔
77
    }
78

79
    /**
80
     * Accessor for value.
81
     *
82
     * Will unserialize the value before returning it.
83
     *
84
     * Successive access will be loaded from cache.
85
     *
86
     * @return mixed
87
     * @throws Exceptions\DataTypeException
88
     */
89
    public function getValueAttribute(): mixed
90
    {
91
        if (!isset($this->cachedValue)) {
660✔
92
            $type = $this->type;
660✔
93
            $value = $this->attributes['value'];
660✔
94

95
            if (str_starts_with($type, self::ENCRYPTED_PREFIX)) {
660✔
96
                $value = $this->getEncrypter()->decrypt($value);
48✔
97
                $type = substr($this->type, strlen(self::ENCRYPTED_PREFIX));
48✔
98
            }
99

100
            $registry = $this->getDataTypeRegistry();
660✔
101
            $handler = $registry->getHandlerForType($type);
660✔
102

103
            if ($handler->useHmacVerification()) {
660✔
104
                $this->verifyHmac($value, $this->attributes['hmac']);
78✔
105
            }
106

107
            $this->cachedValue = $handler->unserializeValue(
654✔
108
                $value
654✔
109
            );
654✔
110
        }
111

112
        return $this->cachedValue;
654✔
113
    }
114

115
    /**
116
     * Mutator for value.
117
     *
118
     * The `type` attribute will be automatically updated to match the datatype of the input.
119
     *
120
     * @param mixed $value
121
     * @throws Exceptions\DataTypeException
122
     */
123
    public function setValueAttribute(mixed $value): void
124
    {
125
        $registry = $this->getDataTypeRegistry();
846✔
126

127
        $this->attributes['type'] = $registry->getTypeForValue($value);
846✔
128
        $handler = $registry->getHandlerForType($this->attributes['type']);
846✔
129

130
        $this->attributes['value'] = $handler->serializeValue($value);
846✔
131
        $this->attributes['numeric_value'] = $handler->getNumericValue($value);
846✔
132
        $this->attributes['hmac'] = $handler->useHmacVerification()
846✔
133
            ? $this->computeHmac($this->attributes['value'])
84✔
134
            : null;
786✔
135

136
        $this->cachedValue = null;
846✔
137
    }
138

139
    public function encrypt(): void
140
    {
141
        if ($this->type === 'null') {
54✔
142
            return;
6✔
143
        }
144

145
        if (str_starts_with($this->type, self::ENCRYPTED_PREFIX)) {
48✔
NEW
146
            return;
×
147
        }
148

149
        $this->attributes['value'] = $this->getEncrypter()
48✔
150
            ->encrypt($this->attributes['value']);
48✔
151
        $this->type = self::ENCRYPTED_PREFIX . $this->type;
48✔
152
        $this->numeric_value = null;
48✔
153
    }
154

155
    public function getRawValueAttribute(): string
156
    {
157
        return $this->getRawValue();
12✔
158
    }
159

160
    /**
161
     * Retrieve the underlying serialized value.
162
     *
163
     * @return string
164
     */
165
    public function getRawValue(): string
166
    {
167
        return $this->attributes['value'];
24✔
168
    }
169

170
    /**
171
     * Load the datatype Registry from the container.
172
     *
173
     * @return Registry
174
     */
175
    protected function getDataTypeRegistry(): Registry
176
    {
177
        return app('metable.datatype.registry');
846✔
178
    }
179

180
    protected function verifyHmac(string $serializedValue, string $hmac): void
181
    {
182
        $expectedHash = $this->computeHmac($serializedValue);
78✔
183
        if (!hash_equals($expectedHash, $hmac)) {
78✔
184
            throw SecurityException::hmacVerificationFailed();
6✔
185
        }
186
    }
187

188
    protected function computeHmac(string $serializedValue): string
189
    {
190
        return hash_hmac('sha256', $serializedValue, config('app.key'));
84✔
191
    }
192

193
    protected function getEncrypter(): Encrypter
194
    {
195
        return self::$encrypter ?? Crypt::getFacadeRoot();
48✔
196
    }
197
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc