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

Fluent Laravel Package

laravel-doctrine/fluent

Laravel Doctrine Fluent provides a fluent PHP mapping driver for Doctrine ORM entities, letting you configure entity mappings with a clean, expressive API instead of annotations, XML, or YAML files.

View on GitHub
Deep Wiki
Context7

Getting Started

laravel-doctrine/fluent is a metadata driver for Doctrine ORM. Instead of describing an entity with annotations or PHP attributes, you write a small, explicit PHP class that tells Doctrine how the entity maps to the database. Your entity stays a plain POPO; the mapping becomes testable, refactorable code.

Reach for it when you want mapping in one place, want to keep domain classes free of persistence concerns, or you're porting a legacy app where annotations are no longer an option (PHP 8 attributes are fine, but fluent scales better for large, convention-heavy schemas).

Minimal setup:

  1. composer require laravel-doctrine/orm laravel-doctrine/fluent — fluent is a companion to the ORM package, not a replacement for it.
  2. Publish the ORM config (php artisan vendor:publish --provider="LaravelDoctrine\ORM\DoctrineServiceProvider") and, in config/doctrine.php, set the manager's metadata driver (meta) to LaravelDoctrine\Fluent\FluentDriver. Confirm the exact key/shape against the README for your installed version — the driver name, not the config path, is what matters.
  3. Tell the driver where your map classes live (a namespace/path such as App\Mappings), or register them explicitly.
  4. Create one FluentMap subclass per entity:
namespace App\Mappings;

use App\Entities\User;
use LaravelDoctrine\Fluent\FluentMap;

class UserMap extends FluentMap
{
    public function map(): void
    {
        $this->className(User::class);
        $this->table('users');

        $this->id('id')->type('integer')->generatedValue();
        $this->string('name')->length(120);
        $this->string('email')->unique();
        $this->boolean('active')->default(true);
        $this->datetime('createdAt')->columnName('created_at');
    }
}

First use case: port exactly one existing entity (a User is ideal) and validate it with php artisan doctrine:schema:validate and php artisan doctrine:migrations:diff. If the generated SQL matches the existing table, the driver is wired correctly and you can migrate the rest of the schema entity by entity.

Implementation Patterns

One map per entity, mirroring the entity directory. User → UserMap, Order → OrderMap. Keeping the naming predictable is what lets the driver discover maps from a namespace/path instead of you maintaining a registry list.

Fields are chained builders. Every helper (string, integer, boolean, text, decimal, datetime, date, json, array) returns a builder you can decorate: ->nullable(), ->unique(), ->default(...), ->length(...), ->columnName(...), ->precision()/->scale(), ->options([...]) for platform-specific column flags. Read top to bottom, the map is the schema.

Relations use the same fluent style:

$this->manyToOne('group', Group::class)
    ->inversedBy('users')
    ->joinColumn('group_id')
    ->nullable();

$this->oneToMany('posts', Post::class)
    ->mappedBy('author')
    ->cascadePersist();

$this->manyToMany('roles', Role::class)
    ->inversedBy('users')
    ->joinTable('user_role');

Prefer fully-qualified class names for targets — short-name/alias resolution is a source of "entity not found" errors when maps move between namespaces.

Extract a shared base map instead of copy-pasting. Nearly every entity repeats an id, plus timestamps or soft-delete columns. A BaseMap extends FluentMap (or a trait per concern) with a mapDefaults() hook keeps maps short and makes a schema-wide change (e.g. switching to UUIDs) a single edit.

Other things the builder covers: embeddables ($this->embedded('address', Address::class)), single-table inheritance with discriminators, lifecycle callbacks/entity listeners, and table-level indexes and unique constraints. Treat anything you'd otherwise write in an attribute as expressible here.

Workflow: edit the map → clear the metadata cache → doctrine:migrations:diff → review the SQL → doctrine:migrations:migrate. Because maps are plain classes, you can also assert on them in a unit test (instantiate the map, run it, inspect the resulting metadata) without touching a database.

Gotchas and Tips

  • Silent no-op if the driver isn't switched. If config/doctrine.php still says attributes/annotations, Doctrine will happily map nothing from your map classes and you'll get "entity has no mapped fields" or a stale schema. This is the first thing to check when a new map "does nothing."
  • Metadata cache is the #1 debugging trap. Changes to a map are invisible until the metadata cache is cleared (php artisan doctrine:clear-metadata-cache). Local/staging with APP_DEBUG on usually caches to array and hides this; production will bite you.
  • Never mix mapping styles for the same entity. Attributes plus a fluent map means two drivers disagreeing about the same class; pick one per entity and stick with it for the whole app where possible.
  • Column naming is conventional, not magic. Fluent derives column names from property names via the naming strategy. If existing SQL uses snake_case and your properties are camelCase, set columnName() explicitly or configure the naming strategy — otherwise diffs will propose renaming every column.
  • 3.0.0 is a DBAL 4 (+ likely ORM 3) release — assume breakage beyond the headline. DBAL 4 removes Type::addType(), Type::getName(), and the old fetchAll()/execute() connection API. If you register custom types through custom_types in the Doctrine config or a service provider, audit each type: register by name (Type::getTypeRegistry()->register()) and drop getName(). Any code touching ClassMetadataInfo should move to ClassMetadata.
  • Upgrade the whole laravel-doctrine/* set in lockstep (orm, migrations, extensions, fluent). A split major between orm and fluent is the usual cause of driver/class-not-found errors after a composer update.
  • Verify the PHP claim. The 3.0.0 notes say "php 5 support" — almost certainly a typo for a modern PHP (8.5) version. Don't infer a PHP floor from the changelog; check composer.json on Packagist. Note also that the release note lists only additions even though this is a major bump, so read the full diff or composer why-not before upgrading.
  • Extension points: subclass FluentMap for shared behaviour, wrap repeated field groups in traits, and swap/extend the driver class itself if you need custom discovery. There is no need to fork the package for conventions — the base map is the intended seam.
  • Always diff before migrating. doctrine:migrations:diff is your fastest feedback loop on a map change and catches the ordering/uniqueness mistakes that are otherwise invisible until deployment.
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.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
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
adachsoft/api-version-bundle
cgv/localized-resource