Skip to content

Commit

Permalink
feat: add read only support
Browse files Browse the repository at this point in the history
  • Loading branch information
maxhelias committed Sep 2, 2023
1 parent 32b26ec commit 028e54a
Show file tree
Hide file tree
Showing 11 changed files with 102 additions and 15 deletions.
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"league/flysystem-ftp": "^3.1",
"league/flysystem-google-cloud-storage": "^3.1",
"league/flysystem-memory": "^3.1",
"league/flysystem-read-only": "^3.15",
"league/flysystem-sftp-v3": "^3.1",
"symfony/dotenv": "^5.4|^6.0",
"symfony/framework-bundle": "^5.4|^6.0",
Expand Down
34 changes: 33 additions & 1 deletion docs/1-getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,13 @@ it gives you to swap the actual implementation during tests.
More specifically, it can be useful to swap from a persisted storage to a memory one during
tests, both to ensure the state is reset between tests and to increase tests speed.

To achieve this, you can overwrite your storages in the test environment:
To achieve this, you need to install the memory provider:

```
composer require league/flysystem-memory
```

Then, you can overwrite your storages in the test environment:

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

## Using read only to disallow any write operations

In some context, it can be useful to protect any write operations on your storages service.

To achieve this, you need to install the read-only package :

```
composer require league/flysystem-read-only
```

And then, you can configure your storage with the `readonly` options.

```yaml
# config/packages/flysystem.yaml
flysystem:
storages:
users.storage:
adapter: 'local'
options:
directory: '%kernel.project_dir%/storage/users'
readonly: true
```

With this configuration, any write operation will throw a suitable exception.

## Next

[Cloud storage providers](https://github.com/thephpleague/flysystem-bundle/blob/master/docs/2-cloud-storage-providers.md)
20 changes: 14 additions & 6 deletions docs/B-configuration-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ flysystem:
prefix: 'optional/path/prefix'

users2.storage:
adapter: 'azure'
options:
client: 'azure_client_service'
container: 'container_name'
prefix: 'optional/path/prefix'
adapter: 'azure'
options:
client: 'azure_client_service'
container: 'container_name'
prefix: 'optional/path/prefix'

users3.storage:
adapter: 'ftp'
Expand Down Expand Up @@ -78,5 +78,13 @@ flysystem:
source: 'flysystem_storage_service_to_use'

users10.storage:
adapter: 'custom_adapter'
adapter: 'custom_adapter'

users11.storage:
adapter: 'local'
options:
directory: '/tmp/storage'
public_url_generator: 'flysystem_public_url_generator_service_to_use'
temporary_url_generator: 'flysystem_temporary_url_generator_service_to_use'
read_only: true
```
1 change: 1 addition & 0 deletions src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public function getConfigTreeBuilder(): TreeBuilder
->end()
->scalarNode('public_url_generator')->defaultNull()->end()
->scalarNode('temporary_url_generator')->defaultNull()->end()
->booleanNode('read_only')->defaultFalse()->end()
->end()
->end()
->defaultValue([])
Expand Down
34 changes: 29 additions & 5 deletions src/DependencyInjection/FlysystemExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
use League\Flysystem\FilesystemOperator;
use League\Flysystem\FilesystemReader;
use League\Flysystem\FilesystemWriter;
use League\Flysystem\ReadOnly\ReadOnlyFilesystemAdapter;
use League\FlysystemBundle\Adapter\AdapterDefinitionFactory;
use League\FlysystemBundle\Exception\MissingPackageException;
use League\FlysystemBundle\Lazy\LazyFactory;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
Expand Down Expand Up @@ -63,16 +65,29 @@ private function createStoragesDefinitions(array $config, ContainerBuilder $cont
// Create adapter definition
if ($adapter = $definitionFactory->createDefinition($storageConfig['adapter'], $storageConfig['options'])) {
// Native adapter
$container->setDefinition('flysystem.adapter.'.$storageName, $adapter)->setPublic(false);
$container->setDefinition($id = 'flysystem.adapter.'.$storageName, $adapter)->setPublic(false);
} else {
// Custom adapter
$container->setAlias('flysystem.adapter.'.$storageName, $storageConfig['adapter'])->setPublic(false);
$container->setAlias($id = 'flysystem.adapter.'.$storageName, $storageConfig['adapter'])->setPublic(false);
}

// Create ReadOnly adapter
if ($storageConfig['read_only']) {
if (!class_exists(ReadOnlyFilesystemAdapter::class)) {
throw new MissingPackageException("Missing package, to use the readonly option, run:\n\ncomposer require league/flysystem-read-only");
}

$originalAdapterId = $id;
$container->setDefinition(
$id = $id.'.read_only',
$this->createReadOnlyAdapterDefinition(new Reference($originalAdapterId))
);
}

// Create storage definition
$container->setDefinition(
$storageName,
$this->createStorageDefinition($storageName, new Reference('flysystem.adapter.'.$storageName), $storageConfig)
$this->createStorageDefinition($storageName, new Reference($id), $storageConfig)
);

// Register named autowiring alias
Expand All @@ -82,7 +97,7 @@ private function createStoragesDefinitions(array $config, ContainerBuilder $cont
}
}

private function createLazyStorageDefinition(string $storageName, array $options)
private function createLazyStorageDefinition(string $storageName, array $options): Definition
{
$resolver = new OptionsResolver();
$resolver->setRequired('source');
Expand All @@ -98,7 +113,7 @@ private function createLazyStorageDefinition(string $storageName, array $options
return $definition;
}

private function createStorageDefinition(string $storageName, Reference $adapter, array $config)
private function createStorageDefinition(string $storageName, Reference $adapter, array $config): Definition
{
$publicUrl = null;
if ($config['public_url']) {
Expand All @@ -122,4 +137,13 @@ private function createStorageDefinition(string $storageName, Reference $adapter

return $definition;
}

private function createReadOnlyAdapterDefinition(Reference $adapter): Definition
{
$definition = new Definition(ReadOnlyFilesystemAdapter::class);
$definition->setPublic(false);
$definition->setArgument(0, $adapter);

return $definition;
}
}
14 changes: 14 additions & 0 deletions tests/DependencyInjection/FlysystemExtensionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use Google\Cloud\Storage\Bucket;
use Google\Cloud\Storage\StorageClient;
use League\Flysystem\FilesystemOperator;
use League\Flysystem\UnableToWriteFile;
use MicrosoftAzure\Storage\Blob\BlobRestProxy;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Dotenv\Dotenv;
Expand Down Expand Up @@ -104,6 +105,19 @@ public function testUrlGenerators()
self::assertSame('https://example.org/temporary/test1.txt?expiresAt=1670846026', $fs->temporaryUrl('test1.txt', new \DateTimeImmutable('@1670846026')));
}

public function testReadOnly()
{
$kernel = $this->createFysystemKernel();
$container = $kernel->getContainer()->get('test.service_container');

$fs = $container->get('flysystem.test.fs_read_only');

$this->expectException(UnableToWriteFile::class);
$this->expectExceptionMessage('Unable to write file at location: path/to/file. This is a readonly adapter.');

$fs->write('/path/to/file', 'Unable to write in read only');
}

private function createFysystemKernel()
{
(new Dotenv())->populate([
Expand Down
2 changes: 1 addition & 1 deletion tests/Kernel/EmptyAppKernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public function registerBundles(): iterable
return [new FlysystemBundle()];
}

public function registerContainerConfiguration(LoaderInterface $loader)
public function registerContainerConfiguration(LoaderInterface $loader): void
{
$loader->load(function (ContainerBuilder $container) {
$container->loadFromExtension('flysystem', [
Expand Down
2 changes: 1 addition & 1 deletion tests/Kernel/FlysystemAppKernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public function registerBundles(): iterable
return [new FrameworkBundle(), new FlysystemBundle()];
}

public function registerContainerConfiguration(LoaderInterface $loader)
public function registerContainerConfiguration(LoaderInterface $loader): void
{
$adapterClients = $this->adapterClients;

Expand Down
2 changes: 1 addition & 1 deletion tests/Kernel/FrameworkAppKernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public function registerBundles(): iterable
return [new FrameworkBundle(), new FlysystemBundle()];
}

public function registerContainerConfiguration(LoaderInterface $loader)
public function registerContainerConfiguration(LoaderInterface $loader): void
{
$loader->load(function (ContainerBuilder $container) {
$container->loadFromExtension('framework', ['secret' => '$ecret', 'test' => true, 'http_method_override' => false]);
Expand Down
6 changes: 6 additions & 0 deletions tests/Kernel/config/flysystem.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,9 @@ flysystem:
directory: '/tmp/storage'
public_url_generator: 'flysystem.test.public_url_generator'
temporary_url_generator: 'flysystem.test.temporary_url_generator'

fs_read_only:
adapter: 'local'
options:
directory: '/tmp/storage'
read_only: true
1 change: 1 addition & 0 deletions tests/Kernel/config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ services:
flysystem.test.fs_public_url: { alias: 'fs_public_url' }
flysystem.test.fs_public_urls: { alias: 'fs_public_urls' }
flysystem.test.fs_url_generator: { alias: 'fs_url_generator' }
flysystem.test.fs_read_only: { alias: 'fs_read_only' }

0 comments on commit 028e54a

Please sign in to comment.