Passed
Pull Request — 1.x (#163)
by
unknown
04:42 queued 02:54
created

MarshallerProvider::createDeflateMarshaller()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 4
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 9
rs 10
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);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->createMarshaller($this->options) targeting BEAR\QueryRepository\Mar...der::createMarshaller() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

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.

Loading history...
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