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

Resource Bundle Laravel Package

atoolo/resource-bundle

The Atoolo resource bundle aggregates resources from IES, Sitepark's content management system, into data this PHP library can handle. It provides the resource layer for Atoolo applications, with documentation available for developers working in the Atoolo context.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and register

composer require atoolo/resource-bundle

This is a Symfony bundle (sitepark/atoolo-resource-bundle, MIT, PHP 8.1–8.5, Symfony 6/7). Register it in config/bundles.php if it isn't auto-registered by your app's recipe/flex setup:

Atoolo\Resource\Bundle\AtooloResourceBundle::class => ['all' => true],

Configure the resource root first — this is the one thing that will bite you immediately.

The bundle reads pre-produced resources from disk, and the root is resolved from an environment variable (RESOURCE_ROOT). Per the 1.0.0 changelog there was explicit work on "better handling of missing RESOURCE_ROOT", so expect a clear failure rather than a silent one — but set it anyway:

RESOURCE_ROOT=/var/resources

There is also an atoolo_resource.resource_host parameter (added in 1.0.0) and a configDir on ResourceChannel for channel-level configuration.

Where to look first

The README is one paragraph and points outward. Read in this order:

  1. https://sitepark.github.io/atoolo-docs/develop/bundles/resource/ — the actual documentation, not the repo.
  2. The Resource class constructor and DataBag — that's the shape of everything you'll consume.
  3. Service/ definitions — the bundle switched to autowiring in 1.5.0, so service IDs are mostly class names.

First use case: render a page from an aggregated resource

The canonical day-one scenario is: a controller receives an ID or path, you load the resource, and render it.

use Atoolo\Resource\ResourceLoader;

final class PageController
{
    public function __construct(private readonly ResourceLoader $loader) {}

    public function __invoke(string $id): Response
    {
        $resource = $this->loader->load($id);
        return $this->render('page.html.twig', ['resource' => $resource]);
    }
}

Inject the interface, not CachedResourceLoader — the bundle registers the cache loader as a service (1.0.0: "add cache-loader as service"), and you want to be able to swap it in tests.

Implementation Patterns

Always address resources through ResourceLocation

ResourceLocation carries the language/channel coordinates of a resource. 1.4.0 added ResourceLocation::ofPath(), which is the ergonomic constructor for "I have a URL path, give me a location":

use Atoolo\Resource\ResourceLocation;

$location = ResourceLocation::ofPath('/services/consulting');
$resource = $this->loader->load($location);

Prefer this over string IDs in application code. It keeps language/channel handling in one place instead of scattered across call sites.

Use CachedResourceLoader for anything request-scoped

The loader is disk-backed and the resource files don't change at runtime. Wrap read paths in the cached loader (injected as a service since 1.0.0) and treat the plain loader as the "cold/passthrough" variant. There is also a documented ability to delete loader caches — wire that into your deploy/cache-warm step, not into request handling:

# after RSYNC of new resource data
bin/console cache:pool:clear <resource_pool>   # confirm the pool/service name in your app

The 1.7.0 change ("lazy-load service atoolo_resource.resource_channel") exists so the channel object — which pulls in configDir and attributes — doesn't get constructed on every request. Keep it lazy by injecting ServiceLocator/Closure where you only occasionally need channel metadata, rather than injecting ResourceChannel directly into hot controllers.

Hierarchies: find, then walk

Resources carry parent references. The bundle gives you two separate tools — use them for what they're named:

  • ResourceHierarchyFinder — "where am I in the tree?" (ancestor chain, breadcrumbs, active navigation state).
  • ResourceHierarchyWalker — "give me the subtree" (sitemap generation, recursive menu rendering, export jobs).

Typical breadcrumb:

$location = ResourceLocation::ofPath($request->getPathInfo());
$ancestors = $this->finder->findAncestors($location);

return $this->render('breadcrumbs.html.twig', ['items' => $ancestors]);

Typical menu build (do it in a warmer, not per-request):

foreach ($this->walker->walk($rootLocation) as $node) {
    $menu->add($node);
}

Guard both with a depth limit in your own code if the tree is editor-managed — the 1.1.0 fix for "circular parent reference detection" tells you this has happened in production before.

Read data through DataBag, and check has() first

Resource payloads are aggregated and heterogeneous across content types. DataBag is the accessor, and DataBag::has(string) exists specifically so you don't have to isset() on every optional field:

$teaser = $resource->getData()->has('teaser')
    ? $resource->getData()->get('teaser')
    : $resource->getData()->get('defaultTeaser');

Also lean on the 1.0.0 "More robust loading against various invalid resources" behaviour: the loader is designed to survive malformed input. That means a missing field is a normal branch, not an exception — write your templates defensively rather than assuming get() returns non-null.

Language fallback is automatic — but be deliberate about it

1.0.0 added "Load resource with default language if the translation is missing". This is convenient and dangerous: you can ship a site where half the pages are silently in the wrong language. If language correctness matters (legal pages, hreflang), compare the loaded resource's language against the requested one and log or flag the mismatch.

p-parameter service (1.5.0)

The 1.5.0 "p-parameter service" is the bundle's bridge for the p query parameter convention used by IES front ends. If you're integrating with existing Sitepark-generated links, resolve the request's p parameter through this service rather than parsing $_GET yourself — this is where channel/language defaults get applied consistently.

Tenants and channels

ResourceChannel (with attributes, added 1.6.0), ResourceLanguage, and ResourceTenant (with host, added 1.6.0) model a multi-site deployment. In a multi-tenant app, resolve tenant from the request host via ResourceTenant, then build the ResourceLocation from tenant + language + path. Keep that resolution in one kernel-listener-ish service; the bundle deliberately doesn't know about your hostname strategy.

Gotchas and Tips

RESOURCE_ROOT is the whole ballgame. If resources "aren't there", check the env var before anything else — a relative path, a container path that wasn't mounted, or a root that resolves differently between CLI (cache warmers) and FPM is the single most common failure. Print realpath() of it in a debug command once and move on.

Autowiring changed the DI surface in 1.5.0. If you're upgrading from ≤1.4, any config that referenced explicit service IDs (notably the factory namespace, refactored in 1.4.0) may break. Grep your services.yaml for atoolo_resource. and re-check against the bundle's service definitions after the upgrade rather than assuming it worked because the container compiled.

The channel service is lazy for a reason. Don't defeat it. Injecting ResourceChannel into a controller forces configDir resolution and attribute parsing on every request. Inject a closure/ServiceLocator if you only need it on some paths.

Cache invalidation is a deploy concern, not a runtime concern. Resources are pre-produced and land on disk out-of-band. Sequence your deploys: sync resources → clear loader caches → warm menus/navigation → deploy code. If you clear caches from a web request you'll get thundering-herd rebuilds on the first traffic after a content push.

Circular parent references were a real bug (fixed in 1.1.0). The fix exists, but it was a detection fix — meaning it guards rather than repairs. If editors can set parents, add an admin-side cycle check too; you don't want the walker to be your first line of defence.

Silent default-language fallback is the sneakiest bug class here. A page that should be /de/kontakt but resolves to the /en/contact content will render fine and look wrong only to a native speaker. Add a functional test per language that asserts the loaded resource's language matches the requested one.

0 stars, 0 dependents, no in-repo docs. Treat this as first-party-but-niche: you will not find Stack Overflow answers or third-party examples. Budget time for reading source directly. The upside is real engineering discipline — PHPStan level 9, php-cs-fixer enforced, E2E suite in a sibling repo (atoolo-e2e-test). That's a better signal than the star count.

Version pinning. With 1.x and frequent feature/minor releases (1.0 → 1.9 across the visible log) and no dependents to shake out regressions, pin to a minor (^1.9) and read each release note. The changelogs are genuinely informative — 1.0.0's list is effectively the feature map you'd otherwise have to reverse-engineer.

Extension points if you outgrow the defaults: the ResourceLoader interface (decorate to add metrics/logging), ResourceBaseLocator / LayoutResourceBaseLocator (custom path→resource mapping; the latter was generalised in 1.0.0), and the DataBag (custom accessors for your content types). Prefer a decorator over a fork — you want to take upstream releases given how actively this moves.

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.
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
adachsoft/php-code-style