{"id":14984092,"url":"https://github.com/ranky/media-bundle","last_synced_at":"2025-10-29T16:19:25.669Z","repository":{"id":64933441,"uuid":"579193145","full_name":"ranky/media-bundle","owner":"ranky","description":"MediaBundle is a media file manager bundle for Symfony with a REST API and an admin interface (React)","archived":false,"fork":false,"pushed_at":"2024-08-09T22:13:40.000Z","size":6882,"stargazers_count":57,"open_issues_count":6,"forks_count":20,"subscribers_count":7,"default_branch":"main","last_synced_at":"2025-03-31T10:11:12.920Z","etag":null,"topics":["compression","ddd","doctrine-orm","easyadmin","file-manager","hexagonal-architecture","image","layered-architecture","media","php","react","resize","symfony","symfony-bundle","thumbnails","twig","upload"],"latest_commit_sha":null,"homepage":"https://github.com/ranky/media-bundle","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ranky.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"chiqui3d"}},"created_at":"2022-12-16T22:34:03.000Z","updated_at":"2025-03-01T03:17:37.000Z","dependencies_parsed_at":"2024-09-25T00:30:57.091Z","dependency_job_id":null,"html_url":"https://github.com/ranky/media-bundle","commit_stats":{"total_commits":127,"total_committers":4,"mean_commits":31.75,"dds":0.03149606299212604,"last_synced_commit":"e4264ec355f4ed9f4bea30d66cd79bd8657f4ddf"},"previous_names":[],"tags_count":33,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ranky%2Fmedia-bundle","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ranky%2Fmedia-bundle/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ranky%2Fmedia-bundle/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ranky%2Fmedia-bundle/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ranky","download_url":"https://codeload.github.com/ranky/media-bundle/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252880590,"owners_count":21819023,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["compression","ddd","doctrine-orm","easyadmin","file-manager","hexagonal-architecture","image","layered-architecture","media","php","react","resize","symfony","symfony-bundle","thumbnails","twig","upload"],"created_at":"2024-09-24T14:08:25.940Z","updated_at":"2025-10-06T00:29:30.027Z","avatar_url":"https://github.com/ranky.png","language":"PHP","funding_links":["https://github.com/sponsors/chiqui3d"],"categories":[],"sub_categories":[],"readme":"# MediaBundle (Media File Manager for Symfony)\n\n[![CI](https://github.com/ranky/media-bundle/actions/workflows/ci.yaml/badge.svg)](https://github.com/ranky/media-bundle/actions/workflows/ci.yaml)\n\nMediaBundle is a media file manager bundle for Symfony with a REST API and an admin interface (React). It provides a clean and user-friendly way to upload, edit and delete files. It supports multiple formats. You can upload images, videos, audios, documents, zip files, etc.\n\nMediaBundle automatically compress your media files to reduce their size without sacrificing quality. Additionally, it offers the ability to resize your media files to fit specific dimensions (breakpoints), making it easy to ensure that your images are the correct size for your website.\n\nMediaBundle also integrates with your database to store and manage your media files efficiently. This means you can keep track of all your media assets in one place, similar to how WordPress manages media files. This way, you can have all your media files organized and accessible in one place.\n\nTable of Contents \n==================\n* [Video](#video)\n* [Advantages](#advantages)\n* [Features](#features)\n* [Requirements](#requirements)\n  * [Imagick extension](#imagick-extension)\n  * [Compression Tools](#compression-tools)\n* [Installation](#installation)\n* [Configuration](#configuration)\n  * [Full configuration](#full-configuration-with-default-values)\n  * [Configuration Explanation](#configuration-explanation)\n* [Usage](#usage)\n  * [Security](#security) \n  * [Media File Manager](#media-file-manager)\n  * [Form Types](#form-types)\n  * [Retrieve media files in Twig](#retrieve-media-files-in-twig)\n  * [Responsive images with Twig macro](#responsive-images-with-twig-macro)\n  * [Standalone button to open the Media File Manager in selection mode](#standalone-button-to-open-the-media-file-manager-in-selection-mode)\n  * [TinyMCE integration](#tinymce-integration)\n  * [EasyAdmin integration](#easyadmin-integration)\n  * [Events](#events)\n* [Caveats](#caveats)\n* [Extra](#extra)\n* [To Do](#to-do)\n* [License](#license)\n* [Donate](#donate)\n\n## Video\n\nhttps://user-images.githubusercontent.com/2461400/208309129-280d4fdb-d3f5-4cb7-bd32-175db6bb6f70.mp4\n\nhttps://user-images.githubusercontent.com/2461400/208732093-44cf5a21-62f9-4402-bbcf-0cffa4aa56f6.mp4\n\n## Advantages\n\n* Tested in projects with Symfony 5.4, ^6.0 and 7.0 (need to fix some deprecations)\n* Symfony form types for easy integration\n* EasyAdmin integration\n* TinyMCE integration\n\n## Features\n\n* User-friendly interface:\n  * Offers a wide range of configurable options, allowing you to customize its behavior to fit your needs\n  * Single or multiple upload\n  * Drag \u0026 drop\n  * List or grid view\n  * Edit alt, title and name\n  * Single and bulk delete\n  * Search filters\n  * Select single or multiple media with different form types\n  * Sort by date. Soon I will add more sort options\n  * Prevent media file name duplication\n* Resize images on upload, supported by:\n  * Imagick extension \n  * GD extension\n* Compression of images, supported by: \n  * Jpegoptim\n  * Optipng\n  * Pngquant\n  * Gifsicle\n  * Svgo\n  * Cwebp\n* Generate thumbnails with 4 types of breakpoint (responsive formats) with the following default sizes: \n  * **Large:** [1024], I only put the width because that way the height is automatically calculated by saving the aspect ratio. Although you can change it, you can see it in [configuration](#configuration)\n  * **Medium:** [768]\n  * **Small:** [576]\n  * **Xsmall:** [130, 130]\n\n## Requirements\n\n* PHP 8.1 or higher\n* Symfony 5.4 or higher\n* Doctrine ORM (MySQL, MariaDB, SQLite, PostgreSQL)\n* Imagick or GD extension: **Recommended Imagick** extension because it supports more formats than the GD extension\n* Optional compression tools:\n  * Jpegoptim\n  * Optipng\n  * Pngquant\n  * Gifsicle\n  * Svgo\n  * Cwebp\n\n### Imagick extension\n\n#### Requirements\nImagick extension requires the `imagemagick` library installed in your system.\nIf you are not going to use Docker remember that many distros come with outdated `imagemagick`  and no `WebP` support. Most likely you will have to compile it manually:\n\n##### Ubuntu example\n\n```bash\nsudo apt-get update\nsudo apt-get install build-essential\n```\n```bash\nsudo apt-get install pkg-config libx11-dev libxext-dev libxt-dev liblcms2-dev libwmf-dev libjpeg-dev libpng-dev libgif-dev libfreetype6-dev libtiff5-dev libwebp-dev libzip-dev\n```\n```bash\nwget https://www.imagemagick.org/download/ImageMagick.tar.gz\ntar xvzf ImageMagick.tar.gz\ncd ImageMagick-7.*\n./configure\nmake\nsudo make install\nidentify -version\nidentify -version | grep webp\n```\n\nDocker: [Dockerfile](tools/docker/php-fpm/build.Dockerfile)\n\n#### Install Imagick extension\n\n##### Case Docker\n[Dockerfile](tools/docker/php-fpm/build.Dockerfile)\n\n##### Case Pecl\n```sh\nsudo pecl install imagick\n```\n\n##### Enable the extension if it is not done automatically 👀\n```ini\n; php.ini\nextension=imagick.so\n```\n##### Verify Installation\n```bash\nphp -m | grep imagick\nphp -r 'phpinfo();' | grep imagick\nphp -r 'phpinfo();' | grep ImageMagick\n```\n\n### Compression Tools\nNone of these tools is mandatory; it already depends on the need to optimize the images or not.\n```sh \nsudo apt-get install jpegoptim\nsudo apt-get install optipng\nsudo apt-get install pngquant\nsudo apt-get install gifsicle\nsudo apt-get install webp\n```\n[Dockerfile](tools/docker/php-fpm/build.Dockerfile)\n\n## Installation\n\n```sh\ncomposer require ranky/media-bundle\n```\nWhile I create the recipes for Symfony Flex, here are the steps to follow:\n\n#### Step 1: Enable the bundle in the kernel\n\nAlthough this step should be done automatically.\n\n```php\n# config/bundles.php\nreturn [\n    // ...\n    Ranky\\MediaBundle\\RankyMediaBundle::class =\u003e ['all' =\u003e true],\n];\n```\n\n#### Step 2: Import the routes\n\nYAML\n\n```yaml \n# config/routes/ranky_media.yaml\nranky_media:\n  resource: \"@RankyMediaBundle/config/routes.php\"\n  prefix: /admin # Optional: The same prefix you use when importing the routes must be the \"api_prefix\" in the bundle options.\n```\n\nPHP\n\n```php\n# config/routes/ranky_media.php\nuse Symfony\\Component\\Routing\\Loader\\Configurator\\RoutingConfigurator;\n\nreturn static function (RoutingConfigurator $routes) {\n    $routes\n        -\u003eimport('@RankyMediaBundle/config/routes.php') // annotation or attributes\n        -\u003eprefix('/admin')\n        ;\n};\n```\n\n#### Step 3: Create the following file and configure it for your application.\n\nThe minimum required configuration is provided, in the [configuration](#configuration) you will see all the options.\n\nYAML\n```yaml \n# config/packages/ranky_media.yaml\nranky_media:\n  media_entity: App\\Entity\\Media\n  user_entity: App\\Entity\\User\n  api_prefix: /admin\n```\nPHP\n```php\n# config/packages/ranky_media.php\n  return static function (RankyMediaConfig $rankyMediaConfig) {\n    $rankyMediaConfig\n        -\u003emediaEntity(Media::class)\n        -\u003euserEntity(User::class)\n        -\u003eapiPrefix('/admin') // Optional: The same prefix you use when importing the routes must be the same here\n        ;\n};\n```\n\n#### Step 4: Media entity and repository\n\n```php\n# src/Entity/Media.php\nuse Doctrine\\ORM\\Mapping as ORM;\nuse Ranky\\MediaBundle\\Domain\\Model\\Media as BaseMedia;\n\n#[ORM\\Table(name: 'ranky_media')]\n#[ORM\\Entity(repositoryClass: MediaRepository::class)]\nclass Media extends BaseMedia\n{\n\n}\n```\n```php\n# src/Repository/MediaRepository.php\nclass MediaRepository extends DoctrineOrmMediaRepository\n{\n    public function __construct(\n        ManagerRegistry $registry,\n        DoctrineCriteriaBuilderFactory $doctrineCriteriaBuilderFactory,\n        UidMapperPlatform $uidMapperPlatform,\n    ) {\n        parent::__construct(\n            $registry,\n            $doctrineCriteriaBuilderFactory,\n            $uidMapperPlatform,\n            Media::class\n        );\n    }\n}\n```\n\n\n#### Step 5 (Optional): Add custom Dbal types\n\nCurrently, the `LoadClassMetadata` event is responsible for loading custom DBAL types. If this event does not work, you can add custom functions manually using the following examples:\n\nNote: This Event Subscriber may be removed in the future.\n\nPHP\n```php\n// config/packages/doctrine.php\n// Sqlite\nuse Ranky\\MediaBundle\\Infrastructure\\Persistence\\Dql\\Sqlite\\MimeSubType;\nuse Ranky\\MediaBundle\\Infrastructure\\Persistence\\Dql\\Sqlite\\MimeType;\nuse Ranky\\MediaBundle\\Infrastructure\\Persistence\\Dql\\Sqlite\\Month;\nuse Ranky\\MediaBundle\\Infrastructure\\Persistence\\Dql\\Sqlite\\Year;\n// MySQL\nuse Ranky\\MediaBundle\\Infrastructure\\Persistence\\Dql\\Mysql\\MimeSubType;\nuse Ranky\\MediaBundle\\Infrastructure\\Persistence\\Dql\\Mysql\\MimeType;\nuse Ranky\\MediaBundle\\Infrastructure\\Persistence\\Dql\\Mysql\\Month;\nuse Ranky\\MediaBundle\\Infrastructure\\Persistence\\Dql\\Mysql\\Year;\n// Postgres\nuse Ranky\\MediaBundle\\Infrastructure\\Persistence\\Dql\\Postgresql\\MimeSubTypee;\nuse Ranky\\MediaBundle\\Infrastructure\\Persistence\\Dql\\Postgresql\\MimeType;\nuse Ranky\\MediaBundle\\Infrastructure\\Persistence\\Dql\\Postgresql\\Month;\nuse Ranky\\MediaBundle\\Infrastructure\\Persistence\\Dql\\Postgresql\\Year;\nuse Symfony\\Config\\DoctrineConfig;\n\nreturn static function (DoctrineConfig $doctrineConfig): void {\n   // ...\n  $orm = $doctrineConfig-\u003eorm();\n  $em = $orm-\u003eentityManager('default');\n  $dql = $em-\u003edql();\n  $dql-\u003estringFunction('MIME_TYPE', MimeType::class);\n  $dql-\u003estringFunction('MIME_SUBTYPE', MimeSubType::class);\n  $dql-\u003edatetimeFunction('YEAR', Year::class);\n  $dql-\u003edatetimeFunction('MONTH', Month::class);\n}\n```\nYAML\n```yaml\n# config/packages/doctrine.yaml\ndoctrine:\n  orm:\n      dql:\n        string_functions:\n          MIME_TYPE: Ranky\\MediaBundle\\Infrastructure\\Persistence\\Dql\\Postgresql\\MimeType\n          MIME_SUBTYPE: Ranky\\MediaBundle\\Infrastructure\\Persistence\\Dql\\Postgresql\\MimeSubType\n        datetime_functions:\n          YEAR: Ranky\\MediaBundle\\Infrastructure\\Persistence\\Dql\\Postgresql\\Year\n          MONTH: Ranky\\MediaBundle\\Infrastructure\\Persistence\\Dql\\Postgresql\\Month\n```\n\n#### Step 5: Schema update and assets install\n\n```sh\nphp bin/console doctrine:schema:update --force\nphp bin/console assets:install\n```\n\n## Configuration\n\n### Full configuration with default values\nAll options are optional, but `user_entity` is necessary to configure if you do not want to include guest users in the user filter.\nAlso, the `media_entity` is required. The default value is `App\\Entity\\Media`, thanks to this option you can use your own entity. \n\n```php\n# config/packages/ranky_media.php\nreturn static function (RankyMediaConfig $rankyMediaConfig) {\n\n    $rankyMediaConfig\n        -\u003emediaEntity(Media::class)\n        -\u003euserEntity(User::class)\n        -\u003euserIdentifierProperty('username')\n        -\u003eapiPrefix(null)\n        -\u003euploadDirectory('%kernel.project_dir%/public/uploads')\n        -\u003euploadUrl('/uploads')\n        -\u003epaginationLimit(30)\n        -\u003edateTimeFormat('Y-m-d H:i')\n        -\u003emimeTypes([])\n        -\u003emaxFileSize(7340032)\n        -\u003edisableCompression(false)\n        -\u003ecompressOnlyOriginal(false)\n    ;\n\n    $rankyMediaConfig-\u003eimage()\n        -\u003eresizeDriver(ImageResizeDriver::IMAGICK-\u003evalue)\n        -\u003eresizeGifDriver(GifResizeDriver::NONE-\u003evalue)\n        -\u003equality(80)\n        -\u003eoriginalMaxWidth(1920)\n        -\u003ebreakpoints()\n            -\u003elarge([1024])\n            -\u003emedium([768])\n            -\u003esmall([576])\n            -\u003exsmall([130, 130])\n    ;\n};\n```\n\n### Configuration Explanation\n\n**user_entity** (string, default: `App\\Entity\\User`)\n\nThis is the fully qualified class name (FQCN) of the user entity class. This is required in order to get the username in case you are using a different UserIdentifier and in that way to be able to filter media by user.\n\n**Example:** `User::class`\n\n**media_entity** (string, default: `App\\Entity\\Media`)\nThis is the fully qualified class name (FQCN) of the media entity class. This is required in order to use own entity.\n\n**user_identifier_property** (string, default: `username`)\n\nThis is the property of the user entity that contains the user identifier. This is required if it is different from the `username`.\n\n**date_time_format** (string, default: `Y-m-d H:i`)\n\nThis is the format in which the creation and update date of a media are displayed in the list view and in the media modal.\n\n**api_prefix** (string, default: `null`)\n\nThis is mandatory if you want to import bundle routes with a prefix like \"/admin\", to follow some kinds of convention in your admin or panel.\n\nSee more in the [security](#security) section\n\nThis configuration will also create a global twig variable (`ranky_media_api_prefix`) that you can use later in your templates.\n\n**Example:** `/admin`\n\n**upload_directory** (string, default: `%kernel.project_dir%/public/uploads`)\n\nThis is the directory where uploaded files will be stored.\n\n**Example:** %kernel.project_dir%/public/uploads\n\n**upload_url** (string, default: `/uploads`)\n\nThis is the URL where uploaded files will be accessible.\n\n**Example:** `/uploads` or `https://mydomain.test/uploads`\n\n**mime_types** (array, default: `[]`)\n\nThis is an array of allowed MIME types. An empty array means that all MIME types are allowed.\n\n**Examples:** \n * []\n * ['image/jpeg', 'image/png', 'application/pdf'] \n * ['image/*']\n * ['.jpg', '.pdf']\n\n**disable_compression** (boolean, default: `false`)\n\nThis allows you to disable compression in order to avoid the small overhead that it produces after resizing.\n\n**compress_only_original** (boolean, default: `false`)\n\nThis will compress only the original image and will ignore thumbnails. This can be a good option as the thumbnails are already quite small, and sometimes it may not be necessary to compress them.\n\n**max_file_size** (integer, default: `7340032`)\n\nThis is the maximum allowed file size in `bytes`. The default value is 7340032 (7 MB).\n\n**pagination_limit** (integer, default: `30`)\n\nThis is the number of items that will be shown per page in the Media File Manager. The default value is 30.\n\n**image**\n\nThis is the configuration for the image settings.\n\n**resize_driver** (enum, default: `ImageResizeDriver::IMAGICK-\u003evalue`)\n\nThis is the driver that will be used for image resizing. The available drivers are:\n * imagick `ImageResizeDriver::IMAGICK-\u003evalue`\n * gd `ImageResizeDriver::GD-\u003evalue`\n\n**resize_gif_driver** (enum, default: `GifResizeDriver::NONE-\u003evalue`)\n\nThis is the driver that will be used for GIF image resizing. The available drivers are:\n* none `GifResizeDriver::NONE-\u003evalue`\n* ffmpeg `GifResizeDriver::FFMPEG-\u003evalue`\n* gifsicle `GifResizeDriver::GIFSICLE-\u003evalue`\n* imagick `GifResizeDriver::IMAGICK-\u003evalue`\n\n**quality** (integer, default: `80`)\n\nThis is the quality of the compression images. The default value is 80.\n\n**original_max_width** (integer, default: `1920`)\n\nThis is the maximum width for the original file. This will prevent the original file from being stored with a large number of megabytes. \nA null value will not resize the image.\n\n**breakpoints** (array, default: `['large' =\u003e [1024], 'medium' =\u003e [768], 'small' =\u003e [576], 'xsmall' =\u003e [130, 130]]`)\n\nThis is an array of breakpoints that will be used to generate thumbnails with different sizes. The available breakpoints are: \n* large `Breakpoint::LARGE-\u003edimensions()` [1024]\n* medium `Breakpoint::MEDIUM-\u003edimensions()` [768]\n* small `Breakpoint::SMALL-\u003edimensions()` [576]\n* xsmall `Breakpoint::XSMALL-\u003edimensions()` [130, 130]\n\nEach breakpoint has its own default dimensions, but these can be overridden by specifying custom dimensions in the configuration.\n\n## Usage\n\n### Security\nAll routes in this bundle start with `/ranky/media` without any kind of security.\nBut we all know that in Symfony it's very easy to protect a route with certain roles, like, for example:\n\n```yaml\n# config/packages/security.yaml\n  # Note: Only the *first* access control that matches will be used\n  access_control:\n    # additional security lives in the controllers\n    - { path: ^/ranky/media, roles: ROLE_USER }\n    - { path: ^/admin,     roles: ROLE_USER }\n    - { path: ^/admin/users, roles: ROLE_SUPER_ADMIN }\n    - { path: ^/login$,    roles: PUBLIC_ACCESS }\n    - { path: /, roles: PUBLIC_ACCESS }\n\n```\nWith this configuration you have already secured the Media File Manager with the role `ROLE_USER`.\n\nIt is also possible as we have seen previously that you want to import the routes of this bundle with the prefix of `/admin`, then all you have to do is to put the prefix in the security path:\n\n```yaml\n# Note: Only the *first* access control that matches will be used\n  access_control:\n    - { path: ^/admin/ranky/media, roles: ROLE_USER }\n    - { path: ^/admin,     roles: ROLE_USER }\n    - { path: ^/admin/users, roles: ROLE_SUPER_ADMIN }\n```\nDon't forget to set the [route prefix](#step-3-import-the-routes) in the bundle configuration.\n\n### Media File Manager\n\nOnce installed, you can access a route that I have created for quick access to the Media File Manager. The path is `/ranky/media/embed`.\n\nBut the idea is that you create a page in your admin.\n\nExample:\n  * router: `/admin/media`\n  * template: `media.html.twig`\n\n```twig\n{# media.html.twig #}\n{# Note that it is not mandatory to use blocks, you can import the assets as you prefer #}\n\n{% block stylesheets %}\n    {{- parent() -}}\n    {{ encore_entry_link_tags('ranky_media', null, 'ranky_media') }}\n{% endblock %}\n\n{% block javascripts %}\n    {{- parent() -}}\n    {{ encore_entry_script_tags('ranky_media', null, 'ranky_media', attributes={\n        defer: true\n    }) }}\n{% endblock %}\n\n{% block body %}\n    \u003cdiv class=\"ranky-media\" data-api-prefix=\"{{ ranky_media_api_prefix }}\"\u003e\u003c/div\u003e\n{% endblock %}\n```\n\n### Form types\n\nOnly one type of form, but with 4 ways to store the data in the database:\n\n#### 1. Single selection. Store the mediaId (Ulid) without association\n\n`media_id` type is a Doctrine ULID type\n\n```php\n#[ORM\\Column(name: 'media_id', type: 'media_id', nullable: true)]\nprivate ?MediaId $mediaId;\n```\n\n```php\nuse Ranky\\MediaBundle\\Presentation\\Form\\RankyMediaFileManagerType;\n\nclass MyFormType extends AbstractType\n{\n    public function buildForm(FormBuilderInterface $builder, array $options)\n    {\n        $builder\n            -\u003eadd('mediaId', RankyMediaFileManagerType::class, [\n                'label' =\u003e 'Media Id',\n                'modal_title' =\u003e 'Featured image',\n            ])\n        ;\n    }\n}\n```\n\n\n#### 2. Multiple selection. Store the array of mediaId (json) without association\n\n```php\n#[ORM\\Column(name: 'gallery', type: Types::JSON, nullable: true)]\nprivate ?array $gallery;\n```\n\n```php\nuse Ranky\\MediaBundle\\Presentation\\Form\\RankyMediaFileManagerType;\n\nclass MyFormType extends AbstractType\n{\n    public function buildForm(FormBuilderInterface $builder, array $options)\n    {\n        $builder\n            -\u003eadd('gallery', RankyMediaFileManagerType::class, [\n                'label' =\u003e 'Array of Ids',\n                'multiple' =\u003e true,\n                'modal_title' =\u003e 'Gallery',\n            ])\n        ;\n    }\n}\n```\n\n#### 3. Single selection. Store path without association\n\n```php\n#[ORM\\Column(name: 'image', type: Types::STRING, nullable: true)]\npublic string $image;\n```\n\n```php\nuse Ranky\\MediaBundle\\Presentation\\Form\\RankyMediaFileManagerType;\n\nclass MyFormType extends AbstractType\n{\n    public function buildForm(FormBuilderInterface $builder, array $options)\n    {\n        $builder\n            -\u003eadd('image', RankyMediaFileManagerType::class, [\n                'label' =\u003e 'Image',\n                'save_path' =\u003e true,\n                'modal_title' =\u003e 'Select image',\n            ])\n        ;\n    }\n}\n```\n\n#### 4. Multiple selection. Store the array of paths without association\n\n```php\n#[ORM\\Column(name: 'gallery', type: Types::JSON, nullable: true)]\nprivate ?array $gallery;\n```\n\n```php\nuse Ranky\\MediaBundle\\Presentation\\Form\\RankyMediaFileManagerType;\n\nclass MyFormType extends AbstractType\n{\n    public function buildForm(FormBuilderInterface $builder, array $options)\n    {\n        $builder\n            -\u003eadd('gallery', RankyMediaFileManagerType::class, [\n                'label' =\u003e 'Array of paths',\n                'multiple' =\u003e true,\n                'save_path' =\u003e true,\n                'modal_title' =\u003e 'Gallery',\n            ])\n        ;\n    }\n}\n```\n\n#### 5. Single selection. Store the mediaId (Ulid) with ManyToOne association\n\n```php\n#[ORM\\ManyToOne(targetEntity: Media::class)]\n#[ORM\\JoinColumn(name: 'media', referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]\nprivate ?Media $media;\n```\n\n```php\nuse Ranky\\MediaBundle\\Presentation\\Form\\RankyMediaFileManagerType;\n\nclass MyFormType extends AbstractType\n{\n    public function buildForm(FormBuilderInterface $builder, array $options)\n    {\n        $builder\n            -\u003eadd('media', RankyMediaFileManagerType::class, [\n              'label'              =\u003e 'Media ManyToOne',\n              'association'        =\u003e true,\n              'modal_title'        =\u003e 'Featured image',\n            ])\n        ;\n    }\n}\n```\n\n#### 6. Multiple selection. Store the media collection with ManyToMany association\n\n```php\n#[ORM\\JoinTable(name: 'pages_medias')]\n#[ORM\\JoinColumn(name: 'page_id', referencedColumnName: 'id', onDelete: 'CASCADE')]\n#[ORM\\InverseJoinColumn(name: 'media_id', referencedColumnName: 'id', onDelete: 'CASCADE')]\n#[ORM\\ManyToMany(targetEntity: Media::class)]\n#[ORM\\OrderBy([\"createdAt\" =\u003e \"DESC\"])]\nprivate ?Collection $medias;\n\npublic function __construct()\n{\n    $this-\u003emedias = new ArrayCollection();\n}\n```\n\n```php\nuse Ranky\\MediaBundle\\Presentation\\Form\\RankyMediaFileManagerType;\n\nclass MyFormType extends AbstractType\n{\n    public function buildForm(FormBuilderInterface $builder, array $options)\n    {\n        $builder\n            -\u003eadd('medias', RankyMediaFileManagerType::class, [\n                'label'              =\u003e 'Media Collection',\n                'association'        =\u003e true,\n                'modal_title'        =\u003e 'Gallery',\n                'multiple_selection' =\u003e true,\n            ])\n        ;\n    }\n}\n```\n\n### Retrieve media files in Twig\nTo retrieve the media files, you have a global twig service will help you. Let's see the examples in the same order as we have looked in the types of forms.\n\nService: `ranky_media` [RankyMediaTwigExtension](src/Presentation/Twig/MediaTwigExtension.php)\n\n  * **findById:** Retrieve a media response by MediaId \n  * **findByIds:** Retrieve a collection of media response by MediaId array\n  * **findByPath:** Retrieve a media response by path\n  * **findByPaths:** Retrieve a collection of media response by path array\n  * **mediaToResponse:** Convert a media entity to a media response\n  * **mediaCollectionToArrayResponse:** Convert a collection of media entity to a collection of media response\n\nExtension:  \n    * **ranky_media_url:** Twig function to retrieve the url of a media file\n    * **ranky_media_thumbnail_url:** Twig function to retrieve the url of a thumbnail\n\n#### Example\n```twig\n{# @var media \\Ranky\\MediaBundle\\Application\\DataTransformer\\Response\\MediaResponse #}\n{% set media = ranky_media.findById(page.mediaId) %}\n{# {% set medias = ranky_media.findByIds(page.gallery) %} #}\n{{ dump(media) }}\n{% if media %}\n  \u003cp\u003e\n      \u003cimg src=\"{{ media.file.url }}\"\n           alt=\"{{ media.description.alt }}\"\n           title=\"{{ media.description.title }}\"\n      /\u003e\n  \u003c/p\u003e\n  \u003cp\u003e{{ ranky_media_url(media) }}\u003c/p\u003e\n  \u003cp\u003eThumbnails\u003c/p\u003e\n  {% for thumbnail in media.thumbnails %}\n      {# @var thumbnail \\Ranky\\MediaBundle\\Application\\DataTransformer\\Response\\ThumbnailResponse #}\n      \u003cp\u003eThumbnail\n          breakpoint: {{ thumbnail.breakpoint }} {{ thumbnail.dimension.asString }}\u003c/p\u003e\n      \u003cimg src=\"{{ thumbnail.url }}\"\n           alt=\"{{ media.description.alt }}\"\n           title=\"{{ media.description.title }}\"\n      /\u003e\n      \u003cp\u003e{{ ranky_media_url(thumbnail.url) }}\u003c/p\u003e\n      \u003cp\u003e{{ ranky_media_thumbnail_url(thumbnail.path, thumbnail.breakpoint) }}\u003c/p\u003e\n  {% endfor %}\n{% endif %}\n```\n\n### Responsive images with Twig macro\n\n```twig\n{% import '@RankyMedia/macros.html.twig' as rankyMedia %}\n{% set media = ranky_media.findById(page.mediaId) %}\n{{ rankyMedia.reponsive_image(media) }}\n```\n\n### Standalone button to open the Media File Manager in selection mode\n\nYou can use the same options as in the [form](templates/form.html.twig).\nRemember to import the assets first\n```twig\n{{ encore_entry_script_tags('ranky_media', null, 'ranky_media', attributes={\ndefer: true }) }}\n{{ encore_entry_link_tags('ranky_media', null, 'ranky_media') }}\n```\nThe `ranky-media-open-modal` class is required, In order not to conflict with the form types.\n```html\n\u003cbutton class=\"ranky-media-open-modal\" data-api-prefix=\"{{ranky_media_api_prefix}}\" data-modal-title=\"Media File Manager\" data-multiple-selection=\"true\"\u003e\n  Media File Manager\n\u003c/button\u003e\n```\n```js\n document.addEventListener('DOMContentLoaded', () =\u003e {\n  [...document.querySelectorAll('.ranky-media-open-modal')].forEach((element) =\u003e {\n    element.addEventListener('ranky-media:selected-media',(event) =\u003e {\n      console.log(event.detail);\n    })\n  });\n});\n```\n\n### TinyMCE integration\n![TinyMCE Ranky Media Bundle](https://user-images.githubusercontent.com/2461400/209868439-f92bcbec-3a04-443c-a909-4d216a844961.jpg)\n\nThe `ranky-media-open-modal` class is required.\n\n\n```twig\n\u003cdiv class=\"form-group\"\u003e\n    \u003cbutton type=\"button\"\n            data-multiple-selection=\"true\"\n            data-api-prefix=\"{{ ranky_media_api_prefix }}\"\n            class=\"btn btn-alt-primary ranky-media-open-modal\"\u003e\n        \u003ci class=\"fas fa-photo-video\"\u003e\u003c/i\u003e Media File Manager\n    \u003c/button\u003e\n    {# Textarea with TinyMCE #}\n    {{ form_row(form.content) }}\n\u003c/div\u003e\n```\n```js\ndocument.addEventListener('DOMContentLoaded', () =\u003e {\n  [...document.querySelectorAll('.ranky-media-open-modal')].forEach((element) =\u003e {\n    element.addEventListener('ranky-media:selected-media',(event) =\u003e {\n      event.detail.medias?.forEach((media) =\u003e {\n        if (media.file.mimeType === 'image') {\n          const imageTag = `\u003cimg\n                            width=\"500\"\n                            src=\"${media.file.url}\"\n                            alt=\"${media.description.alt}\"\n                            title=\"${media.description.title}\"\n                        /\u003e`;\n          tinymce.activeEditor.insertContent(imageTag);\n        }\n      })\n    })\n  });\n});\n```\n  \n\n### EasyAdmin integration\n\nA [field](src/Presentation/Form/EasyAdmin/EARankyMediaFileManagerField.php) for EasyAdmin has been created that integrates the same functionalities previously explained in [Form Types](#form-types)\n\nHere, I show an example of the six variations:\n\n```php\nuse Ranky\\MediaBundle\\Presentation\\Form\\EasyAdmin\\EARankyMediaFileManagerField;\n\n// ...\n\npublic function configureFields(string $pageName): iterable\n    {\n       // ...\n        yield EARankyMediaFileManagerField::new('path')-\u003esavePath();\n        yield EARankyMediaFileManagerField::new('paths')-\u003emultipleSelection()-\u003esavePath();\n        yield EARankyMediaFileManagerField::new('mediaId');\n        yield EARankyMediaFileManagerField::new('gallery')-\u003emultipleSelection()-\u003emodalTitle('Gallery');\n        yield EARankyMediaFileManagerField::new('media')-\u003eassociation();\n        yield EARankyMediaFileManagerField::new('medias')-\u003eassociation()-\u003emultipleSelection();\n    }\n```\n\n### Events\n\n* PreCreateEvent::NAME\n* PostCreateEvent::NAME\n* PreUpdateEvent::NAME\n* PostUpdateEvent::NAME\n* DeleteEvent::PRE_DELETE\n* DeleteEvent::POST_DELETE\n\n```php\nuse Ranky\\MediaBundle\\Infrastructure\\Event\\PreCreateEvent;\nuse Symfony\\Component\\EventDispatcher\\EventSubscriberInterface;\n\nclass MyEventSubscriber implements EventSubscriberInterface\n{\n    public static function getSubscribedEvents()\n    {\n        return [\n            PreCreateEvent::NAME =\u003e 'onPreCreate',\n        ];\n    }\n\n    public function onPreCreate(PreCreateEvent $event)\n    {\n        // do something\n    }\n}\n```\n\n## Caveats\n\n* Currently, [Uppy](https://github.com/transloadit/uppy) is used to support file uploads through the Media File Manager, and it requires [SSL certificate](#install-local-certificates) or be available via localhost.\n  See more https://github.com/transloadit/uppy/issues/4133\n* If you are using React, you will have a problem because this bundle adds React, and you can have two versions of React on one page. This will be fixed as soon as I registered a package in NPM.\n* PostgreSQL, MariaDB, MySQL and SQLite are supported by Doctrine ORM. Doctrine MongoDB ODM not supported yet.\n\n## Extra\n\n### Demo\n\nIn a few days there will be a complete demo. For now, you can watch the [video](#video)\n\n### Install local certificates\n\n```sh\nmkcert -install\nmkcert \"*.example.test\"\n```\nMore info https://github.com/FiloSottile/mkcert\n\n### Docker\nYou can see how to install PHP extensions and compression tools through Docker in the [Dockerfile](tools/docker/php-fpm/build.Dockerfile) I used it for testing.\n\n\n## To Do\n- [ ] Private media files\n- [ ] ~~Recipes~~\n- [ ] Adapters for [file storage](https://github.com/thephpleague/flysystem-bundle): S3, Azure, Google Cloud, etc.\n- [ ] Support for php-vips (libvips) https://github.com/libvips/php-vips\n- [ ] Image Editor\n- [ ] Create NPM package, so you can use/import and not have multiple versions of React \n- [ ] `ORDER BY FIELD` in `WHERE IN` clause\n- [ ] Add more sorting filters\n- [ ] PDF compression with Ghostscript\n- [ ] Video compression and resizing with FFmpeg\n- [ ] Audio compression\n- [ ] Create, view and edit EXIF data\n- [ ] Creation and organization of directories\n- [ ] Add more tests\n\n## License\n\nMediaBundle is licensed under the MIT License – see the [LICENSE](LICENSE) file for details\n\n## Donate\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Franky%2Fmedia-bundle","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Franky%2Fmedia-bundle","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Franky%2Fmedia-bundle/lists"}