comur/image-bundle
Symfony bundle for image upload and cropping in forms. Built on jQuery File Upload and JCrop with Bootstrap-friendly widgets (single image or gallery), library selection, ordering, and optional Imagick support for animated GIF cropping.
Installation:
composer require comur/image-bundle
Enable in config/bundles.php:
Comur\ImageBundle\ComurImageBundle::class => ['all' => true],
Routing:
Create config/routes/comur_image.yaml:
comur_image:
resource: "@ComurImageBundle/Resources/config/routing.yml"
prefix: /
Modal Template:
Include in your base layout (e.g., base.html.twig):
<body>
{% include "ComurImage/Form/croppable_image_modal.html.twig" %}
</body>
FOSJsRouting:
Add to <head>:
<script src="{{ asset('bundles/fosjsrouting/js/router.js') }}"></script>
<script src="{{ path('fos_js_routing_js', {'callback': 'fos.Router.setData'}) }}"></script>
Entity Setup:
// src/Entity/YourEntity.php
use Doctrine\ORM\Mapping as ORM;
class YourEntity {
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
protected $image;
public function getUploadDir() {
return 'uploads/your_entity';
}
public function getWebPath() {
return $this->image ? '/'.$this->getUploadDir().'/'.$this->image : null;
}
}
Form Type:
// src/Form/YourEntityType.php
use Comur\ImageBundle\Form\Type\CroppableImageType;
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('image', CroppableImageType::class, [
'uploadConfig' => [
'uploadDir' => 'uploads/your_entity',
'webDir' => 'uploads/your_entity',
'fileExt' => '*.jpg;*.png;*.gif',
'maxFileSize' => 5, // MB
],
'cropConfig' => [
'minWidth' => 500,
'minHeight' => 300,
'aspectRatio' => true,
'thumbs' => [
['maxWidth' => 200, 'maxHeight' => 200, 'useAsFieldImage' => true],
],
],
]);
}
Template:
{{ form_row(form.image) }}
Dynamic Upload Directories: Use entity methods for dynamic paths:
// Entity
public function getUploadDir() {
return 'uploads/'.$this->getSlug();
}
Reusing Configs:
Extract uploadConfig/cropConfig to a service or trait for consistency:
// src/Service/ImageConfig.php
class ImageConfig {
public static function getDefaultConfig() {
return [
'uploadConfig' => [...],
'cropConfig' => [...],
];
}
}
Gallery Integration:
For galleries, use CroppableGalleryType with an array field:
// Entity
/**
* @ORM\Column(type="array", nullable=true)
*/
protected $galleryImages;
// Form
$builder->add('gallery', CroppableGalleryType::class, [
'uploadConfig' => [
'uploadDir' => 'uploads/your_entity/gallery',
'webDir' => 'uploads/your_entity/gallery',
],
'galleryDir' => 'gallery',
]);
Symfony UX: Combine with Symfony UX Turbo for seamless updates:
{{ form_theme(form.image, ['ComurImage/Form/croppable_image_widget.html.twig']) }}
Validation: Add custom validation for file types/sizes:
use Symfony\Component\Validator\Constraints as Assert;
/**
* @Assert\File(
* maxSize="5M",
* mimeTypes={"image/jpeg", "image/png"},
* mimeTypesMessage="Please upload a valid image (JPEG/PNG)"
* )
*/
protected $image;
API Endpoints:
For API projects, override routes to use JsonResponse:
# config/routes/comur_image.yaml
comur_api_upload:
path: /api/upload
defaults: { _controller: 'comur_image.controller:uploadAction' }
methods: POST
Asset Management:
Use webpack-encore to bundle assets if not using Bower:
// webpack.config.js
Encore
.addEntry('comur-image', './vendor/comur/image-bundle/public/js/comur-image.js')
.splitEntry('comur-image')
.enableSingleRuntimeChunk();
Deprecated uploadUrl:
uploadUrl (absolute path) triggers security warnings.uploadDir (relative to public_dir config).# config/packages/comur_image.yaml
comur_image:
config:
public_dir: '%kernel.project_dir%/public'
Imagick Requirement:
// CropConfig
'disableGifCrop' => true,
Bootstrap Version Conflicts:
{% include "ComurImage/Form/croppable_image_modal.html.twig" with {
'bootstrap_version': 3
} %}
File Permissions:
public/uploads/ is writable:
chmod -R 775 public/uploads/
Route Overrides:
comur_api_upload, comur_api_crop, or comur_api_image_library.Check Upload Paths:
uploadDir and webDir match your filesystem structure.public function getUploadDir() {
error_log('Upload dir: ' . 'uploads/'.$this->getSlug());
return 'uploads/'.$this->getSlug();
}
JavaScript Errors:
fos.Router (ensure FOSJsRouting is loaded)./js/router.js (verify asset paths).Crop Configuration:
minWidth/minHeight values with actual image dimensions.aspectRatio temporarily to isolate issues:
'cropConfig' => ['aspectRatio' => false],
Gallery Sorting:
// Entity
public function __serialize() {
return [$this->galleryImages];
}
Custom Templates:
Override Twig templates in templates/ComurImage/:
# templates/ComurImage/Form/croppable_image_widget.html.twig
{% extends 'ComurImage/Form/croppable_image_widget.html.twig' %}
{% block image_preview %}
{{ parent() }} <!-- Customize preview logic -->
{% endblock %}
Event Listeners: Hook into upload/crop events via Symfony events:
// src/EventListener/ImageUploadListener.php
use Comur\ImageBundle\Event\ImageUploadEvent;
class ImageUploadListener {
public function onUpload(ImageUploadEvent $event) {
$file = $event->getFile();
// Add custom logic (e.g., rename files)
}
}
Register in services.yaml:
services:
App\EventListener\ImageUploadListener:
tags:
- { name: kernel.event_listener, event: comur.image.upload, method: onUpload }
Dynamic Configs: Use form events to modify configs dynamically:
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$form = $event->getForm();
$data = $event->getData();
$form
How can I help you explore Laravel packages today?