PHPackages                             jdz/mediamanager - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [File &amp; Storage](/categories/file-storage)
4. /
5. jdz/mediamanager

ActiveLibrary[File &amp; Storage](/categories/file-storage)

jdz/mediamanager
================

Framework-agnostic media library management: browse, reshape and fill a media folder, with upload validation, watermarking and picker trees

2.0.0(3w ago)031MITPHPPHP &gt;=8.2

Since Jul 11Pushed 3w ago1 watchersCompare

[ Source](https://github.com/joffreydemetz/mediaManager)[ Packagist](https://packagist.org/packages/jdz/mediamanager)[ Docs](https://jdz.joffreydemetz.com/mediamanager)[ RSS](/packages/jdz-mediamanager/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (2)Dependencies (12)Versions (3)Used By (1)

JDZ MediaManager
================

[](#jdz-mediamanager)

Framework-agnostic media library management: browse, reshape and fill a media folder — plus upload validation, watermarking and folder trees for pickers.

Installation
------------

[](#installation)

```
composer require jdz/mediamanager
```

MediaManager
------------

[](#mediamanager)

`JDZ\MediaManager\Manager\MediaManager` is the whole API. Point it at a folder and it owns everything under it.

```
use JDZ\MediaManager\Manager\MediaManager;
use JDZ\MediaManager\ValueObject\MediaConfig;

$manager = new MediaManager(MediaConfig::fromArray([
  'rootPath'           => $publicPath . 'media/',
  'systemFolders'      => ['pages', 'documents'],  // structural, cannot be moved/renamed/deleted
  'extsImage'          => ['png', 'gif', 'jpg', 'jpeg'],
  'extsDocument'       => ['pdf'],
  'mimesImage'         => ['image/png', 'image/gif', 'image/jpeg'],
  'mimesDocument'      => ['application/pdf'],
  'maxWeightImage'     => 2,      // Mo, per file
  'maxWeightDocument'  => 10,     // Mo, per file
  'maxWeightFiles'     => 3000,   // Mo, whole library (0 = no quota)
  'maxNumFiles'        => 6000,   // whole library (0 = no quota)
  'maxPictureLongSide' => 1200,   // px, images above are downscaled on upload
  'protectPath'        => $publicPath . 'protect/',
  'watermarkPath'      => $publicPath . 'media/nepascopier.png',
]));
```

### Browsing

[](#browsing)

```
$manager->getFilesystem('photos/2024', 'images', $thumbResolver);
// ['value' => 'photos/2024', 'name' => '2024', 'previous' => 'photos',
//  'folders' => [['value','name','previous','type','icon'], ...],
//  'files'   => [['value','name','ext','thumb'], ...]]

$manager->getBreadcrumbs('photos/2024');  // [['folder' => '', 'title' => 'Medias'], ['folder' => 'photos', ...], ...]
$manager->getFolderSelectList();          // [['value','name','level'], ...], flattened, 'Racine' first
$manager->getTree('photos/2024');         // ROOT node + nested children, active branch flagged
$manager->getSelectorList('', '../media/', $imageResolver);  // images grouped per folder, for a picker
```

Thumbnails are **not** this package's business. `getFilesystem()` and `getSelectorList()` take optional resolvers so the consumer plugs in its own image pipeline:

```
$thumbResolver = fn (string $relPath, string $fileName): ?string => $myImager->thumb($relPath, 150);
$imageResolver = fn (string $relPath): ?array => ['thumb' => ..., 'orientation' => 'landscape'];  // null drops the file
```

### Reshaping

[](#reshaping)

Every mutation returns an `OperationResult`.

```
$result = $manager->moveFolder('photos/2024', 'archive');

if (false === $result->isSuccess()) {
  echo $translator->translate($result->key, $result->params);
} else {
  $newPath = $result->get('folder');
}
```

```
$manager->createFolder('photos', '2025');
$manager->renameFolder('photos/2024', 'archives-2024');   // data: folder
$manager->moveFolder('photos/2024', 'archive');           // data: folder
$manager->deleteFolder('photos/2024');                    // data: parent — refuses non-empty
$manager->renameFile('photos', 'img.jpg', 'sunset');      // data: file — extension is preserved
$manager->moveFile('photos', 'img.jpg', 'archive');       // data: folder, file
$manager->deleteFile('photos', 'img.jpg');
$manager->resolveDownloadPath('photos', 'img.jpg');       // absolute path, or null
```

### Uploading

[](#uploading)

```
$result = $manager->upload($request->files->get('fileUploadName'), 'photos', $force);

if (true === $result->isSuccess()) {
  echo $result->filename;   // slugged, collision-free, downscaled if needed
  echo $result->value();    // 'photos/mon-image.jpg'
}
```

Validates mime and weight against the config, slugs the name (camelCase split, transliterated, lowercased, dash-separated), resolves collisions with `-1`, `-2`, `-3` unless `$force`, then downscales oversized pictures in place (GD via Imagine, EXIF autorotate). A failed resize never fails the upload.

Library quotas are enforced up front — `$manager->getStats()` exposes the counters and the verdict:

```
$stats = $manager->getStats();   // numFiles, weightFiles (Mo), maxNumFiles, maxWeightFiles, uploadable, errors
```

### Watermarking

[](#watermarking)

```
$manager->protectImage('photos', 'img.jpg');    // backs the original up, stamps the watermark over it
$manager->unprotectImage('photos', 'img.jpg');  // restores it, drops the backup
$manager->isProtected('img.jpg');
```

Backups live flat under `protectPath`, filed under the file's own name — two files sharing a basename in different folders share one backup slot.

Paths and safety
----------------

[](#paths-and-safety)

Folder paths travel through the browser with their slashes swapped for `[-]`. `decodePath()` / `encodePath()` handle the codec; run every inbound folder value through `decodePath()`.

Every path is canonicalized and checked to be inside the root before anything touches the disk — `..` segments, absolute paths and directory parts in file names are refused with `MEDIAMANAGER_ERROR_INVALID_PATH`. Paths stay forward-slashed on every platform.

Translation keys
----------------

[](#translation-keys)

The package never renders a message. It returns keys, which the consumer translates:

**Success**`MEDIAMANAGER_SUCCESS_DIRCREATE`, `_DIRRENAME`, `_DIRMOVE`, `_DIRDELETE`, `_FILRENAME`, `_FILMOVE`, `_FILDELETE` (`%filename%`), `_PROTECT`, `_UNPROTECT`**Folders**`MEDIAMANAGER_ERROR_ROOT_CANNOT_BE_CHANGED`, `_ROOT_FOLDERS_CANNOT_BE_CHANGED`, `_ENTER_FOLDER_NAME`, `_ENTER_FOLDER_NEW_NAME`, `_FOLDER_NAME_UNCHANGED`, `_FOLDER_NOT_EMPTY`, `_SOURCE_FOLDER_INVALID`, `_SOURCE_FOLDER_NOT_FOUND`, `_DESTINATION_FOLDER_NOT_FOUND`, `_DESTINATION_FOLDER_ALREADY_EXISTS`, `_CANNOT_MOVE_INTO_ITSELF`**Files**`MEDIAMANAGER_ERROR_NO_FILE_SPECIFIED`, `_ENTER_FILE_NEW_NAME`, `_FILE_NAME_UNCHANGED`, `_SOURCE_FILE_NOT_FOUND`, `_DESTINATION_FILE_ALREADY_EXISTS`, `_SAME_FILEPATH`, `_NOT_AN_IMAGE`, `_PROTECT_ORIGINAL_NOT_FOUND`**Uploads**`MEDIAMANAGER_ERROR_UPLOAD_FILE_TOO_BIG` (`%maxFileSize%`), `_UPLOAD_FILE_UNAUTH_EXT` (`%authExts%`), `_UPLOAD_TOO_MANY_FILES` (`%maxFileCount%`), `_UPLOAD_FILESYSTEM_FULL` (`%maxWeightFiles%`)**Generic**`MEDIAMANAGER_ERROR_INVALID_PATH`, `MEDIAMANAGER_ERROR_OPERATION_FAILED` (`%error%`)Trees
-----

[](#trees)

`ImageTree` builds a nested folders/files structure; `DocumentTree` builds a flat list with per-type icons. Both skip entries whose basename starts with `_`, and accept ignore lists. Reachable directly, or through `getRedactorImageTree()` / `getRedactorDocumentTree()`.

```
use JDZ\MediaManager\Tree\ImageTree;
use JDZ\MediaManager\Tree\DocumentTree;

$images = (new ImageTree())
  ->setBasePath($publicPath . 'media/')
  ->setBaseUrl('../media/')
  ->setIgnoreFolders(['template'])
  ->setIgnoreFiles(['share.jpg'])
  ->getTree();
// ['folders' => [['label','path','files' => [...]], ...], 'files' => [['url','id','title','ext'], ...]]

$documents = (new DocumentTree())
  ->setBasePath($publicPath . 'media/')
  ->setBaseUrl('../media/')
  ->getTree();
// [['id','title','text','icon','url','ext'], ...]
```

Upgrading from 1.x
------------------

[](#upgrading-from-1x)

`JDZ\MediaManager\Upload` is gone — its work is done by `MediaManager::upload()`, which takes an explicit target folder, enforces library quotas and returns an `UploadResult` instead of throwing. `Tree/` is unchanged.

Tests
-----

[](#tests)

```
composer test
```

License
-------

[](#license)

MIT — see [LICENSE](LICENSE).

###  Health Score

40

—

FairBetter than 86% of packages

Maintenance94

Actively maintained with recent releases

Popularity4

Limited adoption so far

Community9

Small or concentrated contributor base

Maturity47

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~25 days

Total

2

Last Release

25d ago

Major Versions

1.0.0 → 2.0.02026-08-05

### Community

Maintainers

![](https://www.gravatar.com/avatar/5e83e3701566e43438525ed14578487e732b849d152b5071aa1613a0dad96913?d=identicon)[jdz](/maintainers/jdz)

---

Top Contributors

[![joffreydemetz](https://avatars.githubusercontent.com/u/15113527?v=4)](https://github.com/joffreydemetz "joffreydemetz (5 commits)")

---

Tags

uploadadminutilitiesmediamanagerJDZ

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/jdz-mediamanager/health.svg)

```
[![Health](https://phpackages.com/badges/jdz-mediamanager/health.svg)](https://phpackages.com/packages/jdz-mediamanager)
```

###  Alternatives

[easycorp/easyadmin-bundle

Admin generator for Symfony applications

4.3k18.7M440](/packages/easycorp-easyadmin-bundle)[sulu/sulu

Core framework that implements the functionality of the Sulu content management system

1.4k1.4M241](/packages/sulu-sulu)[contao/core-bundle

Contao Open Source CMS

1231.7M3.1k](/packages/contao-core-bundle)[symfony/framework-bundle

Provides a tight integration between Symfony components and the Symfony full-stack framework

3.6k263.2M12.7k](/packages/symfony-framework-bundle)[drupal/core-recommended

Locked core dependencies; require this project INSTEAD OF drupal/core.

7544.4M460](/packages/drupal-core-recommended)[shopware/core

Shopware platform is the core for all Shopware ecommerce products.

605.9M709](/packages/shopware-core)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
