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 Xinclude Xml Driver Bundle Laravel Package

wernerdweight/doctrine-xinclude-xml-driver-bundle

A Symfony bundle that adds XInclude support to Doctrine's XML mapping driver, letting you split and reuse ORM mapping files via xi:include. Modularize large Doctrine XML mappings and compose them from shared fragments while keeping standard Doctrine metadata workflows.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a Symfony bundle that swaps Doctrine's XML metadata driver for one that resolves <xi:include href="..."/> elements, letting you split large *.orm.xml mapping files into reusable fragments. There is no Laravel package, no service provider, no auto-discovery — so as a Laravel developer your first decision is whether you need the bundle (you don't) or just the driver class it wraps (you probably do).

Step 1 — confirm the need. You only benefit if you use XML mappings (not attributes) and your mapping files have grown into merge-conflict territory: one 800-line Entities.orm.xml covering a whole module, or repeated <lifecycle-callbacks>/<entity-listeners> blocks across bounded contexts. If your mappings are already one-file-per-entity, you gain nothing but an extra dependency.

Step 2 — read the source, not the README. The repo is small and last released 2023-02-21. Open, in order:

  1. The driver class (the XmlDriver subclass that overrides loadMappingFile() and passes LIBXML_XINCLUDE to simplexml_load_file()).
  2. DependencyInjection/*Extension.php and any compiler pass — this shows how Symfony registers it, which is exactly the wiring you must reproduce manually.
  3. composer.json for the doctrine/orm and php constraints — this is the number that will decide whether you can use it at all.

Step 3 — decide install vs. copy. For a Laravel app, copying the ~40-line driver into app/Doctrine/ is usually cheaper than depending on a 1-star, single-release bundle whose Symfony wiring you'll strip anyway. If a bundle-less driver package from the same author exists, prefer that.

Step 4 — wire it. Bypass the bundle entirely; point the EntityManager's configuration at the driver:

// app/Providers/DoctrineServiceProvider.php
use App\Doctrine\XIncludeXmlDriver;
use Doctrine\ORM\Configuration;

$this->app->afterResolving(Configuration::class, function (Configuration $config) {
    $config->setMetadataDriverImpl(new XIncludeXmlDriver([
        realpath(base_path('doctrine/mappings')), // absolute, always
    ]));
});

With laravel-doctrine/orm, the EntityManager is built from config/doctrine.php ('meta' => 'xml'); you can't name a custom driver class there, so either swap the driver implementation after resolution as above, or bind a subclass of DoctrineManager that overrides driver construction.

First use case. Take one existing mapping file, split it into a thin index plus one fragment per entity, and verify with php artisan doctrine:mapping:info (or $em->getMetadataFactory()->getAllMetadata()) that the entity count is unchanged.

Implementation Patterns

Fragment-per-entity, index per module. The index file stays under source control as the "table of contents"; each fragment is owned by the team that owns the entity. Keep the fragment root as <entity> — not <doctrine-mapping> — so XInclude substitution inserts it as a direct child of the index's root.

<!-- doctrine/mappings/Billing.orm.xml -->
<doctrine-mapping
    xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
    xmlns:xi="http://www.w3.org/2001/XInclude">

    <xi:include href="Billing/Invoice.orm.xml"/>
    <xi:include href="Billing/Payment.orm.xml"/>
    <xi:include href="Billing/CreditNote.orm.xml"/>
</doctrine-mapping>
<!-- doctrine/mappings/Billing/Invoice.orm.xml -->
<entity name="App\Billing\Invoice" table="invoices">
    <id name="id" type="integer"><generator strategy="IDENTITY"/></id>
</entity>

One index per bounded context. Point the driver at a directory of small index files (Billing.orm.xml, Shipping.orm.xml, Identity.orm.xml) rather than at the fragments themselves. This gives you a clean seam for extracting a module into its own package later: move the directory, move the index, no mapping churn.

Shared fragments for cross-cutting boilerplate. The real payoff is reusing the blocks Doctrine repeats: <lifecycle-callbacks> stacks, <entity-listeners>, and the <indexes>/<unique-constraints> a legacy database forces onto every table. A _shared/audit-columns.orm.xml included by many indexes beats copy-paste, provided you accept it is textual inclusion (see Gotchas).

Layer test fixtures on top of production mappings. Because inclusion happens at metadata-parse time, you can build a test-only index that includes the same fragments plus fixture-only entities, and point the test EntityManager at it. This keeps test entities out of your production mapping directory without duplicating real mappings.

Gate it in CI. A fragment that fails to resolve still produces a valid-looking SimpleXMLElement; the failure surfaces later as "no metadata for class X" or a schema-validation exception. Add a smoke test that boots the EntityManager and calls getAllMetadata() — it should exercise every index file.

Gotchas and Tips

Relative href resolution is the #1 footgun. libxml resolves the include against the base URI of the including document, and that base URI is whatever string was handed to the parser. Doctrine passes paths from your config verbatim. If the configured path is relative, includes resolve against the process CWD — which differs between php artisan, php-fpm, and queue workers. Always realpath() your mapping paths before constructing the driver.

Fragments need their own namespace declaration. xmlns is not inherited across the include boundary. Each fragment must declare xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" or XmlDriver will silently find zero entity children. This is the most common "it loaded but found nothing" failure.

Root element shape matters. Including a fragment whose root is <doctrine-mapping> inside another <doctrine-mapping> gives you a nested <doctrine-mapping> child, which XmlDriver does not walk — those entities vanish. Include <entity>-rooted fragments only.

XSD validation can reject the include itself. Doctrine's XML driver validates against the ORM schema when configured to; <xi:include> is not part of that schema unless the xi namespace and processContents="lax" line up. If you hit validation errors, prefer fragment roots that validate, or disable schema validation in dev first to isolate whether the include or the content is at fault.

Failures are quiet by default. simplexml_load_file emits a warning and returns false/a partial tree on a missing or malformed include. Wrap the driver load in libxml_use_internal_errors(true) during debugging and dump libxml_get_errors() — otherwise you chase a misleading "class not found" through your entity code.

Metadata cache invalidation is per-index, not per-fragment. Editing Billing/Invoice.orm.xml may not invalidate the cached metadata for Billing.orm.xml in production or in a warmed CI cache. Expect to php artisan cache:clear (or drop the Doctrine metadata cache) after any fragment edit that "didn't take".

No override semantics. XInclude is textual. Two fragments declaring the same entity class will both be parsed; Doctrine's later-wins behaviour is not something to build on. If you need to override a mapping, use Doctrine's own mapping-inheritance, not include ordering.

Version risk is real. One star, no releases since February 2023, zero dependents — assume no one will fix a break for you. Pin doctrine/orm in composer.json and re-read the driver if you upgrade: ORM 3.x changed driver internals, and this package was published in the ORM 2.x era. Vendor the class into your own namespace so an abandoned dependency can't block a Laravel/PHP upgrade.

Extension point. Everything interesting lives in loadMappingFile(). Subclass it if you need non-href resolution (e.g. a class-name-based lookup so fragments are discovered by FQCN, not path), include-depth limits, or logging of every resolved include — a five-line override that makes include chains auditable in dev.

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.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin