Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Easycrop Laravel Package

insitaction/easycrop

EasyCrop is a Laravel package that simplifies image cropping with a ready-to-use cropping workflow, configurable options, and straightforward integration for handling uploaded and saved images in PHP applications.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install and confirm the constraint fits your stack.

composer require insitaction/easycrop:^1.0.5
composer why-not insitaction/easycrop

v1.0.5 (the current release) explicitly allows Symfony 7, so the old advice to expect a composer.json constraint bump is no longer the default. Run why-not anyway — if it reports a conflict now, it's your Stimulus version or a transitive dependency, not the Symfony constraint. Only fall back to patching if why-not points at a package you can't move.

2. Register the bundle. In config/bundles.php:

return [
    // ...
    Insitaction\EasyCropBundle\EasyCropBundle::class => ['all' => true],
];

Confirm the namespace in vendor/insitaction/easycrop/composer.json (autoload.psr-4) before copying this — the package name and the bundle namespace frequently don't match in smaller bundles.

3. Import the routes.

# config/routes/easycrop.yaml
easycrop:
    resource: '@EasyCropBundle/Resources/config/routing.yaml'

If that path is wrong, php bin/console debug:router | grep -i crop will tell you what actually got registered.

4. Wire the Stimulus controller. v1.0.5 ships a bugfix for its Stimulus dependency, which confirms the widget is a UX Stimulus controller, not a standalone script. With Flex, check assets/controllers.json picked up the controller and that @hotwired/stimulus is installed. With AssetMapper, add the controller to importmap.php yourself. If the controller never connects, the widget renders as a dead <img> with no error — see the asset notes below.

5. Dump the config reference before writing any YAML. This is the single fastest way to learn the bundle's real surface area:

php bin/console config:dump-reference easy_crop

6. Where to look first, in order:

  • vendor/insitaction/easycrop/README.md — refreshed in v1.0.5, so read it before the older advice you'll find in issues. Still treat it as advertising.
  • src/DependencyInjection/Configuration.php — the authoritative list of options, including any allowed-mimetype config touched by the v1.0.5 bugfix.
  • src/Controller/ — the endpoint the JS widget posts to. This defines your payload shape.
  • src/Resources/public/ and assets/ — the Stimulus controller and CSS you must register through your build pipeline.

7. First use case. The canonical flow is: user uploads a full-size image → the crop widget overlays a box → the box is posted to the crop endpoint → a cropped file is written. Start by wiring that end-to-end on one entity (a User avatar or a Post thumbnail) before touching anything else. You want to see a file land on disk with the dimensions you asked for; everything else is refinement.

Implementation Patterns

Non-destructive crops (preferred). Store the crop box on your entity, not the pixels:

#[ORM\Column(type: 'integer')] private int $cropX = 0;
#[ORM\Column(type: 'integer')] private int $cropY = 0;
#[ORM\Column(type: 'integer')] private int $cropWidth = 0;
#[ORM\Column(type: 'integer')] private int $cropHeight = 0;

Then render on demand — via the bundle's service, or by handing the box to LiipImagineBundle as a crop filter. You keep the original, users can re-crop, and you never lose pixels to a bad drag.

Destructive crops (escape hatch). Only when storage is genuinely constrained. Always write to image.original.jpg first, then overwrite, and keep the backup for a week. Once a user saves a bad crop over their only copy, you have a support ticket.

Form integration. Cropping is a two-phase operation and should look like one to the user: upload field on the form, crop widget on a follow-up step or in a modal after save. Keep the crop fields as hidden inputs populated by the Stimulus controller, and validate server-side that the box is inside the image bounds.

$builder
    ->add('imageFile', FileType::class, ['mapped' => false, 'required' => false])
    ->add('cropX', HiddenType::class)
    ->add('cropY', HiddenType::class)
    ->add('cropWidth', HiddenType::class)
    ->add('cropHeight', HiddenType::class);

Frontend assets. The bundle ships a Stimulus controller plus static CSS, not an Encore entry.

  • Webpack Encore + Flex: php bin/console assets:install public, confirm the controller appears in assets/controllers.json, then import your normal bootstrap.js. package.json must include @hotwired/stimulus and @symfony/stimulus-bundle.
  • AssetMapper: add the bundle's controller path to importmap.php and run php bin/console importmap:require @hotwired/stimulus. Skipping this is the "silently doesn't load" failure mode — no console error, just an inert widget.
  • Neither (plain script tag): the Stimulus bugfix in v1.0.5 won't help you; you'll be reimplementing the widget's event handlers. Don't.

Version-pin Stimulus. Because v1.0.5 changes the bundle's Stimulus dependency handling, lock @hotwired/stimulus in package.json rather than floating. A Stimulus 3.2 vs 3.0 mismatch presents as "controller registered but never initialised", which is a miserable two-hour debug.

Coordinate space discipline. Decide once whether the box is in displayed pixels or natural image pixels, and enforce it everywhere. The cleanest approach: have the JS send naturalWidth/naturalHeight alongside the box, and scale server-side. Mixing spaces is the most common source of crops that are 30% off.

Multi-image entities and galleries. Loop the crop per attachment and key the payload (items[3][x], items[3][y], …). One request, one DB flush. Don't make the user save the entity five times.

Testing. A functional test beats a unit test here, because the real risk is the controller contract. After v1.0.5 specifically, add a mimetype case — the release fixes a mimetype bug, so assert both the accept path (PNG/JPEG) and the reject path (a .php renamed to .jpg, an SVG if unsupported):

$client->request('POST', '/easycrop', ['path' => 'uploads/test.jpg', 'x' => 10, 'y' => 10, 'width' => 100, 'height' => 100]);
$this->assertResponseIsSuccessful();
[$w, $h] = getimagesize($path);
$this->assertSame([100, 100], [$w, $h]);

$client->request('POST', '/easycrop', ['path' => 'uploads/evil.php.jpg', /* ... */]);
$this->assertResponseStatusCodeSame(400);

Gotchas and Tips

Path traversal is still the top risk. A crop endpoint that accepts a path and writes a file is a file-write primitive. v1.0.5 was a dependency and docs release — it did not touch the controller's security posture. If the controller takes the path from the request, wrap it in a voter or signed payload, resolve it with realpath() and assert it's inside your upload directory, and reject anything with .. or a leading /. Do not ship it as-is.

Re-test your upload validation on upgrade. The v1.0.5 mimetype fix means the accept/reject behaviour differs from v1.0.4. If you previously worked around a mime check in your own form type, that workaround is now layered on top of changed bundle behaviour — remove it and re-run your accept/reject tests for every format you support (JPEG, PNG, WebP, HEIC).

Still effectively a one-maintainer package. The Symfony 7 support is real and welcome, but this is 1 star, 0 dependents, and a repo that went ~2 years between releases. Plan accordingly: pin the exact version (1.0.5), keep your own functional tests around the endpoint, and budget a day for a fork if you need a patch. Treat the new documentation as improved but not guaranteed-current.

Symfony 7 is now allowed but not battle-tested. The constraint is open; the CI evidence isn't in the release notes. Smoke-test the full flow (upload → widget → crop → file on disk) on your actual Symfony version before committing the upgrade, and check composer why-not if anything pulls an older Stimulus Bundle.

Config defaults won't match your layout. Bundles of this vintage often default to %kernel.root_dir%/../web/uploads. On a modern Symfony app that path doesn't exist and the crop fails with a confusing "unable to open" rather than a config error. Set the upload/web dir explicitly on day one.

EXIF orientation. Phone photos carry an orientation flag. Crop the raw bytes without auto-orienting first and the box lands in the wrong place on roughly half of mobile uploads. Run Imagick::autoOrientImage() (or the GD equivalent) before cropping, or normalise on upload.

Memory and driver. A 12MP JPEG decoded with GD needs ~50MB. Batch-cropping a gallery blows the default memory_limit. Check php -m | grep -E 'gd|imagick', and bump to 256M+ if you accept large uploads.

Cache busting. Overwriting a file at the same path means browsers and CDNs serve the old crop for hours. Append ?v= . the crop timestamp to the URL, or write to a new filename each time.

Debugging checklist:

php bin/console debug:router | grep -i crop
php bin/console debug:container --tag=twig.extension | grep -i crop
php bin/console config:dump-reference easy_crop
php bin/console debug:stimulus            # is the controller registered?
php bin/console assets:install public     # is the JS actually on disk?

Then in the browser: does the element carry data-controller with the bundle's identifier, and is the controller present in the compiled assets/ bundle? Those two questions resolve most "the widget doesn't appear" reports. Log the incoming crop payload verbatim on the first integration — nine times out of ten the bug is visible in the numbers.

Extension points worth knowing. Override the route's controller in config/routes/easycrop.yaml (defaults: { _controller: App\Controller\CropController::crop }) to wrap or replace the shipped logic without forking; decorate the crop service in services.yaml for logging or validation; override templates via twig.paths if the widget needs restyling; and swap the Stimulus controller for your own by re-registering the identifier in assets/bootstrap.js if you need different JS behaviour. Prefer these overrides over patches — they survive a composer update that a hacked vendor/ file does not, and they're exactly what protects you if v1.0.5 turns out to be the last release for another two years.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle