Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
93.75% covered (success)
93.75%
15 / 16
88.89% covered (success)
88.89%
8 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
TenantScopedStorage
93.75% covered (success)
93.75%
15 / 16
88.89% covered (success)
88.89%
8 / 9
10.02
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 put
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 putStream
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 get
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 delete
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 exists
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 url
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 size
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 prefixPath
87.50% covered (success)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
2.01
1<?php
2
3/**
4 * Tenant-scoped storage wrapper.
5 *
6 * This Source Code Form is subject to the terms of the Mozilla Public License,
7 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
8 * obtain one at https://mozilla.org/MPL/2.0/.
9 *
10 * @package   phpMyFAQ
11 * @author    Thorsten Rinne <thorsten@phpmyfaq.de>
12 * @copyright 2026 phpMyFAQ Team
13 * @license   https://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
14 * @link      https://www.phpmyfaq.de
15 * @since     2026-02-09
16 */
17
18declare(strict_types=1);
19
20namespace phpMyFAQ\Storage;
21
22final readonly class TenantScopedStorage implements StorageInterface
23{
24    public function __construct(
25        private StorageInterface $storage,
26        private string $tenantPrefix,
27    ) {
28    }
29
30    public function put(string $path, string $contents): bool
31    {
32        return $this->storage->put($this->prefixPath($path), $contents);
33    }
34
35    public function putStream(string $path, mixed $stream): bool
36    {
37        return $this->storage->putStream($this->prefixPath($path), $stream);
38    }
39
40    public function get(string $path): string
41    {
42        return $this->storage->get($this->prefixPath($path));
43    }
44
45    public function delete(string $path): bool
46    {
47        return $this->storage->delete($this->prefixPath($path));
48    }
49
50    public function exists(string $path): bool
51    {
52        return $this->storage->exists($this->prefixPath($path));
53    }
54
55    public function url(string $path): string
56    {
57        return $this->storage->url($this->prefixPath($path));
58    }
59
60    public function size(string $path): int
61    {
62        return $this->storage->size($this->prefixPath($path));
63    }
64
65    private function prefixPath(string $path): string
66    {
67        $normalizedPath = ltrim(
68            string: str_replace(search: '\\', replace: '/', subject: trim(string: $path)),
69            characters: '/',
70        );
71        $prefix = trim(string: $this->tenantPrefix, characters: '/');
72
73        if ($normalizedPath === '') {
74            return $prefix;
75        }
76
77        return $prefix . '/' . $normalizedPath;
78    }
79}