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

Eloquent Bundle Laravel Package

wouterj/eloquent-bundle

Integrates Laravel's Eloquent ORM into Symfony applications. Register Eloquent models as services and use the familiar ActiveRecord-style API—queries, relationships, and migrations—in your Symfony project, alongside or instead of Doctrine, with full dependency injection support.

Deep Wiki
Context7

Getting Started

This bundle gives you illuminate/database — Eloquent models, the query builder, connections, and the model event system — wired into Symfony's container. It does not bring Artisan, migrations, factories, seeders, queues, or the HTTP kernel. Think "Eloquent as a library," not "Laravel inside Symfony."

Minimal setup

composer require wouterj/eloquent-bundle

Register the bundle (Flex usually does it for you in config/bundles.php):

WouterJ\EloquentBundle\WouterJEloquentBundle::class => ['all' => true],

Configure at least one connection in config/packages/eloquent.yaml. The connection array maps directly onto Laravel's database.connections.* keys, so anything you'd put in config/database.php mostly transfers:

eloquent:
    connections:
        default:
            driver: mysql
            host: '%env(DB_HOST)%'
            port: '%env(int:DB_PORT)%'
            database: '%env(DB_DATABASE)%'
            username: '%env(DB_USERNAME)%'
            password: '%env(DB_PASSWORD)%'
            charset: utf8mb4
            collation: utf8mb4_unicode_ci

Name one connection default; unqualified Model::query() calls resolve through it.

Where to look first

  1. bin/console debug:container eloquent (and debug:config eloquent) to see which services and options the bundle actually registered — this is the fastest way to find the manager/connection service names in your installed version.
  2. The bundle's DependencyInjection/Configuration.php for the authoritative config tree.
  3. Whatever mechanism sets the Eloquent connection resolver — that global static is the seam everything else hangs off, and knowing where it's set explains most "why is my model hitting the wrong DB" bugs.

First use case: port one model

Copy a model straight out of a Laravel app. Eloquent doesn't care about framework; it cares about the connection resolver being set, which the bundle handles at boot.

namespace App\Model;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    protected $table = 'posts';
    protected $guarded = [];
    protected $casts = ['published_at' => 'datetime'];
}
#[Route('/posts', name: 'post_index')]
public function index(): Response
{
    return $this->render('post/index.html.twig', [
        'posts' => Post::query()->with('author')->latest()->get(),
    ]);
}

That's the whole loop: config → model class → query → render. Get that working against an existing database before touching anything else.

Implementation Patterns

Wrap queries in services, not controllers. Eloquent's static API is convenient and completely untestable in Symfony's DI-centric world. Put the query behind a small service so you can swap it, mock it, or refactor to Doctrine later:

final class PostRepository
{
    public function latest(int $limit = 20): Collection
    {
        return Post::query()->with('author')->latest()->limit($limit)->get();
    }
}

Inject PostRepository anywhere. Avoid Post:: static calls from Twig, controllers, and form types.

Let the container build models when they need dependencies. The bundle's model resolver means models can be constructed through Symfony's container, so constructor injection into a model works if you register it as a service. Use this sparingly — for things like a TenantScopedModel base class that needs a Security or tenant resolver. For plain data models, don't; container-built models are hard to reason about and serialize badly.

Multiple connections for legacy migration. This is the bundle's strongest argument in a real codebase. Point one connection at the legacy schema, one at whatever you're building:

class LegacyOrder extends Model
{
    protected $connection = 'legacy';
    protected $table = 'orders_2019';
}

You can run Eloquent alongside Doctrine on disjoint tables indefinitely, migrating table by table. Two ORMs over the same rows is where it goes wrong (see Gotchas).

Model events and observers. Eloquent's creating/saving/deleted hooks fire normally. Register observers from a service or a small compiler-pass-driven provider rather than a boot() method — Symfony has no AppServiceProvider:

// in a service that runs at boot, or a dedicated listener on kernel.request
Post::observe(PostObserver::class);

If you need Symfony's dispatcher to see Eloquent events, bridge them explicitly with a listener that re-dispatches; there's no automatic wiring in most versions.

Transactions. DB::transaction(fn () => ...) works. For request-scoped transactions, prefer an explicit service method — Symfony's kernel.terminate runs after the response is sent, so committing there is a good way to lose writes silently.

Testing. There are no factories or RefreshDatabase. Use a dedicated test database plus manually seeded fixtures, or truncate in setUp(). Because the connection resolver is global static state, boot the kernel (or reset the resolver) between test cases if you test against multiple connections:

protected function setUp(): void
{
    parent::setUp();
    Post::query()->truncate();
}

Schema management. The bundle ships no migrations. Use Doctrine Migrations on a separate connection pointing at the same database, raw SQL files applied by your deploy pipeline, or Phinx. Whatever you pick, treat it as a first-class part of the setup — this is the biggest gap versus a Laravel project.

Gotchas and Tips

No Artisan means no make:model, db:seed, or migrate. Write model classes by hand; they're 15 lines. Don't waste time looking for generators — some versions expose a couple of commands, but the surface is far smaller than you expect.

Global static connection resolver. Model::$resolver is process-global. Long-running workers, multiple kernels in one test suite, and symfony/messenger consumers can all carry stale resolver state. If a model suddenly writes to the wrong database after a test or a worker restart, this is almost always why.

No ModelNotFoundException → 404. Eloquent throws Illuminate\Database\Eloquent\ModelNotFoundException from findOrFail; Symfony has no exception handler that understands it. Add a kernel.exception listener mapping it to a 404 — you'll want it on day one.

Pagination needs manual wiring. ->paginate() requires Laravel's current-page and paginator resolvers, which the bundle doesn't necessarily set. Cheapest fix: skip paginate() and use ->forPage($page, $perPage) with a ->count() for the total, or call Paginator::currentPageResolver() yourself at boot.

Symfony Serializer doesn't understand Eloquent. $model->toArray() respects $hidden, $visible, $casts, and appends — use it and wrap in new JsonResponse($model->toArray()). If you must use the Serializer, write a normalizer that delegates to toArray(). Otherwise you'll serialize raw attributes, leak password hashes, and expose lazy-loaded relations mid-serialization.

N+1 is worse here than in Laravel because Twig will happily walk {% for comment in post.comments %} and issue a query per row with no debug bar flashing at you. Eager load with with() at the repository boundary, and consider Model::preventLazyLoading() in dev.

Two ORMs, one table = identity map hell. Doctrine keeps a unit of work; Eloquent doesn't. If Doctrine hydrates an entity and Eloquent then mutates the same row, Doctrine will overwrite it on flush with stale data. Keep them on disjoint tables, or commit to one per aggregate.

Profiler blind spot. Queries won't show in the Doctrine panel. Use DB::listen(fn ($q) => $this->logger->debug($q->sql, $q->bindings)) in a dev-only service to get query visibility.

Naming things. driver values and connection keys differ subtly from Laravel's config in some versions (mysql vs pdo_mysql). When the connection silently fails or falls back, run debug:config eloquent and compare against the bundle's Configuration.php — don't assume your config/database.php copy-paste is valid.

Extension points worth knowing: custom casts (Illuminate\Contracts\Database\Eloquent\CastsAttributes), query builder macros registered in a boot service, Model::setConnectionResolver() if you need dynamic multi-tenancy, and Eloquent's Builder for shared scopes. All of these are plain PHP and work fine under Symfony's container — nothing bundle-specific required.

Version pinning. This is community-maintained with a relatively small user base (166 stars). Pin a minor version in composer.json, read the changelog before upgrading, and keep the integration surface small — a repository layer means a future swap to Doctrine, or to a plain Capsule setup, costs a week instead of a quarter.

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.
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle