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

Drupal8 Bundle Laravel Package

theodo/drupal8-bundle

Symfony bundle for using Drupal 8 as a headless CMS. It provides configurable services around Drupal’s REST API, helping Symfony apps authenticate and fetch Drupal entities and content without custom integration code. Useful for decoupled projects pairing Drupal content management with a Symfony fro...

View on GitHub
Deep Wiki
Context7

Getting Started

Start with a reality check, not a composer require. This is a Symfony full-stack bundle, not a Laravel package. It expects AppKernel::registerBundles() / config/bundles.php, a Symfony HTTP kernel, and a Drupal 8 bootstrap inside the same process. Laravel has no bundle-registration lifecycle, so composer require theodo/drupal8-bundle gives you a dependency you cannot wire into bootstrap/app.php without forking both the bundle and Drupal's kernel bootstrapping. Treat the repository as a reference implementation of a pattern, not a dependency.

Where to look first in the repo (in this order):

  • Drupal/DrupalKernel.php — how Drupal is booted inside another framework's kernel; this is the part you are not going to copy.
  • Drupal/DrupalService.php — the facade object injected into controllers. Note which operations it exposes: loading entities, rendering, cache clearing. Those operations are your API contract.
  • DependencyInjection/ — the config keys (drupal_root, etc.) and service wiring. This tells you which knobs actually matter.
  • Event subscribers / subrequest handling — how Symfony forwards a request into Drupal's render pipeline. This is the strangler-fig idea worth stealing.
  • Command/ — install/clear-cache helpers; useful as a checklist of side effects you must reproduce as artisan commands.

Your first use case in Laravel is not "embed Drupal," it's "consume Drupal." Stand up Drupal (10/11 if you are greenfield; Drupal 8 only if it already exists) as a separate container behind a private network, enable JSON:API, and build the equivalent of DrupalService as a plain Laravel service:

// config/services.php
'drupal' => [
    'base_uri' => env('DRUPAL_BASE_URI'),
    'token'    => env('DRUPAL_API_TOKEN'),
    'timeout'  => 3,
],
// app/Services/DrupalClient.php
final class DrupalClient
{
    public function __construct(private Http $http) {}

    public function node(string $type, string $uuid): array
    {
        return Cache::tags(["drupal:node:$uuid"])
            ->remember("drupal:node:$uuid", 600, fn () =>
                Http::baseUrl(config('services.drupal.base_uri'))
                    ->withToken(config('services.drupal.token'))
                    ->get("/jsonapi/node/{$type}/{$uuid}")
                    ->throw()
                    ->json('data')
            );
    }
}

If that returns an attribute set and renders in a Blade view, you have reproduced 90% of the day-to-day value the bundle provides — without the GPL linkage or the dual-kernel bootstrap.

Implementation Patterns

Strangler-fig route fallback (the bundle's real idea). The bundle forwards subrequests from Symfony controllers into Drupal's rendering pipeline so legacy pages stay live while you rebuild. In Laravel, do it at the HTTP boundary instead of in-process: a catch-all route that proxies unmatched paths to Drupal and returns its rendered response, so you cut routes over one at a time.

// routes/web.php — must be registered LAST
Route::fallback(function (Request $request) {
    if (! config('features.drupal_proxy')) {
        abort(404);
    }

    $response = Http::baseUrl(config('services.drupal.base_uri'))
        ->timeout(2)
        ->withHeaders(['X-Forwarded-Host' => $request->getHost()])
        ->get($request->getRequestUri());

    return response($response->body(), $response->status())
        ->header('Content-Type', $response->header('Content-Type'));
});

Then migrate path-by-path into real Laravel controllers and flip features.drupal_proxy off per section. This gives you the incremental cutover the bundle was built for, without two frameworks in one PHP process.

DrupalService → a thin, cached repository layer. Mirror the bundle's service surface but keep it HTTP-shaped. One class per content type (ArticleRepository), each depending on DrupalClient, each exposing intent methods (findBySlug, latest) rather than raw JSON:API calls. This is the same "expose Drupal content as a Symfony service" goal, achieved through Laravel's container.

Cache invalidation via Drupal cache tags. The bundle leans on Drupal's cache system; in a decoupled setup you must bridge it explicitly. Use Laravel's taggable cache store and have Drupal hit a webhook on save:

// app/Http/Controllers/DrupalInvalidationController.php
public function __invoke(Request $request)
{
    abort_unless($request->header('X-Drupal-Signature') === config('services.drupal.webhook_secret'), 403);

    Cache::tags($request->input('tags'))->flush(); // e.g. ['drupal:node:abc-123']
}

Editorial stays in Drupal; presentation stays in Blade. The bundle's Twig-in-Symfony story is the part to leave behind. Render Drupal's structured JSON:API output through Blade components, or, if you must reuse rendered markup, sanitize a Drupal-rendered body.processed field into Blade and never attempt to share a Twig/Blade template engine.

Session and identity: bridge by contract, not by cookie. The bundle's shared-runtime appeal is partly "one session." In Laravel, do not share a cookie domain with a legacy Drupal 8 instance. Instead expose a signed token exchange (Drupal SSO endpoint → Laravel guard) or push identity from Laravel into Drupal via a service account. If the requirement is genuinely "same session in one process," that is the one case where a Symfony-era brownfield is the only sane home — and it should be time-boxed.

Per-route cutover behind feature flags. Wrap migrated routes in a Route::middleware('feature:drupal_proxy_off') gate so you can flip individual sections back to the proxy in production without a deploy. The bundle achieves this by which controller you route to; Laravel achieves it with flags, which is far cheaper to operate.

Commands for parity. Reimplement the bundle's console helpers as artisan commands (drupal:clear-cache, drupal:health) so your existing scheduler and deploy pipeline treat Drupal as just another upstream, not a co-process.

Gotchas and Tips

  • GPL-2.0 is a code-level hazard, not just a legal footnote. Do not copy DrupalKernel.php or the DrupalService implementation into a proprietary Laravel app. Read it for the shape of the API and write your own HTTP client — the patterns are not copyrightable, the expressions are.
  • Drupal 8 is EOL (Nov 2021) and gets no security advisories. If you adopt the bundle for an existing D8 site, your team owns every CVE manually and forever. If you are greenfield, target Drupal 10/11 with JSON:API — the modern Drupal already runs Symfony 6/7 internally, which is precisely why this bundle's problem no longer needs solving.
  • Two caches that don't talk. The bundle's hidden operational cost — Drupal page cache plus Symfony cache — becomes your problem in a decoupled build too. Version cache keys or use tags, and always set a short TTL as a backstop. Never cache an "empty" result for long or a Drupal blip becomes a stale 404.
  • Never let a Drupal outage 500 your Laravel app. The bundle's failure mode is cascading: Drupal dies, every Symfony page that touched it dies. Set aggressive HTTP timeouts (2–3s), wrap calls in rescue()/retry(), and render a fallback Blade partial when the client fails.
  • 404 vs 403 semantics will drift. Drupal's 403 page is a rendered page with a 403 status; Laravel's is an exception. In the fallback proxy, pass status and Content-Type through verbatim, and make sure Drupal's error pages don't leak admin paths.
  • JSON:API write support is version-dependent. On Drupal 8, JSON:API was effectively read-only; write support matured later. Plan on "Laravel reads, editors write in Drupal" and don't build an admin UI in Laravel on the assumption you can POST back.
  • drupalSettings and asset pipelines do not survive the trip. If you proxy rendered markup, inline scripts referencing drupalSettings will break or leak. Strip <script> blocks you don't own, or restrict the proxy to content-only paths and rebuild presentation in Blade.
  • Pin and vendor if you ever actually install it. 33 stars, zero dependents, single-vendor origin, no maintenance signals: if you fork it into packages/, pin the commit, add a README naming an owner, and put a review date on the calendar. Unclaimed forks rot silently.
  • Test against fakes, not a live Drupal. Http::fake() your DrupalClient responses from captured JSON:API payloads; keep one nightly integration test against a real container. This is the practical upside of the HTTP boundary — the bundle's dual-kernel bootstrap is essentially untestable in unit tests.
  • The Symfony escape hatch is closed for a reason. If a stakeholder suggests "just install the bundle," the honest answer is that it requires Symfony 2.3–3.x + Drupal 8 + PHP 7.x, and Laravel 9/10+ is on Symfony 6 components with incompatible contracts. Push the conversation to the JSON:API contract instead of a forked kernel.
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
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect