1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
namespace BEAR\QueryRepository; |
6
|
|
|
|
7
|
|
|
use BEAR\RepositoryModule\Annotation\MarshallerOptions; |
8
|
|
|
use InvalidArgumentException; |
9
|
|
|
use Override; |
10
|
|
|
use Ray\Di\ProviderInterface; |
11
|
|
|
use RuntimeException; |
12
|
|
|
use Symfony\Component\Cache\Marshaller\DefaultMarshaller; |
13
|
|
|
use Symfony\Component\Cache\Marshaller\DeflateMarshaller; |
14
|
|
|
use Symfony\Component\Cache\Marshaller\MarshallerInterface; |
15
|
|
|
use Symfony\Component\Cache\Marshaller\SodiumMarshaller; |
16
|
|
|
|
17
|
|
|
use function base64_decode; |
18
|
|
|
use function extension_loaded; |
19
|
|
|
use function is_array; |
20
|
|
|
use function is_string; |
21
|
|
|
use function sprintf; |
22
|
|
|
use function strlen; |
23
|
|
|
|
24
|
|
|
use const SODIUM_CRYPTO_BOX_KEYPAIRBYTES; |
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* Provider for creating marshaller instances based on configuration options |
28
|
|
|
* |
29
|
|
|
* Supports the following marshaller types: |
30
|
|
|
* - 'default': Basic marshaller with optional igbinary support |
31
|
|
|
* - 'deflate': Compression-enabled marshaller (requires zlib extension) |
32
|
|
|
* - 'sodium': Encryption-enabled marshaller (requires sodium extension) |
33
|
|
|
* |
34
|
|
|
* For sodium marshallers, keys can be provided as: |
35
|
|
|
* - Base64-encoded strings (recommended for configuration files) |
36
|
|
|
* - Binary strings (32 bytes or SODIUM_CRYPTO_BOX_KEYPAIRBYTES length) |
37
|
|
|
* |
38
|
|
|
* @implements ProviderInterface<MarshallerInterface|null> |
39
|
|
|
*/ |
40
|
|
|
final class MarshallerProvider implements ProviderInterface |
41
|
|
|
{ |
42
|
|
|
/** @param array<string, mixed> $options Marshalling options */ |
43
|
|
|
public function __construct( |
44
|
|
|
#[MarshallerOptions] |
45
|
|
|
private readonly array $options = [], |
46
|
|
|
) { |
47
|
|
|
} |
48
|
|
|
|
49
|
|
|
#[Override] |
50
|
|
|
public function get(): MarshallerInterface|null |
51
|
|
|
{ |
52
|
|
|
return $this->createMarshaller($this->options); |
|
|
|
|
53
|
|
|
} |
54
|
|
|
|
55
|
|
|
/** |
56
|
|
|
* Create marshaller instance based on options |
57
|
|
|
* |
58
|
|
|
* @param array<string, mixed> $options |
59
|
|
|
*/ |
60
|
|
|
private function createMarshaller(array $options): MarshallerInterface|null |
61
|
|
|
{ |
62
|
|
|
if (empty($options) || ($options['enabled'] ?? false) !== true) { |
63
|
|
|
return null; |
64
|
|
|
} |
65
|
|
|
|
66
|
|
|
/** @var string $type */ |
67
|
|
|
$type = is_string($options['type'] ?? null) ? $options['type'] : 'default'; |
68
|
|
|
$useIgbinary = (bool) ($options['use_igbinary'] ?? false); |
69
|
|
|
|
70
|
|
|
return match ($type) { |
71
|
|
|
'default' => $this->createDefaultMarshaller($useIgbinary), |
72
|
|
|
'deflate' => $this->createDeflateMarshaller($useIgbinary), |
73
|
|
|
'sodium' => $this->createSodiumMarshaller($options, $useIgbinary), |
74
|
|
|
default => throw new InvalidArgumentException(sprintf('Invalid marshaller type: %s', $type)), |
75
|
|
|
}; |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
private function createDefaultMarshaller(bool $useIgbinary): DefaultMarshaller |
79
|
|
|
{ |
80
|
|
|
if ($useIgbinary && ! extension_loaded('igbinary')) { |
81
|
|
|
throw new RuntimeException('igbinary extension is required for igbinary marshaller'); |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
return new DefaultMarshaller($useIgbinary); |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
private function createDeflateMarshaller(bool $useIgbinary): DeflateMarshaller |
88
|
|
|
{ |
89
|
|
|
if (! extension_loaded('zlib')) { |
90
|
|
|
throw new RuntimeException('zlib extension is required for deflate marshaller'); |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
$defaultMarshaller = $this->createDefaultMarshaller($useIgbinary); |
94
|
|
|
|
95
|
|
|
return new DeflateMarshaller($defaultMarshaller); |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
/** @param array<string, mixed> $options */ |
99
|
|
|
private function createSodiumMarshaller(array $options, bool $useIgbinary): SodiumMarshaller |
100
|
|
|
{ |
101
|
|
|
if (! extension_loaded('sodium')) { |
102
|
|
|
throw new RuntimeException('sodium extension is required for sodium marshaller'); |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
if (! isset($options['keys']) || ! is_array($options['keys']) || empty($options['keys'])) { |
106
|
|
|
throw new InvalidArgumentException('Keys are required for sodium marshaller'); |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
/** @var array<mixed> $rawKeys */ |
110
|
|
|
$rawKeys = $options['keys']; |
111
|
|
|
$processedKeys = $this->processSodiumKeys($rawKeys); |
112
|
|
|
$defaultMarshaller = $this->createDefaultMarshaller($useIgbinary); |
113
|
|
|
|
114
|
|
|
return new SodiumMarshaller($processedKeys, $defaultMarshaller); |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
/** |
118
|
|
|
* Process and validate sodium encryption keys |
119
|
|
|
* |
120
|
|
|
* @param array<mixed> $keys Raw encryption keys (base64 encoded or binary) |
121
|
|
|
* |
122
|
|
|
* @return array<string> Processed binary encryption keys |
123
|
|
|
*/ |
124
|
|
|
private function processSodiumKeys(array $keys): array |
125
|
|
|
{ |
126
|
|
|
$processedKeys = []; |
127
|
|
|
|
128
|
|
|
foreach ($keys as $key) { |
129
|
|
|
if (! is_string($key)) { |
130
|
|
|
throw new InvalidArgumentException('All keys must be strings'); |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
// Try to decode as base64 first (common format in Symfony configs) |
134
|
|
|
$decodedKey = base64_decode($key, true); |
135
|
|
|
if ($decodedKey !== false) { |
136
|
|
|
// Validate key length for sodium (should be 32 bytes for crypto_box) |
137
|
|
|
if (strlen($decodedKey) === SODIUM_CRYPTO_BOX_KEYPAIRBYTES || strlen($decodedKey) === 32) { |
138
|
|
|
$processedKeys[] = $decodedKey; |
139
|
|
|
continue; |
140
|
|
|
} |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
// If not valid base64, assume it's already binary and validate length |
144
|
|
|
if (strlen($key) === SODIUM_CRYPTO_BOX_KEYPAIRBYTES || strlen($key) === 32) { |
145
|
|
|
$processedKeys[] = $key; |
146
|
|
|
continue; |
147
|
|
|
} |
148
|
|
|
|
149
|
|
|
throw new InvalidArgumentException(sprintf( |
150
|
|
|
'Invalid sodium key format or length. Expected base64-encoded key or binary key of length %d or %d bytes', |
151
|
|
|
SODIUM_CRYPTO_BOX_KEYPAIRBYTES, |
152
|
|
|
32, |
153
|
|
|
)); |
154
|
|
|
} |
155
|
|
|
|
156
|
|
|
return $processedKeys; |
157
|
|
|
} |
158
|
|
|
} |
159
|
|
|
|
This check looks for function or method calls that always return null and whose return value is used.
The method
getObject()
can return nothing but null, so it makes no sense to use the return value.The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.