arkounay/quick-admin-generator-bundle
Installation:
composer require arkounay/quick-admin-generator-bundle
Add to config/bundles.php and run php bin/console qag:install.
First CRUD Controller:
// src/Controller/Admin/CategoryController.php
namespace App\Controller\Admin;
use App\Entity\Category;
use Arkounay\Bundle\QuickAdminGeneratorBundle\Controller\Crud;
class CategoryController extends Crud {
public function getEntity(): string { return Category::class; }
}
Access Admin Panel:
Visit /admin/category (or your configured prefix). The bundle auto-generates routes, forms, and listings.
Product) with Doctrine annotations.Crud in a controller for Product./admin/product to see auto-generated CRUD operations (list, create, edit, delete).Workflow:
Crud for each entity.getName(), getIcon(), or getBadgeNumber() for metadata.class ProductController extends Crud {
public function getName(): string { return 'Shop Product'; }
public function getIcon(): ?string { return 'package'; }
public function getBadgeNumber(): ?int { return $this->repository->count(['stock' => 0]); }
}
Patterns:
#[QAG\Field], #[QAG\HideInList], etc., on entity properties.
#[ORM\Column]
#[QAG\Field(label: 'Product Name', help: 'Display name for customers')]
private $name;
getListingFields() or getFormFields().
protected function getListingFields(): Fields {
return parent::getListingFields()
->remove('description')
->add('price', 'currency', ['currency' => 'EUR']);
}
Integration Tips:
isGranted() in permission methods:
public function isEditable($entity): bool {
return $this->isGranted('ROLE_EDITOR') && $entity->getUser()->getId() === $this->getUser()->getId();
}
Workflow:
getFilters():
protected function getFilters(): Filters {
return parent::getFilters()->add('createdAt')->add('category');
}
getListQueryBuilder():
protected function getListQueryBuilder(): QueryBuilder {
return parent::getListQueryBuilder()->andWhere('e.isActive = true');
}
Best Practices:
__construct():
public function __construct(private MailerInterface $mailer) {}
public function sendNotificationAction($entity) {
$this->mailer->send(...);
}
Extension Points:
blocks/list_row.html.twig).Product:
{% block product_list_row %}
<tr>
<td>{{ product.name }}</td>
<td class="text-success">{{ product.price|currency }}</td>
</tr>
{% endblock %}
Route Conflicts:
getRoute() returns unique values to avoid conflicts.php bin/console debug:router | grep qag.Field Configuration Overrides:
#[QAG\Field]) are merged with controller overrides. Explicitly remove() fields if needed:
$fields->remove('createdAt'); // Overrides attribute-based inclusion.
Security Bypass:
hasQuickListQueryBuilderSecurity() is false, users can access filtered-out entities via direct URLs. Always enable it for filtered queries:
protected function hasQuickListQueryBuilderSecurity(): bool { return true; }
Circular Dependencies:
Twig Caching:
php bin/console cache:clear
config/packages/dev/doctrine.yaml:
doctrine:
dbal:
logging: true
profiling: true
qag.events.entity_to_string or qag.events.field_config for debugging field rendering:
$event->getSubject(); // Entity instance
$event->getArgument('response'); // Current __toString value
Eager-Loading:
->leftJoin() in getListQueryBuilder() to avoid N+1 queries for relationships:
$qb->leftJoin('e.category', 'c');
Batch Actions:
isBatchDeletable():
public function isBatchDeletable(): bool { return $this->repository->count([]) < 1000; }
Custom Actions:
getActions():
protected function getActions(): array {
return array_merge(parent::getActions(), [
'publish' => [
'type' => 'button',
'label' => 'Publish',
'icon' => 'brand-github',
'action' => 'publishAction',
],
]);
}
Event Listeners:
qag.events.pre_create or qag.events.post_update for pre/post hooks:
public static function getSubscribedEvents(): array {
return [
'qag.events.pre_create' => 'onPreCreate',
];
}
Abstract Controllers:
SoftDeletableCrud):
abstract class SoftDeletableCrud extends Crud {
public function isDeletable($entity): bool {
return !$entity->isDeleted();
}
}
How can I help you explore Laravel packages today?