Skip to content

Commit a2cd947

Browse files
committed
feat: add read only support
1 parent b4924ea commit a2cd947

File tree

8 files changed

+97
-10
lines changed

8 files changed

+97
-10
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"league/flysystem-ftp": "^3.1",
3636
"league/flysystem-google-cloud-storage": "^3.1",
3737
"league/flysystem-memory": "^3.1",
38+
"league/flysystem-read-only": "^3.15",
3839
"league/flysystem-sftp-v3": "^3.1",
3940
"symfony/dotenv": "^5.4|^6.0",
4041
"symfony/framework-bundle": "^5.4|^6.0",

docs/1-getting-started.md

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,13 @@ it gives you to swap the actual implementation during tests.
137137
More specifically, it can be useful to swap from a persisted storage to a memory one during
138138
tests, both to ensure the state is reset between tests and to increase tests speed.
139139

140-
To achieve this, you can overwrite your storages in the test environment:
140+
To achieve this, you need to install the memory provider:
141+
142+
```
143+
composer require league/flysystem-memory
144+
```
145+
146+
Then, you can overwrite your storages in the test environment:
141147

142148
```yaml
143149
# config/packages/flysystem.yaml
@@ -162,6 +168,32 @@ flysystem:
162168
This configuration will swap every reference to the `users.storage` service (or to the
163169
`FilesystemOperator $usersStorage` typehint) from a local adapter to a memory one during tests.
164170

171+
## Using read only to disallow any write operations
172+
173+
In some context, it can be useful to protect any write operations on your storages service.
174+
175+
To achieve this, you need to install the read-only package :
176+
177+
```
178+
composer require league/flysystem-read-only
179+
```
180+
181+
And then, you can configure your storage with the `readonly` options.
182+
183+
```yaml
184+
# config/packages/flysystem.yaml
185+
186+
flysystem:
187+
storages:
188+
users.storage:
189+
adapter: 'local'
190+
options:
191+
directory: '%kernel.project_dir%/storage/users'
192+
readonly: true
193+
```
194+
195+
With this configuration, any write operation will throw a suitable exception.
196+
165197
## Next
166198

167199
[Cloud storage providers](https://github.com/thephpleague/flysystem-bundle/blob/master/docs/2-cloud-storage-providers.md)

docs/B-configuration-reference.md

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ flysystem:
1111
prefix: 'optional/path/prefix'
1212

1313
users2.storage:
14-
adapter: 'azure'
15-
options:
16-
client: 'azure_client_service'
17-
container: 'container_name'
18-
prefix: 'optional/path/prefix'
14+
adapter: 'azure'
15+
options:
16+
client: 'azure_client_service'
17+
container: 'container_name'
18+
prefix: 'optional/path/prefix'
1919

2020
users3.storage:
2121
adapter: 'ftp'
@@ -78,5 +78,13 @@ flysystem:
7878
source: 'flysystem_storage_service_to_use'
7979

8080
users10.storage:
81-
adapter: 'custom_adapter'
81+
adapter: 'custom_adapter'
82+
83+
users11.storage:
84+
adapter: 'local'
85+
options:
86+
directory: '/tmp/storage'
87+
public_url_generator: 'flysystem_public_url_generator_service_to_use'
88+
temporary_url_generator: 'flysystem_temporary_url_generator_service_to_use'
89+
read_only: true
8290
```

src/DependencyInjection/Configuration.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ public function getConfigTreeBuilder(): TreeBuilder
5151
->end()
5252
->scalarNode('public_url_generator')->defaultNull()->end()
5353
->scalarNode('temporary_url_generator')->defaultNull()->end()
54+
->booleanNode('read_only')->defaultFalse()->end()
5455
->end()
5556
->end()
5657
->defaultValue([])

src/DependencyInjection/FlysystemExtension.php

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
use League\Flysystem\FilesystemOperator;
1616
use League\Flysystem\FilesystemReader;
1717
use League\Flysystem\FilesystemWriter;
18+
use League\Flysystem\ReadOnly\ReadOnlyFilesystemAdapter;
1819
use League\FlysystemBundle\Adapter\AdapterDefinitionFactory;
20+
use League\FlysystemBundle\Exception\MissingPackageException;
1921
use League\FlysystemBundle\Lazy\LazyFactory;
2022
use Symfony\Component\DependencyInjection\ContainerBuilder;
2123
use Symfony\Component\DependencyInjection\Definition;
@@ -63,16 +65,29 @@ private function createStoragesDefinitions(array $config, ContainerBuilder $cont
6365
// Create adapter definition
6466
if ($adapter = $definitionFactory->createDefinition($storageConfig['adapter'], $storageConfig['options'], $storageConfig['directory_visibility'] ?? null)) {
6567
// Native adapter
66-
$container->setDefinition('flysystem.adapter.'.$storageName, $adapter)->setPublic(false);
68+
$container->setDefinition($id = 'flysystem.adapter.'.$storageName, $adapter)->setPublic(false);
6769
} else {
6870
// Custom adapter
69-
$container->setAlias('flysystem.adapter.'.$storageName, $storageConfig['adapter'])->setPublic(false);
71+
$container->setAlias($id = 'flysystem.adapter.'.$storageName, $storageConfig['adapter'])->setPublic(false);
72+
}
73+
74+
// Create ReadOnly adapter
75+
if ($storageConfig['read_only']) {
76+
if (!class_exists(ReadOnlyFilesystemAdapter::class)) {
77+
throw new MissingPackageException("Missing package, to use the readonly option, run:\n\ncomposer require league/flysystem-read-only");
78+
}
79+
80+
$originalAdapterId = $id;
81+
$container->setDefinition(
82+
$id = $id.'.read_only',
83+
$this->createReadOnlyAdapterDefinition(new Reference($originalAdapterId))
84+
);
7085
}
7186

7287
// Create storage definition
7388
$container->setDefinition(
7489
$storageName,
75-
$this->createStorageDefinition($storageName, new Reference('flysystem.adapter.'.$storageName), $storageConfig)
90+
$this->createStorageDefinition($storageName, new Reference($id), $storageConfig)
7691
);
7792

7893
// Register named autowiring alias
@@ -122,4 +137,13 @@ private function createStorageDefinition(string $storageName, Reference $adapter
122137

123138
return $definition;
124139
}
140+
141+
private function createReadOnlyAdapterDefinition(Reference $adapter): Definition
142+
{
143+
$definition = new Definition(ReadOnlyFilesystemAdapter::class);
144+
$definition->setPublic(false);
145+
$definition->setArgument(0, $adapter);
146+
147+
return $definition;
148+
}
125149
}

tests/DependencyInjection/FlysystemExtensionTest.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Google\Cloud\Storage\Bucket;
1717
use Google\Cloud\Storage\StorageClient;
1818
use League\Flysystem\FilesystemOperator;
19+
use League\Flysystem\UnableToWriteFile;
1920
use MicrosoftAzure\Storage\Blob\BlobRestProxy;
2021
use PHPUnit\Framework\TestCase;
2122
use Symfony\Component\Dotenv\Dotenv;
@@ -104,6 +105,19 @@ public function testUrlGenerators()
104105
self::assertSame('https://example.org/temporary/test1.txt?expiresAt=1670846026', $fs->temporaryUrl('test1.txt', new \DateTimeImmutable('@1670846026')));
105106
}
106107

108+
public function testReadOnly()
109+
{
110+
$kernel = $this->createFysystemKernel();
111+
$container = $kernel->getContainer()->get('test.service_container');
112+
113+
$fs = $container->get('flysystem.test.fs_read_only');
114+
115+
$this->expectException(UnableToWriteFile::class);
116+
$this->expectExceptionMessage('Unable to write file at location: path/to/file. This is a readonly adapter.');
117+
118+
$fs->write('/path/to/file', 'Unable to write in read only');
119+
}
120+
107121
private function createFysystemKernel(): FlysystemAppKernel
108122
{
109123
(new Dotenv())->populate([

tests/Kernel/config/flysystem.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,9 @@ flysystem:
9898
directory: '/tmp/storage'
9999
public_url_generator: 'flysystem.test.public_url_generator'
100100
temporary_url_generator: 'flysystem.test.temporary_url_generator'
101+
102+
fs_read_only:
103+
adapter: 'local'
104+
options:
105+
directory: '/tmp/storage'
106+
read_only: true

tests/Kernel/config/services.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,4 @@ services:
2525
flysystem.test.fs_public_url: { alias: 'fs_public_url' }
2626
flysystem.test.fs_public_urls: { alias: 'fs_public_urls' }
2727
flysystem.test.fs_url_generator: { alias: 'fs_url_generator' }
28+
flysystem.test.fs_read_only: { alias: 'fs_read_only' }

0 commit comments

Comments
 (0)