Installation:
composer require appventus/aviary-bundle:dev-master
Add to AppKernel.php:
new AppVentus\AviaryBundle\AviaryBundle(),
Configure Upload Path:
In config.yml:
parameters:
aviary.uploadpath: "%kernel.root_dir%/../web/uploads"
Twig Configuration:
In config.yml:
twig:
form:
resources:
- 'AviaryBundle:Form:fields.html.twig'
First Use Case:
Create an entity with a OneToOne relation to Gallery (e.g., Product):
/**
* @ORM\OneToOne(targetEntity="Appventus\AviaryBundle\Entity\Gallery", cascade={"persist"})
*/
private $gallery;
Add GalleryType to your form type:
use Appventus\AviaryBundle\Form\GalleryType;
$builder->add('gallery', new GalleryType());
Render the Form:
{{ form_start(form, {'method': 'POST', 'attr': {'id': 'fileupload'}}) }}
{{ form_widget(form.gallery) }}
{{ form_end(form) }}
Initialize Aviary: Include the Aviary JS library and initialize it in your frontend:
var featherEditor = new Aviary.Feather({
apiKey: 'YOUR_AVIARY_API_KEY',
appendTo: '#fileupload',
onSave: function(imageID, newURL) { /* Handle save */ }
});
Form Submission:
The GalleryType handles multiple file uploads via BlueImp jQuery File Uploader. Files are stored in the configured aviary.uploadpath.
Entity Integration:
Attach the Gallery entity to your main entity (e.g., Product) via the OneToOne relation. The bundle persists the Gallery automatically when saving the parent entity.
Frontend Integration:
AsseticInjectorBundle to inject Aviary scripts/styles:
{% javascripts injector='aviary_scripts' %}
// Aviary JS library
{% endjavascripts %}
#fileupload).Image Editing: After upload, trigger Aviary’s editor via JavaScript:
featherEditor.launch({
image: uploadedImageURL,
onSave: function(imageID, newURL) {
// Update the entity with the edited image URL
$.post('/update-image', { id: imageID, url: newURL });
}
});
Backend Handling:
Gallery entity’s setter to handle edited image URLs:
public function setImages(array $images) {
foreach ($images as $image) {
if (isset($image['edited'])) {
$image['path'] = $image['edited']; // Update path to edited image
}
}
$this->images = $images;
}
Custom Validation:
Extend the GalleryType to add validation rules for file types/sizes:
$builder->add('images', CollectionType::class, [
'entry_type' => FileType::class,
'allow_add' => true,
'allow_delete' => true,
'constraints' => [
new File([
'maxSize' => '1024k',
'mimeTypes' => ['image/jpeg', 'image/png'],
]),
],
]);
Dynamic Upload Paths: Use a service to dynamically set the upload path based on the entity type:
services:
aviary.upload_path_resolver:
class: App\Service\AviaryUploadPathResolver
arguments: ['@request_stack']
Then inject it into the GalleryType to override the default path.
Batch Processing: For large uploads, use a queue system (e.g., Symfony Messenger) to process images asynchronously:
$gallery->setImages($request->files->get('gallery')['images']);
$this->messageBus->dispatch(new ProcessGalleryImagesMessage($gallery));
Twig Extensions: Create a custom Twig extension to generate Aviary initialization scripts dynamically:
$twig->addExtension(new class extends \Twig\Extension\AbstractExtension {
public function getFunctions() {
return [
new \Twig\TwigFunction('aviary_init_script', [$this, 'getAviaryScript']),
];
}
public function getAviaryScript() {
return 'var featherEditor = new Aviary.Feather({...});';
}
});
Usage in Twig:
{{ aviary_init_script() }}
API Key Management:
// Bad: Hardcoded
apiKey: 'YOUR_API_KEY'
// Good: Fetch from backend
fetch('/api/aviary-config')
.then(response => response.json())
.then(config => {
var featherEditor = new Aviary.Feather({ apiKey: config.apiKey });
});
File Permissions:
Ensure the aviary.uploadpath directory is writable by the web server:
chmod -R 775 %kernel.root_dir%/../web/uploads
CSRF Token Mismatch: The bundle’s file upload may fail if the CSRF token is not included. Ensure your form includes:
{{ form_start(form, {'attr': {'id': 'fileupload', 'data-csrf-token': app.request.csrfToken}}) }}
BlueImp Dependency: The bundle relies on BlueImp jQuery File Uploader, which may conflict with other upload libraries. Exclude its assets if needed:
// Disable BlueImp's default behavior if using another uploader
$('#fileupload').fileupload('option', 'autoUpload', false);
Entity Cascade Issues:
If the parent entity (e.g., Product) is deleted without cascading, the Gallery entity may become orphaned. Explicitly handle this in your repository:
public function deleteProduct(Product $product) {
$gallery = $product->getGallery();
if ($gallery) {
$this->getEntityManager()->remove($gallery);
}
$this->getEntityManager()->remove($product);
}
Check Upload Path:
Verify the aviary.uploadpath parameter is correctly set and the directory exists. Log it in a controller:
$this->container->getParameter('aviary.uploadpath');
Aviary Console Errors:
Inspect the browser’s console for Aviary-specific errors (e.g., apiKey missing or invalid). Example error:
Aviary error: Invalid API key
File Upload Failures:
move_uploaded_file failures).enctype="multipart/form-data" is set in the form:
{{ form_start(form, {'attr': {'enctype': 'multipart/form-data'}}) }}
JavaScript Initialization:
Wrap Aviary initialization in a DOMContentLoaded event to avoid race conditions:
document.addEventListener('DOMContentLoaded', function() {
var featherEditor = new Aviary.Feather({...});
});
Custom Gallery Entity:
Extend the Gallery entity to add metadata (e.g., altText, caption):
/**
* @ORM\Column(type="string", nullable=true)
*/
private $caption;
Update the GalleryType to include these fields.
Plugin-Specific Config:
Aviary supports plugins (e.g., filters, effects). Pass custom config via the featherEditor options:
var featherEditor = new Aviary.Feather({
tools: ['crop', 'enhance', 'draw'],
// Plugin-specific config
crop: {
preset: 'square',
},
});
Event Listeners:
Listen for Gallery events to trigger actions (e.g., image optimization):
$gallery->addEventListener('postPersist', function(Gallery $gallery) {
$this->optimize
How can I help you explore Laravel packages today?