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

Remote Template Bundle Laravel Package

leaseweb/remote-template-bundle

Symfony bundle that loads Twig templates from remote sources rather than the local filesystem, so views can be managed and shared across services without redeploying. Handy for centralized template storage, multi-tenant apps, and CMS-driven content.

View on GitHub
Deep Wiki
Context7

Getting Started

Day one reality check: this is a Symfony2 bundle. composer require leaseweb/remote-template-bundle in a Laravel app will fail on PHP constraint and symfony/* component pins, and even if it installed there is no bundle-registration path. You are reading it, not depending on it. The repository is archived with zero dependents, so treat it as a reference for the loader pattern and budget the afternoon you'd spend skimming it as the entire "installation" step.

Where to look first (30 minutes):

  1. The Twig loader class — it should implement Twig_LoaderInterface (getSource, getCacheKey, isFresh, exists). This is the whole pattern; everything else is DI plumbing.
  2. The DependencyInjection extension — see how the loader is constructed and where the remote origin/base URL is configured.
  3. The Bundle class — confirms the Symfony2-era API surface and that nothing here transfers.

Ignore the service definitions and config YAML beyond confirming what a "remote source" is modelled as (a base URL plus a path, most likely).

Your first Laravel-side use case: a single, low-risk, first-party marketing landing page rendered from a namespace that is populated by a sync command — not a fetch on the request path.

php artisan make:command TemplatesSync
# pulls https://themes.internal/acme/v42/landing.blade.php over HTTPS,
# verifies sha256 against a signed manifest, writes atomically to
# storage/app/templates/acme/v42/landing.blade.php

Register it once in a service provider:

public function boot(): void
{
    $version = Cache::rememberForever('templates.acme.version', fn () => file_get_contents(
        storage_path('app/templates/acme/LATEST')
    ));
    View::addNamespace('acme', storage_path("app/templates/acme/{$version}"));
}

Then return view('acme::landing');. That is the smallest shape of the capability, and it has no network call in the render path.

Config you actually need: allowlisted origins, a local cache/artifact directory, a signing key or pinned hash manifest, a TTL/version pointer, and a boolean kill switch defaulted to off.

Implementation Patterns

Pattern A — build-time sync (default choice). An Artisan command fetches, verifies, and materialises templates into a versioned directory, which is registered as a view namespace. Rendering is entirely local, identical across instances, diffable in review, and deployable as an artifact. This covers ~80% of "marketing needs to change a page without a release" because you can run the sync in CI on a schedule.

// inside the command
$resp = Http::withOptions(['allow_redirects' => false])
    ->timeout(10)->connectTimeout(3)
    ->withHeaders(['If-None-Match' => $etag])
    ->get($url);

abort_unless($resp->successful(), 500);
abort_unless(hash_equals($expectedSha, hash('sha256', $resp->body())), 500);

$tmp = $target.'.tmp.'.Str::random(8);
Storage::put($tmp, $resp->body());
Storage::move($tmp, $target);   // atomic rename

Pattern B — runtime fetch only on cache miss, fail closed. If a genuine runtime requirement survives scrutiny, subclass Illuminate\View\FileViewFinder (or wrap it) so that a miss in the namespace triggers a cached fetch plus a hard fallback to a bundled template. Keep it behind a config flag and never make it the global finder.

class RemoteAwareFinder extends FileViewFinder
{
    protected function findNamespacedView($namespace, $name): string
    {
        try { return parent::findNamespacedView($namespace, $name); }
        catch (InvalidArgumentException $e) {
            return $this->fetchAndCache($namespace, $name); // throws if unfetchable
        }
    }
}

Pattern C — per-tenant namespaces. View::addNamespace("tenant.{$id}", $path) with a version pointer resolved per tenant. This is the multi-brand story: each tenant gets an isolated directory and an isolated version, so a bad publish for tenant A cannot affect tenant B. Resolve the pointer from cache, not from a glob() in boot().

Integration tips that transfer from the original package:

  • Keep the remote loader alongside filesystem resolution, never replacing it. The bundle's stated design is loader-chain participation; in Laravel that means namespace registration, not a rebuilt finder.
  • Mirror Twig's isFresh/getCacheKey intuition: your version pointer should be part of the path so Blade's compiled-view cache naturally invalidates. Versioned paths beat mtime checks.
  • Log and meter every fallback (templates.fallback.count) — silent fallbacks are how a broken origin goes unnoticed for a week.
  • Fake the HTTP client and Storage::fake() in tests; assert the command fails closed on hash mismatch and on non-2xx.

Gotchas and Tips

Remote Blade is remote PHP. Twig's sandbox has no Blade equivalent. A remotely-supplied .blade.php is executable code with your full application context, and even @include('layouts.app') from a tenant template reaches local views. This is the reason the pattern is only safe for first-party, signed artifacts. If the source is tenant- or partner-supplied, stop and use a logic-less format (Mustache, or a JSON layout schema rendered by fixed components) instead.

Do not try to install the package. Composer will hard-fail on PHP and symfony/* conflicts, and there is no port. Any time spent attempting it is wasted; any code you do write shares nothing with upstream and inherits none of its (unverified) hardening.

Atomicity. A half-written template file gets compiled into a storage/framework/views entry, and the resulting parse error points at generated PHP, not at your sync command. Always write-temp-then-rename, and never write directly to the target on a retry.

Blade's compiled-view cache is path-keyed. If you overwrite the same path in place, the compiled view may not refresh. Version the directory (acme/v42/) and update a tiny LATEST pointer file, or run php artisan view:clear in the deploy. Orphaned compiled views accumulate; prune storage/framework/views periodically.

view:cache and config:cache in the deploy pipeline. php artisan view:cache will attempt to compile every registered namespace. If the sync hasn't run yet (or runs on a different instance), the build fails with an obscure missing-view error. Order the pipeline: sync → verify → view:cache, and exclude remote namespaces from precompilation if sync is per-host.

SSRF hardening if you do fetch at runtime. allow_redirects => false, scheme allowlist (https only), host allowlist checked after DNS resolution, verify => true, explicit connectTimeout, and a body-size limit. A decade-old bundle almost certainly has none of these; do not copy its HTTP layer.

Fail closed, but make the failure legible. On hash mismatch or unfetchable origin, throw with the origin, expected hash, and actual hash in the message. Never fall back to unverified content, and never fall back silently to a stale unverified copy.

Namespace collisions. View::addNamespace overwrites; if two packages both claim acme, the last one in boot() wins. Prefix with your app (app.acme) or centralise namespace registration in one provider.

Symfony2-specific, if you inherit a legacy app: the loader is bound as a service ID and consumed by the Twig loader chain; rebinding it globally is how you accidentally route all template resolution through the network. Scope it with a dedicated filesystem loader in front. Upstream is archived — assume no security response and pin the commit hash if you must run it.

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