Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Doctrine Crud Laravel Package

mukadi/doctrine-crud

mukadi/doctrine-crud is a lightweight CRUD package for Doctrine ORM entities, helping Laravel and PHP developers create, read, update, and delete records through a simple, configurable API that cuts boilerplate and keeps persistence code organized.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal setup

composer require mukadi/doctrine-crud
  1. Confirm your Doctrine wiring first. This package is a thin helper over EntityManagerInterface — it is not a Doctrine bridge. If you don't already have laravel-doctrine/orm (or an equivalent) configured with a working EM and at least one mapped entity, stop here. The package assumes a functioning Doctrine stack and adds nothing toward setting one up.
  2. Read composer.json, then src/. With 1 star, 0 dependents, and no meaningful docs, the source is the spec. Check the doctrine/orm and PHP constraints first — if you're on Doctrine 3.x, expect drift from a 2.x-era helper. Then find the primary helper/manager class and map its public methods before writing a line of integration code.
  3. Register it in the container. There's no Laravel service provider or facade, so you wire it yourself:
// app/Providers/AppServiceProvider.php
$this->app->bind(CrudManager::class, function ($app) {
    return new CrudManager($app->make(EntityManagerInterface::class));
});
  1. Wire it into one controller (see first use case) and exercise list/create/update/delete end to end before trusting it anywhere else.

Where to look first

  • The main helper class: does it call flush() internally? Does it accept a filters array, a paginator config, or nothing?
  • Interfaces / abstract classes — these are your only real extension points.
  • Any tests in the repo. They're more reliable than the README for exact call signatures and edge-case behavior.
  • The route registration surface — this is where middleware bypass risk lives.

First use case

Take an existing hand-rolled admin controller for one low-risk entity and replace the persist() / flush() / remove() boilerplate with helper calls. Small, reversible diff; immediately exposes the package's flush and error-handling semantics, which are the behavioral unknowns.

// before
public function store(Request $request)
{
    $post = new Post();
    $post->setTitle($request->input('title'));
    $em->persist($post);
    $em->flush();

    return redirect()->route('posts.index');
}

// after (illustrative — verify method names against src/)
public function store(Request $request)
{
    $this->crud->create(Post::class, $request->only(['title', 'body']));

    return redirect()->route('posts.index');
}

Note the difference in posture: the "after" version accepts an arbitrary array of attributes. You are now responsible for making sure $request->only(...) is an explicit allowlist — never $request->all().

Implementation Patterns

1. Adapter, not direct calls. Keep the package out of your controllers. Wrap it in your own gateway so the escape hatch stays open:

final class PostAdminService
{
    public function __construct(private CrudManager $crud) {}

    public function paginate(int $page, array $filters = []): iterable
    {
        return $this->crud->list(Post::class, $page, $filters);
    }

    public function create(array $data): Post
    {
        return $this->crud->create(Post::class, $this->validated($data));
    }
}

Controllers call PostAdminService. If the fork becomes unmaintainable, you rewrite one class, not every controller.

2. Validation and mass-assignment safety. The helper almost certainly writes whatever attributes you pass straight onto the entity. That means:

  • Validate first, in a FormRequest, and pass only $request->validated() into the service.
  • Never forward $request->all().
  • Watch for fields that should not be user-writable (id, createdAt, ownership/fk columns, state fields). If the helper has no field allowlist, add one in your adapter.
class StorePostRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'title' => ['required', 'string', 'max:255'],
            'body'  => ['required', 'string'],
        ];
    }
}

3. Route registration. If you use the package's route helper, verify exactly what it registers. Whatever it produces will not have your auth middleware, rate limiting, or CSRF protection by default:

Route::middleware(['auth', 'can:manage-posts'])
    ->prefix('admin/posts')
    ->group(function () {
        // if the helper returns a route registrar, attach your middleware here
        // otherwise hand-write resource routes and call the service from controllers
    });

For anything customer-facing, hand-write Route::resource() and call your service. The convenience of auto-generated routes is not worth an unprotected write surface.

4. Filtering and pagination. Use the package's list/filter path for index screens only. The moment a screen needs a join, a computed column, or a DQL aggregate, drop to DQL with a projection instead of coaxing the helper:

// Don't fight the helper for reporting queries.
$rows = $em->createQueryBuilder()
    ->select('p.id', 'p.title', 'COUNT(c.id) AS comments')
    ->from(Post::class, 'p')
    ->leftJoin('p.comments', 'c')
    ->groupBy('p.id')
    ->getQuery()
    ->getArrayResult();

5. Transactions around multi-step writes. If the helper flushes per call, a create-then-associate-relations sequence is two flushes with a window in between. Wrap the whole operation:

$em->beginTransaction();
try {
    $post = $service->create($validated);
    $service->attachTags($post, $request->validated('tags'));
    $em->flush();
    $em->commit();
} catch (\Throwable $e) {
    $em->rollback();
    throw $e;
}

6. Long-running workers / Octane. Reset the entity manager per request or per job loop. Stale identity maps produce data that looks correct in dev and drifts in production.

// in an Octane listener or job loop
$em->clear();

7. Characterization tests before you trust it. For every entity the helper touches, write tests for create, list-with-filters, update, delete, plus the failure paths: missing entity, validation error, unauthorized access. These tests are what let you accept a dependency bump.

8. Rollout behind a feature flag. Wire one entity, ship it behind a flag, watch it for a sprint. Don't convert your whole admin surface in one PR.

Gotchas and Tips

Flush semantics are the #1 unknown. Read the source to determine whether the helper flushes internally, whether it accepts a transaction, and whether it returns the managed entity. If it flushes, you can't batch; if it doesn't, you'll wonder why nothing saves. Pick one mental model and encode it in your adapter's docblocks.

Doctrine ≠ Eloquent — don't carry assumptions across. persist() does not write; flush() does. find() returns a managed instance or null, not a query builder. Lazy associations fire queries on access, so a list page that touches $post->getAuthor()->getName() will N+1 unless you fetch-join. The helper will not warn you.

Doctrine 3.x compatibility drift. Doctrine 3 removed AnnotationDriver defaults and changed several APIs. A helper written against 2.x may install and pass its constraint check yet behave incorrectly at runtime. Pin the constraint, and run the full test suite after any composer update that touches doctrine/*.

Mass-assignment is a real liability here. A generic "simple CRUD" helper that takes an array and writes it to an entity is a security problem unless your allowlist is explicit. Audit every call site. This is the single most likely source of a subtle privilege escalation in an admin panel.

Middleware bypass. Auto-registered routes are the classic failure mode: they exist, they work, and they skip auth. After wiring, hit the route unauthenticated in a test and assert you get a 302/403, not a 200.

Identity map and stale data. In PHP-FPM this is fine. In Octane, FrankenPHP, queue workers, or any long-running loop, the EM's identity map persists across requests. Clear it. Symptoms are old data on read and conflicting entities on write — both look like application bugs.

No service provider, facade, or config file means: no auto-discovery, no config/doctrine-crud.php to publish, no artisan vendor:publish. Everything is manual. Budget half a day for glue, not five minutes.

Single maintainer, no community. Treat the code as yours. Fork it under your org, pin a commit via a VCS or path repository, and keep a written record of the API surface you depend on. Do not consume dev-main.

Write the runbook while you still remember. With one integrator and no Stack Overflow corpus, knowledge concentration is the actual risk. Document: what the helper does, which entities use it, how filters map to DQL, and how to replace it. Two pages now saves a painful archaeology project later.

Know your fallback. For a handful of entities, a Laravel resource controller plus FormRequest is 1–2 days and zero supply-chain risk. Keep that estimate in your head. If the helper isn't saving you more than that, stop and delete it.

License and security disclosure path. Check both before shipping. With no maintainer to ship a CVE fix, your fork is the only mitigation, and you need the license to permit forking in the first place.

When to reach for a real tool instead. If the work is customer-facing, needs multi-tenancy, auditing, or an API surface, stop. Use laravel-doctrine/orm with hand-written resource controllers and DTOs, or an API Platform-style approach. This package covers commodity scaffolding for internal ops screens — that's the ceiling.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core
nodisrupt/laravel-agent
sierratecnologia/media-manager