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

68publishers / file-storage / 15323524541

29 May 2025 12:10PM UTC coverage: 84.554% (-7.8%) from 92.308%
15323524541

Pull #8

github

web-flow
Merge 9bd4b512d into da640973d
Pull Request #8: PHP 8.4 support

531 of 628 relevant lines covered (84.55%)

0.85 hits per line

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

87.4
/src/Bridge/Nette/DI/FileStorageExtension.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace SixtyEightPublishers\FileStorage\Bridge\Nette\DI;
6

7
use Composer\Autoload\ClassLoader;
8
use League\Flysystem\Config as FlysystemConfig;
9
use League\Flysystem\Filesystem;
10
use League\Flysystem\FilesystemOperator;
11
use League\Flysystem\Local\LocalFilesystemAdapter;
12
use League\Flysystem\Visibility;
13
use Nette\DI\CompilerExtension;
14
use Nette\DI\Definitions\ServiceDefinition;
15
use Nette\DI\Definitions\Statement;
16
use Nette\Schema\Expect;
17
use Nette\Schema\Schema;
18
use ReflectionClass;
19
use ReflectionException;
20
use SixtyEightPublishers\FileStorage\Asset\AssetFactory;
21
use SixtyEightPublishers\FileStorage\Asset\AssetFactoryInterface;
22
use SixtyEightPublishers\FileStorage\Asset\AssetsCopier;
23
use SixtyEightPublishers\FileStorage\Asset\AssetsCopierInterface;
24
use SixtyEightPublishers\FileStorage\Asset\PathsProvider;
25
use SixtyEightPublishers\FileStorage\Asset\PathsProviderInterface;
26
use SixtyEightPublishers\FileStorage\Bridge\Nette\DI\Config\FileStorageConfig;
27
use SixtyEightPublishers\FileStorage\Bridge\Nette\DI\Config\FilesystemConfig;
28
use SixtyEightPublishers\FileStorage\Bridge\Nette\DI\Config\StorageConfig;
29
use SixtyEightPublishers\FileStorage\Cleaner\DefaultFileKeepResolver;
30
use SixtyEightPublishers\FileStorage\Cleaner\FileKeepResolverInterface;
31
use SixtyEightPublishers\FileStorage\Cleaner\StorageCleaner;
32
use SixtyEightPublishers\FileStorage\Cleaner\StorageCleanerInterface;
33
use SixtyEightPublishers\FileStorage\Config\Config;
34
use SixtyEightPublishers\FileStorage\Config\ConfigInterface;
35
use SixtyEightPublishers\FileStorage\Exception\RuntimeException;
36
use SixtyEightPublishers\FileStorage\FileStorage;
37
use SixtyEightPublishers\FileStorage\FileStorageInterface;
38
use SixtyEightPublishers\FileStorage\FileStorageProvider;
39
use SixtyEightPublishers\FileStorage\FileStorageProviderInterface;
40
use SixtyEightPublishers\FileStorage\LinkGenerator\LinkGenerator;
41
use SixtyEightPublishers\FileStorage\LinkGenerator\LinkGeneratorInterface;
42
use SixtyEightPublishers\FileStorage\Persistence\FilePersister;
43
use SixtyEightPublishers\FileStorage\Persistence\FilePersisterInterface;
44
use SixtyEightPublishers\FileStorage\Resource\ResourceFactory;
45
use SixtyEightPublishers\FileStorage\Resource\ResourceFactoryInterface;
46
use function array_filter;
47
use function assert;
48
use function class_exists;
49
use function count;
50
use function dirname;
51
use function sprintf;
52

53
final class FileStorageExtension extends CompilerExtension implements FileStorageDefinitionFactoryInterface
54
{
55
    private string $rootDir;
56

57
    public function __construct(?string $rootDir = null)
58
    {
59
        $this->rootDir = $rootDir ?? $this->guessRootDir();
1✔
60
    }
1✔
61

62
    public function getConfigSchema(): Schema
63
    {
64
        return Expect::structure([
1✔
65
            'storages' => Expect::arrayOf(
1✔
66
                Expect::structure([ # the first one storage is default
1✔
67
                    'config' => Expect::array([]),
1✔
68
                    'filesystem' => Expect::structure([
1✔
69
                        'adapter' => Expect::anyOf(Expect::string(), Expect::type(Statement::class))->required()->before(static function ($factory) {
1✔
70
                            return $factory instanceof Statement ? $factory : new Statement($factory);
1✔
71
                        }),
1✔
72
                        'config' => Expect::array([ # @phpstan-ignore-line false positive `Call to an undefined method Nette\Schema\Elements\Structure|Nette\Schema\Elements\Type::mergeDefaults().`
1✔
73
                            FlysystemConfig::OPTION_VISIBILITY => Visibility::PUBLIC,
1✔
74
                            FlysystemConfig::OPTION_DIRECTORY_VISIBILITY => Visibility::PUBLIC,
1✔
75
                        ])->mergeDefaults(),
1✔
76
                    ])->castTo(FilesystemConfig::class),
1✔
77
                    'assets' => Expect::arrayOf('string', 'string'),
1✔
78
                ])->castTo(StorageConfig::class),
1✔
79
            ),
80
        ])->assert(static function (object $config) {
1✔
81
            return isset($config->storages) && 0 < count($config->storages);
1✔
82
        }, 'At least one storage must be defined.')
1✔
83
            ->castTo(FileStorageConfig::class);
1✔
84
    }
85

86
    public function loadConfiguration(): void
87
    {
88
        $builder = $this->getContainerBuilder();
1✔
89
        $config = $this->getConfig();
1✔
90
        assert($config instanceof FileStorageConfig);
91

92
        $factories = array_filter($this->compiler->getExtensions(FileStorageDefinitionFactoryInterface::class), function ($extension) {
1✔
93
            return $extension !== $this;
1✔
94
        });
1✔
95

96
        $factories[] = $this;
1✔
97
        $storageDefinitions = [];
1✔
98
        $defaultStorageDefinition = null;
1✔
99

100
        foreach ($config->storages as $storageName => $storageConfig) {
1✔
101
            foreach ($factories as $factory) {
1✔
102
                assert($factory instanceof FileStorageDefinitionFactoryInterface);
103

104
                if (!$factory->canCreateFileStorage($storageName, $storageConfig)) {
1✔
105
                    continue;
×
106
                }
107

108
                $storageDefinition = $factory->createFileStorage($storageName, $storageConfig);
1✔
109

110
                if (null === $defaultStorageDefinition) {
1✔
111
                    $defaultStorageDefinition = $storageDefinition->setAutowired();
1✔
112
                } else {
113
                    $storageDefinitions[] = $storageDefinition->setAutowired(false);
1✔
114
                }
115

116
                continue 2;
1✔
117
            }
118
        }
119

120
        $builder->addDefinition($this->prefix('file_storage_provider'))
1✔
121
            ->setType(FileStorageProviderInterface::class)
1✔
122
            ->setFactory(FileStorageProvider::class, [
1✔
123
                $defaultStorageDefinition,
1✔
124
                $storageDefinitions,
1✔
125
            ]);
126

127
        $this->registerStorageCleaner();
1✔
128
        $this->registerAssets($config);
1✔
129
    }
1✔
130

131
    public function canCreateFileStorage(string $name, object $config): bool
132
    {
133
        return true;
1✔
134
    }
135

136
    public function createFileStorage(string $name, StorageConfig $config): ServiceDefinition
137
    {
138
        $builder = $this->getContainerBuilder();
1✔
139

140
        $builder->addDefinition($this->prefix('filesystem.' . $name))
1✔
141
            ->setType(FilesystemOperator::class)
1✔
142
            ->setFactory(Filesystem::class, [
1✔
143
                'adapter' => $config->filesystem->adapter,
1✔
144
                'config' => $config->filesystem->config,
1✔
145
            ])
146
            ->setAutowired(false);
1✔
147

148
        $builder->addDefinition($this->prefix('config.' . $name))
1✔
149
            ->setType(ConfigInterface::class)
1✔
150
            ->setFactory(Config::class, [$config->config])
1✔
151
            ->setAutowired(false);
1✔
152

153
        $builder->addDefinition($this->prefix('resource_factory.' . $name))
1✔
154
            ->setType(ResourceFactoryInterface::class)
1✔
155
            ->setFactory(ResourceFactory::class, [$this->prefix('@filesystem.' . $name)])
1✔
156
            ->setAutowired(false);
1✔
157

158
        $builder->addDefinition($this->prefix('link_generator.' . $name))
1✔
159
            ->setType(LinkGeneratorInterface::class)
1✔
160
            ->setFactory(LinkGenerator::class, [$this->prefix('@config.' . $name)])
1✔
161
            ->setAutowired(false);
1✔
162

163
        $builder->addDefinition($this->prefix('file_persister.' . $name))
1✔
164
            ->setType(FilePersisterInterface::class)
1✔
165
            ->setFactory(FilePersister::class, [$this->prefix('@filesystem.' . $name)])
1✔
166
            ->setAutowired(false);
1✔
167

168
        return $builder->addDefinition($this->prefix('file_storage.' . $name))
1✔
169
            ->setType(FileStorageInterface::class)
1✔
170
            ->setFactory(FileStorage::class, [
1✔
171
                $name,
1✔
172
                $this->prefix('@config.' . $name),
1✔
173
                $this->prefix('@resource_factory.' . $name),
1✔
174
                $this->prefix('@link_generator.' . $name),
1✔
175
                $this->prefix('@file_persister.' . $name),
1✔
176
            ])
177
            ->setAutowired(false);
1✔
178
    }
179

180
    private function registerStorageCleaner(): void
181
    {
182
        $builder = $this->getContainerBuilder();
1✔
183

184
        $builder->addDefinition($this->prefix('file_keep_resolver'))
1✔
185
            ->setType(FileKeepResolverInterface::class)
1✔
186
            ->setFactory(DefaultFileKeepResolver::class);
1✔
187

188
        $builder->addDefinition($this->prefix('storage_cleaner'))
1✔
189
            ->setType(StorageCleanerInterface::class)
1✔
190
            ->setFactory(StorageCleaner::class);
1✔
191
    }
1✔
192

193
    private function registerAssets(FileStorageConfig $config): void
194
    {
195
        $builder = $this->getContainerBuilder();
1✔
196

197
        $builder->addDefinition($this->prefix('assets.local_filesystem'))
1✔
198
            ->setType(FilesystemOperator::class)
1✔
199
            ->setFactory(Filesystem::class, [
1✔
200
                new Statement(LocalFilesystemAdapter::class, [$this->rootDir]),
1✔
201
            ])
202
            ->setAutowired(false);
1✔
203

204
        $builder->addDefinition($this->prefix('assets.asset_factory'))
1✔
205
            ->setType(AssetFactoryInterface::class)
1✔
206
            ->setFactory(AssetFactory::class)
1✔
207
            ->setAutowired(false);
1✔
208

209
        $provider = $builder->addDefinition($this->prefix('assets.paths_provider'))
1✔
210
            ->setType(PathsProviderInterface::class)
1✔
211
            ->setFactory(PathsProvider::class)
1✔
212
            ->setAutowired(false);
1✔
213

214
        $builder->addDefinition($this->prefix('assets.assets_copier'))
1✔
215
            ->setType(AssetsCopierInterface::class)
1✔
216
            ->setFactory(AssetsCopier::class, [
1✔
217
                $this->prefix('@assets.local_filesystem'),
1✔
218
                $this->prefix('@assets.paths_provider'),
1✔
219
                $this->prefix('@assets.asset_factory'),
1✔
220
            ]);
221

222
        foreach ($config->storages as $storageName => $storageConfig) {
1✔
223
            if (!empty($storageConfig->assets)) {
1✔
224
                $provider->addSetup('addPaths', [$storageName, $storageConfig->assets]);
1✔
225
            }
226
        }
227

228
        foreach ($this->compiler->getExtensions(AssetsProviderInterface::class) as $assetsProviderExtension) {
1✔
229
            assert($assetsProviderExtension instanceof AssetsProviderInterface);
230

231
            foreach ($assetsProviderExtension->provideAssets() as $assets) {
×
232
                if (!empty($assets->paths)) {
×
233
                    $provider->addSetup('addPaths', [$assets->storageName, $assets->paths]);
×
234
                }
235
            }
236
        }
237
    }
1✔
238

239
    /**
240
     * @throws RuntimeException
241
     */
242
    private function guessRootDir(): string
243
    {
244
        if (!class_exists(ClassLoader::class)) {
×
245
            throw new RuntimeException(sprintf(
×
246
                'Project root directory can\'t be detected because the class %s can\'t be found. Please provide the root directory manually into the %s::__construct().',
×
247
                ClassLoader::class,
×
248
                self::class,
×
249
            ));
250
        }
251

252
        try {
253
            $reflection = new ReflectionClass(ClassLoader::class);
×
254

255
            return dirname((string) $reflection->getFileName(), 3);
×
256
        } catch (ReflectionException $e) {
×
257
            throw new RuntimeException(sprintf(
×
258
                'Project root directory can\'t be detected. Please provide the root directory manually  into the %s::__construct().',
×
259
                self::class,
×
260
            ), 0, $e);
×
261
        }
262
    }
263
}
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