gibilogic/crud-bundle
Gibilogic CRUD Bundle is a Symfony bundle that provides reusable CRUD scaffolding, helping developers quickly build create, read, update, and delete workflows with less boilerplate. It streamlines admin interfaces and backend management pages, making it easy to expose entity management in Symfony ap...
Reality check first: this is a Symfony 2 bundle, not a Laravel package. There is no ServiceProvider, no config/*.php to publish, and no Facade. On a modern Laravel app (PHP 8+, Laravel 10/11) composer require gibilogic/crud-bundle will almost certainly fail on platform/Symfony constraints. Treat the first 15 minutes as a feasibility spike, not an install.
Minimal steps
git clone https://github.com/gibilogic/crud-bundle /tmp/crud-bundle
tree -L 3 /tmp/crud-bundle
composer.json — Symfony/PHP version constraints tell you whether porting is even sane.DependencyInjection/ — the service definitions are the real API surface; every service id here is what you'd inject.Resources/config/ (routing, services YAML) — reveals the intended route names and template conventions.Tests/ if present — fastest way to learn intended usage without guessing.If you're porting into Laravel, map the pieces up front:
| Bundle concept | Laravel equivalent |
|---|---|
CRUD service over EntityManager |
Repository + Model / query builder |
| Symfony Form type | FormRequest + validation rules |
| Flash + redirect | ->with('status', ...) |
| CSRF token | @csrf / VerifyCsrfToken |
Route YAML with app_ prefix |
Route::resource(...) |
1. Extend the abstract controller (Symfony-native usage, for reference/legacy apps). The bundle is designed to be subclassed: you declare the entity class, the form type, and the templates, and inherit the index/create/edit/delete actions. Override only what differs.
class ProductController extends AbstractCrudController
{
protected function getEntityClass(): string { return Product::class; }
protected function getFormType(): string { return ProductType::class; }
protected function getTemplatePrefix(): string { return 'Product:'; }
}
2. Use the CRUD service directly instead of the controller. The more useful pattern day-to-day: inject the manager into your own controller, console command, or importer, and let it own persistence so validation/flash/redirect logic doesn't leak into every action.
public function store(Request $request, CrudManager $crud)
{
$result = $crud->create($request->validated(), Product::class);
return $result->isSuccess()
? redirect()->route('products.index')->with('status', 'Created.')
: back()->withErrors($result->errors())->withInput();
}
3. Index action as a configurable query. The pattern that pays off is a whitelist-driven index: an array of sortable columns, a set of allowed filters, and a page size — never $request->input('sort') passed straight to the query. Copy this shape even if you rewrite the internals:
$sortable = ['name', 'created_at', 'price'];
$sort = in_array($request->sort, $sortable, true) ? $request->sort : 'created_at';
$dir = $request->dir === 'asc' ? 'asc' : 'desc';
4. Delete with a soft-delete hook. Wire a beforeDelete/afterDelete extension point (or a model event in Laravel) rather than putting cascade logic in the controller. Deletion should be a service call that returns a result, so it's testable and reusable from an artisan command.
$crud->delete($product, function ($entity) {
$entity->archived_at = now();
});
5. Keep form handling out of the service. The bundle's form type is the boundary; the CRUD service should accept already-validated data. In Laravel terms, validate in the FormRequest, pass $request->validated() to the service. Mixing the two couples your persistence layer to the HTTP stack and makes CLI reuse painful.
6. Centralize response shaping. All the flash-and-redirect plumbing belongs in one base controller or a response helper. The moment each of the four actions does its own back()/redirect(), you've recreated the boilerplate the bundle was meant to remove.
7. Use it as a reference for query-object layering. The most portable idea in the repo is the split between a controller that only handles HTTP and a service that owns the entity lifecycle. That's worth copying into a first-party Laravel trait or base controller even if you never run a line of the original code.
Package discovery will not fire. There is no extra.laravel block; Laravel won't register anything automatically. Even if composer require succeeded, nothing would wire up.
Composer install will likely fail before you learn anything. The constraints target Symfony 2.x and PHP 5.x. If you want to inspect the code, clone it; if you want to test it, use a matching PHP image (php:5.6-cli) in a throwaway container.
Doctrine vs. Eloquent is not a shallow mismatch. The service layer almost certainly calls EntityManager::persist/flush. Swapping to Eloquent means rewriting the persistence boundary, not wrapping it. Budget for a real port, not an adapter.
No tests means no safety net for the port. Assume there's nothing to inherit. Write feature tests for each of the four operations first — they'll drive the port and catch regressions when you swap the persistence layer.
Route names are conventions, not magic. Symfony route YAML typically prefixes with app_ or the bundle name; the redirects in the abstract controller hardcode them. When you port, either preserve the names or update every redirect()->route() call — a silent 500 on success is a classic symptom.
CSRF is on by default in Symfony Forms but not automatically in Laravel's Route::resource. Remember @csrf in the delete form; forgetting it produces a 419 that looks like an auth bug.
Extension points are the safest thing to preserve. If the service exposes beforeSave/afterSave/beforeDelete hooks, keep that shape in your port — it's where application-specific behavior (audit logging, notifications, archival) will accumulate. Losing it forces every caller to reimplement those concerns.
Don't rely on the package's error-handling shape. Most Symfony 2–era CRUD helpers return either a boolean or throw. Pick one contract (Result object with isSuccess() and errors()) and standardize in your port — mixing the two is the fastest way to get silent failures.
Zero dependents means zero Stack Overflow. Any bug beyond a typo is a bug you own. Fork it explicitly (internal packages/ path, PSR-4 autoload) rather than depending on it, so upgrade and patch decisions stay under your control.
Attribution stays. MIT requires preserving the copyright header through forked classes. Keep the license file in the vendored directory and note the origin in the commit that introduces it.
How can I help you explore Laravel packages today?