{"id":18781560,"url":"https://github.com/devture/symfony-storer-bundle","last_synced_at":"2026-05-02T01:34:53.606Z","repository":{"id":56967219,"uuid":"192176692","full_name":"devture/symfony-storer-bundle","owner":"devture","description":"Symfony bundle that lets you deal with uploaded files in a relatively sane and storage-provider-independent way","archived":false,"fork":false,"pushed_at":"2019-07-01T12:21:33.000Z","size":24,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-05-28T07:48:48.357Z","etag":null,"topics":["amazon-s3","azure-blob","storage","symfony-bundle"],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/devture.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-06-16T10:13:43.000Z","updated_at":"2019-07-01T12:21:34.000Z","dependencies_parsed_at":"2022-08-21T11:20:10.613Z","dependency_job_id":null,"html_url":"https://github.com/devture/symfony-storer-bundle","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/devture/symfony-storer-bundle","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devture%2Fsymfony-storer-bundle","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devture%2Fsymfony-storer-bundle/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devture%2Fsymfony-storer-bundle/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devture%2Fsymfony-storer-bundle/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/devture","download_url":"https://codeload.github.com/devture/symfony-storer-bundle/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devture%2Fsymfony-storer-bundle/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32520156,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-02T01:12:54.858Z","status":"ssl_error","status_checked_at":"2026-05-02T01:12:54.261Z","response_time":64,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["amazon-s3","azure-blob","storage","symfony-bundle"],"created_at":"2024-11-07T20:32:35.721Z","updated_at":"2026-05-02T01:34:53.587Z","avatar_url":"https://github.com/devture.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Description\n\nA Symfony bundle that lets you deal with uploaded files in a relatively sane and storage-provider-independent way.\n\nThe [Gaufrette](https://github.com/KnpLabs/Gaufrette) library is used for storage abstraction.\n\nCurrently, the following storage adapters can be used through the bundle:\n- the local filesystem\n- Amazon S3 or Minio\n- Azure Blob storage\n\nWith minor code changes, all other Gaufrette-supported adapters (see `Helper/AdapterFactory.php`) could be made to work.\nPull requests are welcome!\n\n\n# Installation\n\nInstall through composer (`composer require --dev devture/symfony-web-command-bundle`).\n\nAdd to `config/bundles.php`:\n\n```php\nDevture\\Bundle\\StorerBundle\\DevtureStorerBundle::class =\u003e ['all' =\u003e true],\n```\n\nDepending on the storage provider that you'd like to use, you may need to install additional libraries (e.g. `aws/aws-sdk-php`, etc.)\n\n\n## Configuration\n\nDrop the following config in `config/packages/devture_storer.yaml`\n\n```yaml\ndevture_storer:\n  adapter_url: '%env(resolve:DEVTURE_STORER_ADAPTER_URL)%'\n  validation_max_size_megabytes: 20\n```\n\nYou then need to define an environment variable `DEVTURE_STORER_ADAPTER_URL`, which would specify which storage adapter you'd like to use.\n\nExample (`.env`):\n\n```\n# Local and remote adapters are supported.\n#\n# Remote URLs usually require that their secrets be urlencoded and percentage-sign-escaped (`/` turned to `%%2F`).\n# Reason: `/` found in secrets breaks URL-parsing, so we urlencode it. Symfony thinks % is a parameter, so we escape it (as %%).\n#\n# Example of S3-compatible Minio URL:\n#\tDEVTURE_STORER_ADAPTER_URL=s3://access.key:key%%2Fwith%%2Furlencoded%%2Fslashes@us-east-1.localhost:9000/bucket.name\n#\n# Example of actual Amazon S3 URL:\n#\tDEVTURE_STORER_ADAPTER_URL=s3://access.key:key%%2Fwith%%2Furlencoded%%2Fslashes@ap-northeast-1.s3.amazonaws.com/bucket.name\n#\n# Example of actual Azure Blob URL:\n#\tDEVTURE_STORER_ADAPTER_URL=azure-blob://account-name:account-key%%2Fwith%%2Furlencoded%%2Fslashes@account.blob.core.windows.net/container\nDEVTURE_STORER_ADAPTER_URL=file://%kernel.project_dir%/var/devture-storer\n```\n\nYou also need to register a Doctrine type like this (in `config/doctrine.yaml`):\n\n```yaml\ndoctrine:\n    dbal:\n        # Other stuff here ...\n        types:\n            devture_storer.file: Devture\\Bundle\\StorerBundle\\Doctrine\\StorerFileType\n```\n\n\n# Usage example\n\n## Entity\n\n```php\n/**\n * @ORM\\Table(name=\"user\")\n */\nclass User implements \\Devture\\Bundle\\StorerBundle\\Entity\\StorerFilesContainerInterface {\n\n\t/**\n\t * @ORM\\Id\n\t * @ORM\\Column(type=\"integer\")\n\t * @ORM\\GeneratedValue(strategy=\"AUTO\")\n\t */\n\tprivate $id;\n\n\t/**\n\t * @ORM\\Column(type=\"devture_storer.file\", length=191, nullable=true)\n\t * @Devture\\Bundle\\StorerBundle\\Validator\\Constraints\\Image()\n\t * @Devture\\Bundle\\StorerBundle\\Validator\\Constraints\\MaximumSize(maxSizeMegabytes=10)\n\t */\n\tprivate $photo;\n\n\tpublic function getPhoto(): ?\\Devture\\Bundle\\StorerBundle\\Entity\\FileInterface {\n\t\treturn $this-\u003ephoto;\n\t}\n\n\tpublic function setPhoto(?\\Devture\\Bundle\\StorerBundle\\Entity\\FileInterface $photo) {\n\t\t$this-\u003ephoto = $photo;\n\t}\n\n\t/**\n\t * {@inheritDoc}\n\t * @see \\Devture\\Bundle\\StorerBundle\\Entity\\StorerFilesContainerInterface::getContainedStorerFiles()\n\t */\n\tpublic function getContainedStorerFiles(): array {\n\t\t$files = [];\n\t\tif ($this-\u003ephoto !== null) {\n\t\t\t$files[] = $this-\u003ephoto;\n\t\t}\n\t\treturn $files;\n\t}\n\n}\n```\n\n## Form Type\n\n```php\n\u003c?php\nnamespace App\\UserBundle\\Form\\Type;\n\nuse Symfony\\Component\\Form\\Extension\\Core\\Type\\FileType;\nuse Symfony\\Component\\Form\\AbstractType;\nuse Symfony\\Component\\Form\\FormBuilderInterface;\nuse Symfony\\Component\\Form\\CallbackTransformer;\nuse Symfony\\Component\\HttpFoundation\\File\\UploadedFile;\nuse Devture\\Bundle\\StorerBundle\\FileHandle\\VolatileLocalFileHandle;\n\nclass UserType extends AbstractType {\n\n\tpublic function buildForm(FormBuilderInterface $builder, array $options) {\n\t\t/** @var \\App\\UserBundle\\Entity\\User $entity */\n\t\t$entity = $options['data'];\n\n\t\t$builder-\u003eadd('photoUpload', FileType::class, [\n\t\t\t'label' =\u003e 'Photo',\n\t\t\t'mapped' =\u003e false,\n\t\t\t'required' =\u003e false,\n\t\t]);\n\n\t\t$builder-\u003eget('photoUpload')-\u003eaddModelTransformer(new CallbackTransformer(\n\t\t\tfunction ($whatever): ?\\Devture\\Bundle\\StorerBundle\\Entity\\FileInterface {\n\t\t\t\t// No need to transform \"to form\".\n\t\t\t\treturn $whatever;\n\t\t\t},\n\t\t\tfunction (?UploadedFile $uploadedFile) use ($entity) {\n\t\t\t\tif ($uploadedFile === null) {\n\t\t\t\t\t// Nothing new. Not binding anything.\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t$file = new \\Devture\\Bundle\\StorerBundle\\Entity\\File(\n\t\t\t\t\t\\Devture\\Bundle\\StorerBundle\\Util::generateFullStorageKey(\n\t\t\t\t\t\t'user/photo',\n\t\t\t\t\t\t$uploadedFile-\u003egetClientOriginalName()\n\t\t\t\t\t)\n\t\t\t\t);\n\t\t\t\t$file-\u003esetStorerFileHandle(new VolatileLocalFileHandle($uploadedFile-\u003egetPathname()));\n\n\t\t\t\tif ($entity-\u003egetPhoto() !== null) {\n\t\t\t\t\t// Save the reference to the old photo, so we can delete it\n\t\t\t\t\t// (happens automatically when the new one is persisted).\n\t\t\t\t\t$file-\u003esetPreviousFile($entity-\u003egetPhoto());\n\t\t\t\t}\n\n\t\t\t\t$entity-\u003esetPhoto($file);\n\t\t\t}\n\t\t));\n\t}\n\n\tpublic function getBlockPrefix() {\n\t\treturn 'user';\n\t}\n\n}\n```\n\n## Form theme\n\n```twig\n{% block _user_photoUpload_widget %}\n\t{% set user = form.parent.vars.value %}\n\t{% set photo = user.photo %}\n\n\t{% if photo is not none %}\n\t\t{% if photo_url_full is not none %}\n\t\t\t\u003cimg src=\"{{ photo|user_generate_photo_serving_link }}\" class=\"img-fluid img-thumbnail\" /\u003e\n\t\t{% endif %}\n\n\t\t{# Some delete button could go here #}\n\t{% endif %}\n\n\t{{ form_widget(form) }}\n{% endblock %}\n```\n\n## Serving\n\n```php\nuse Symfony\\Component\\HttpFoundation\\Request;\nuse Symfony\\Component\\HttpFoundation\\Response;\nuse Symfony\\Component\\Routing\\Annotation\\Route;\nuse Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController;\nuse App\\UserBundle\\Repository\\UserRepository;\n\n/**\n * @Route(\"/user/{userId}/photo\", requirements={\"userId\": \"\\d+\"})\n */\nclass UserPhotoController extends AbstractController {\n\n\t/**\n\t * @Route(\"/serve\", name=\"user.photo.serve\", methods={\"GET\"})\n\t */\n\tpublic function serve(\n\t\tRequest $request,\n\t\tint $userId,\n\t\tUserRepository $userRepository\n\t): Response {\n\t\t/** @var User|null $user */\n\t\t$user = $userRepository-\u003efind($userId);\n\t\tif ($user === null || $user-\u003egetPhoto() === null) {\n\t\t\tthrow $this-\u003ecreateNotFoundException('No photo');\n\t\t}\n\n\t\t/** @var \\Devture\\Bundle\\StorerBundle\\Entity\\FileInterface $photo **/\n\t\t$photo = $user-\u003egetPhoto();\n\n\t\treturn new \\Devture\\Bundle\\StorerBundle\\Http\\FileResponse($photo-\u003egetStorerFileHandle(), 200, [\n\t\t\t'Content-Type' =\u003e \\Devture\\Bundle\\StorerBundle\\Util::getContentTypeByFileName($photo-\u003egetStorerFileKey()),\n\t\t]);\n\t}\n\n}\n```\n\n## Deletion\n\n```php\n$previousFile = $user-\u003egetPhoto();\n\nif ($previousFile !== null) {\n\t// Setting a new special `NullFile` with a reference to the one we want to delete\n\t// (called \"previous\"), will ensure that `Storer` would delete it.\n\t$user-\u003esetPhoto(new \\Devture\\Bundle\\StorerBundle\\Entity\\NullFile($previousFile));\n\t$entityManager-\u003eflush($user);\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevture%2Fsymfony-storer-bundle","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdevture%2Fsymfony-storer-bundle","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevture%2Fsymfony-storer-bundle/lists"}