monstrum/symfony-jcrop-bundle
Symfony bundle that integrates the Jcrop jQuery plugin, adding the required assets and helpers so you can easily enable client-side image cropping in your Symfony applications.
The bundle is a thin Symfony 2 wrapper around the jCrop jQuery plugin — it does not do image manipulation for you. Expect it to ship jCrop's JS/CSS as bundle assets plus (depending on the tag you pull) a Twig helper or a small config block for default jCrop options. Everything else — upload, persistence, actual cropping — is your code.
Minimal steps:
Install. The Packagist name and the repo name differ (symfony-jcrop-bundle vs symfony-jcrop), so confirm the exact package before requiring:
composer require monstrum/symfony-jcrop-bundle
Check composer.json in the repo first — on a Symfony 2-era bundle the constraint is typically symfony/symfony: ~2.3 and PHP >=5.3.3. If you are on Symfony 3+, stop here; see Gotchas.
Register the bundle in app/AppKernel.php:
$bundles = array(
// ...
new Monstrum\JcropBundle\MonstrumJcropBundle(),
);
Confirm the class name by opening the bundle file in the repo — Monstrum's naming convention is MonstrumJcropBundle, but verify rather than guess.
Publish assets so jCrop lands under web/bundles/:
php app/console assets:install web --symlink
Include jCrop plus jQuery in your layout — order matters (jQuery first, then jCrop, then your init script):
<script src="{{ asset('bundles/monstrumjcrop/js/jquery.Jcrop.min.js') }}"></script>
<link rel="stylesheet" href="{{ asset('bundles/monstrumjcrop/css/jquery.Jcrop.min.css') }}">
First use case: avatar upload with a crop step. Two routes — one that renders the just-uploaded image with a jCrop canvas, one that accepts the four coordinates (x, y, w, h) and writes the cropped file. If your first attempt is anything more ambitious than that, you are over-scoping a package this thin.
Where to look first: Resources/views/ for the shipped templates/macros and Resources/public/js/ for the actual jCrop version vendored in. That version number determines your jQuery compatibility more than anything the bundle documents.
jCrop is purely client-side; the coordinates only exist in the browser until you POST them. The canonical pattern is a form with four hidden inputs populated by the onSelect/onChange callback:
{{ form_start(form) }}
<img id="crop-target" src="{{ asset(image.webPath) }}" alt="">
{{ form_widget(form.x) }}{{ form_widget(form.y) }}
{{ form_widget(form.w) }}{{ form_widget(form.h) }}
<button type="submit">Crop</button>
{{ form_end(form) }}
<script>
$(function () {
$('#crop-target').Jcrop({
aspectRatio: 1,
setSelect: [0, 0, 200, 200],
onSelect: function (c) {
$('input[name$="[x]"]').val(Math.round(c.x));
$('input[name$="[y]"]').val(Math.round(c.y));
$('input[name$="[w]"]').val(Math.round(c.w));
$('input[name$="[h]"]').val(Math.round(c.h));
}
});
});
</script>
The bundle will not resize for you. Pair it with Imagine (or raw GD) in a small service:
$img = $imagine->open($originalPath);
$img->crop(new Point($x, $y), new Box($w, $h))
->resize(new Box(200, 200))
->save($targetPath, array('quality' => 85));
Keep this in a dedicated service (app.cropper) rather than the controller — you will reuse it for every image type.
The coordinates jCrop hands you are in displayed pixels, not original pixels. If you render a 4000px upload into a 600px container, you must scale:
$scale = $originalWidth / $displayWidth;
$x = (int) round($x * $scale);
// ...same for y, w, h
Store displayWidth as a hidden field at render time, or derive it from a fixed thumbnail width — but never assume displayed == original.
Wrap the four coordinates in a small CropType with HiddenType/hidden fields and NotBlank + Range constraints. This gets you CSRF, validation and handleRequest() for free, and keeps the controller down to five lines.
If the bundle exposes a monstrum_jcrop: config tree, use it for the default options (aspect ratio, min/max size) and read them back into the Twig helper. If it does not, don't fight it — keep your options in a Twig variable or a small JS file included per-page.
services.yaml. Use it only to keep an existing legacy app running; for anything new, drop jCrop's files into web/ yourself or use a maintained alternative (Cropper.js, FilePond, or a modern symfony/ux component).~5.3 or ~5.6 and pull in old Assetic. Running composer update on PHP 7.4+ frequently fails on transitive constraints. Prefer composer require --ignore-platform-reqs only as a last resort, and pin everything.assets:install. If the bundle's README shows {% stylesheets %} tags, that assumes Assetic is enabled. On a project where Assetic was removed, use asset('bundles/monstrumjcrop/...') instead and run assets:install after every composer update — otherwise you get silent 404s and an image that renders but never becomes selectable.Imagick::setImageOrientation / autoRotate() before rendering the preview, so display and original agree.onSelect doesn't fire until the user drags. Default setSelect to something sensible, and validate w > 0 && h > 0 server-side or you will write 1x1 images. Also guard against coordinates exceeding the image bounds after scaling (rounding pushes them over by a pixel).x + w <= width and y + h <= height against the server's knowledge of the image, or a crafted POST crops out of bounds and GD/Imagick throws or pads with black.kernel.terminate listener or a scheduled command.web/bundles/monstrumjcrop/css/ exists and returns 200 before touching the JS.Resources/views/ via {% extends %} or copy the template into app/Resources/MonstrumJcropBundle/views/. That override path is the only supported customization route — treat everything else as a fork.How can I help you explore Laravel packages today?