Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Flysystem: Filesystem Abstraction

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $10.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

I keep talking about how we're going to eventually move our uploads off of our server and put them onto AWS S3. But right now, our entire upload system is very tied to our local filesystem. For example, $file->move()? Yea, that will always move things physically on your filesystem.

One of my favorite tools to help with this problem is a library called Flysystem. It's written by our friend Frank - who co-authored our React tutorial. He also spoke at SymfonyCon in 2018 about Flysystem and that presentation is available right here on SymfonyCasts.

Flysystem gives you a nice service object that you can use to write or read files. Then, behind the scenes, you can swap out whether you want to use a local filesystem, S3, Dropbox or pretty much anything else. It gives you an easy way to work with the filesystem, but that filesystem could be local or in the cloud.

OneupFlysystemBundle

In Symfony, we have an excellent bundle for this library: Google for OneupFlysystemBundle, find their GitHub page, then click into the docs. Copy the library name, find your terminal and run:

Tip

A newer version of this bundle exists, which uses a newer version of the underlying league/flysystem library. To use the same version as we use in this tutorial, install version 3 of the bundle. If you install the newer version, we'll do our best to add notes to guide you through any changes :).

composer require "oneup/flysystem-bundle:^3"

Adapters & Filesystems

While Jordi is preparing our packages, go back to their docs. Flysystem has two important concepts, which you can see here in the config example. First, we need to set up an "adapter", which is a lower-level object. Give it any name - like my_adapter. Then, this key - local - is the critical part: this says that you want to use the local adapter - an adapter that stores things on the local filesystem. Click the AwsS3 adapter link. If you want to use this adapter and store your files in S3, you'll use the key awss3v3. Every adapter also has different options. We're going to start with the local adapter, but move to s3 later.

But the real star, is the filesystem. Same thing: you give it any nickname, like my_filesystem and then say: this filesystem uses the my_adapter adapter. We'll talk about visibility later. The filesystem is the object that we'll work with directly to read, write & delete files.

Ok, go check on Composer. It's done and thanks to the recipe, we have a new config/packages/oneup_flysystem.yaml file with the same config we just saw in the docs.

# Read the documentation: https://github.com/1up-lab/OneupFlysystemBundle/tree/master/Resources/doc/index.md
oneup_flysystem:
adapters:
default_adapter:
local:
directory: '%kernel.cache_dir%/flysystem'
filesystems:
default_filesystem:
adapter: default_adapter
alias: League\Flysystem\Filesystem

Configuring the Adapter & Filesystem

Let's create 1 adapter and 1 filesystem for our uploads. Call the adapter, how about, public_uploads_adapter. I'm saying "public uploads" because this will put things into the public/ directory: they will be publicly accessible. We'll talk about private uploads soon - those are files where you need to do some security checks before you allow a user to see them. Change the directory to %kernel.project_dir% and then /public/uploads.

Tip

If you're using version 4 of oneup/flysystem-bundle, the directory config is now called location.

... line 1
oneup_flysystem:
adapters:
public_uploads_adapter:
local:
directory: '%kernel.project_dir%/public/uploads'
... lines 7 - 10

That is the root of this filesystem: everything will be stored relative to this. Give the filesystem a similar name - public_uploads_filesystem - and set adapter: to public_uploads_adapter.

... line 1
oneup_flysystem:
... lines 3 - 6
filesystems:
public_uploads_filesystem:
adapter: public_uploads_adapter

Filesystem Alias?

What about this alias key? Let's see what that does. First, when you configure a filesystem here, it creates a service. Find your terminal and run:

php bin/console debug:container flysystem

There it is: oneup_flysystem.public_uploads_filesystem_filesystem. That service was created thanks to our config and we'll use it soon in UploaderHelper. The bundle also created another service called: League\Flysystem\Filesystem. Well, actually, it's an alias: I'll type 61 to view more info about it. Yep! This points to our public_uploads_filesystem service. The purpose of this is that it allows us to type-hint League\Flysystem\Filesystem and Symfony will autowire our filesystem service.

If you only have 1 filesystem, having this alias is great. But if you have multiple, well, you can only autowire one of them. I'm going to remove the alias - I'll show you another way to access the filesystem service.

Ok, config done! Next, let's start using this shiny new Filesystem service.

Leave a comment!

6
Login or Register to join the conversation
Christophe R. Avatar
Christophe R. Avatar Christophe R. | posted 1 year ago | edited

Hello, with the new version, is the configuration should be:


flysystem:
    storages:
        default.storage:
            adapter: 'local'
            options:
                directory: '%kernel.project_dir%/public/%uploads_dir_name%'

and in my liip imagine:


liip_imagine:
    driver: "gd"

    loaders:
        default:
            flysystem:
                filesystem_service: 'default.storage'

    resolvers:
        default:
            flysystem:
                filesystem_service: 'default.storage'
                cache_prefix: '/media/cache'
                root_url: '%uploads_base_url%'

But i have issue with the resolver

Reply
Christophe R. Avatar

Source from https://github.com/thephple...

My issue was on the entity where I let the "uploads" dir in path whereas it should ne be there!

Reply

Thanks for sharing Christophe R.!

Reply
Tac-Tacelosky Avatar
Tac-Tacelosky Avatar Tac-Tacelosky | posted 1 year ago

I'm trying to run this with PHP 8.1, but it looks like the cache adapter (composer require league/flysystem-cached-adapter) is locked to an older version of flysystem (~1), which isn't compatible.

Updating flysystem it seems they've changed the name of the interface class to Operator, e.g. League\Flysystem\FilesystemOperator instead of League\Flysystem\FilesystemInterface.

I'm wondering if the Symfony Filesystem can be used instead (ditto on the cache), and if anyone's updated the demo project to the latest Symfony and PHP. I find uploads very confusing, especially on s3, but with all my projects moving to a cloud-based system, it's a necessity. Ryan, any chance of an update to this terrific tutorial?

Reply

Yo Tac-Tacelosky!

Yea, unfortunately, this tutorial only installed on PHP 7 (we've got a little tooltip about it when you download the code - but it might be too subtle).

> but it looks like the cache adapter (composer require league/flysystem-cached-adapter) is locked to an older version of flysystem (~1), which isn't compatible

Correct. This adapter doesn't work with Flysystem v2... and my guess is that at least v2 is needed for php 8.1. That's also why you're seeing interface changes.

> Ryan, any chance of an update to this terrific tutorial?

We might, yes :). But it would be a few months. You can't use Symfony Filesystem, but I think Flysystem is really what you're looking for: a tool that makes working on S3 "feel" like a normal filesystem. I wouldn't be too turned-off by this code not running in PHP 8. Other than some namespace changes (and that cached adapter no longer being supported), nothing has significantly changed. Oh, but if you want, there is also a newer bundle (was made after I made this tutorial) for working with Flysystem in Symfony: https://github.com/thephple...

Cheers!

Reply
CDesign Avatar
CDesign Avatar CDesign | posted 2 years ago | edited

When I run composer require one/flystem-bundle (with Symfony 4.2/May 2021), composer attempts to install flysystem bundle v4.0 but generates the following error:

Your requirements could not be resolved to an installable set of packages.
Problem 1

  • oneup/flysystem-bundle 4.0.7 requires symfony/config ^4.4 || ^5.0 -> no matching package found.

The solution is to install the same version as Ryan (which is v3) like so:

composer require oneup/flysystem-bundle:^3.0

Hope that helps someone else.

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial is built on Symfony 4 but works great in Symfony 5!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.1.3",
        "ext-iconv": "*",
        "aws/aws-sdk-php": "^3.87", // 3.87.10
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "knplabs/knp-markdown-bundle": "^1.7", // 1.7.1
        "knplabs/knp-paginator-bundle": "^2.7", // v2.8.0
        "knplabs/knp-time-bundle": "^1.8", // 1.9.0
        "league/flysystem-aws-s3-v3": "^1.0", // 1.0.22
        "league/flysystem-cached-adapter": "^1.0", // 1.0.9
        "liip/imagine-bundle": "^2.1", // 2.1.0
        "nexylan/slack-bundle": "^2.0,<2.2.0", // v2.1.0
        "oneup/flysystem-bundle": "^3.0", // 3.0.3
        "php-http/guzzle6-adapter": "^1.1", // v1.1.1
        "sensio/framework-extra-bundle": "^5.1", // v5.2.4
        "stof/doctrine-extensions-bundle": "^1.3", // v1.3.0
        "symfony/asset": "^4.0", // v4.2.3
        "symfony/console": "^4.0", // v4.2.3
        "symfony/flex": "^1.9", // v1.17.6
        "symfony/form": "^4.0", // v4.2.3
        "symfony/framework-bundle": "^4.0", // v4.2.3
        "symfony/orm-pack": "^1.0", // v1.0.6
        "symfony/security-bundle": "^4.0", // v4.2.3
        "symfony/serializer-pack": "^1.0", // v1.0.2
        "symfony/twig-bundle": "^4.0", // v4.2.3
        "symfony/validator": "^4.0", // v4.2.3
        "symfony/web-server-bundle": "^4.0", // v4.2.3
        "symfony/yaml": "^4.0", // v4.2.3
        "twig/extensions": "^1.5" // v1.5.4
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.0", // 3.1.0
        "easycorp/easy-log-handler": "^1.0.2", // v1.0.7
        "fzaninotto/faker": "^1.7", // v1.8.0
        "symfony/debug-bundle": "^3.3|^4.0", // v4.2.3
        "symfony/dotenv": "^4.0", // v4.2.3
        "symfony/maker-bundle": "^1.0", // v1.11.3
        "symfony/monolog-bundle": "^3.0", // v3.3.1
        "symfony/phpunit-bridge": "^3.3|^4.0", // v4.2.3
        "symfony/profiler-pack": "^1.0", // v1.0.4
        "symfony/var-dumper": "^3.3|^4.0" // v4.2.3
    }
}
userVoice