dflydev/placeholder-resolver
A lightweight PHP library for resolving placeholders such as %key% in strings using array or custom data sources. It supports recursive substitution, nested lookups, and circular-reference detection, making it useful for configuration and templating.
Architecture fit. dflydev/placeholder-resolver is a small, framework-agnostic Composer library whose entire job is: given a placeholder token (e.g. ${foo.bar}) and a pluggable DataSourceInterface, return the resolved value — recursively, so a value may itself contain placeholders. It has no opinion about Laravel, no service provider, no facade, no config integration. That is both its strength (nothing to fight) and its weakness (everything Laravel-specific is our code). The natural Laravel use cases are narrow and infrastructural: resolving cross-referencing config values, layering environment/tenant overrides onto a base config tree, and parameter substitution in YAML/JSON config files loaded by a custom loader. It is not a templating engine for user-facing output — Blade already owns that space and this library would be a regression there.
Integration feasibility. High in the mechanical sense: composer require, one container binding for the data source, one singleton for the resolver. Expected effort is a one-to-two-day spike plus a thin (~100 LOC) Laravel wrapper. The real question is whether we need the library at all — a placeholder resolver with cycle protection is roughly 50 lines of our own code, and we would own the semantics outright. With 141 stars, zero visible dependents, and an opportunity score of ~31, this reads as a commodity capability rather than an ecosystem bet. I would fund a spike, not a commitment.
Technical risk. Three items dominate:
php: >=5.3 we are fine, but if it caps below our runtime we are pinned on a library nobody is patching.null. That behaviour becomes our contract whether we like it or not.DataSource is wired to env()/config, any code path that renders a resolved value into logs, exception traces, or an API response turns this into a credential-leak vector.Key questions for the spike.
${ in content?symfony/dependency-injection ParameterBag, Twig, or Laravel's own config repository extension) that already covers 90% of our need?Stack fit. Clean fit only if we keep the library at the configuration layer. Bind a DataSourceInterface implementation backed by config() (and optionally a curated env allow-list), register the resolver as a singleton, and expose one small service — PlaceholderService::resolve(string $value): string — that the rest of the app depends on. Do not let controller or view code import Dflydev\ namespaces directly; the wrapper is our seam if we later replace the library with in-house code.
Migration path. Start by inventorying existing ad-hoc interpolation — str_replace chains, preg_replace_callback in config loaders, custom %param% handling in YAML bootstrappers. Each of those is a candidate to consolidate behind the new service. Migrate config-file resolution first (low blast radius, easy to diff), then any runtime string interpolation, and only then consider tenant/feature-flag overrides. Leave Blade templates untouched.
Compatibility. Verify against our actual PHP matrix and Laravel version before writing code — this is a gate, not a task. Add a contract test suite that exercises the resolver through our wrapper only, so an upstream swap is a single-file change. If the upstream constraint blocks our PHP 8.2/8.3 runtime, fork-and-vendor is acceptable for a library this small; patching 200 lines is cheaper than abandoning a config standard mid-migration.
Sequencing.
Maintenance. Upstream maintenance is not something we can rely on; assume we own this. Budget for a fork-and-vendor contingency and pin the version exactly — no caret ranges on a library with this activity profile. The wrapper keeps the long-term maintenance surface at roughly one file plus a test suite.
Support. No vendor support, community issues only. Internal ownership must be assigned to the platform team explicitly, with the wrapper documented as a platform primitive. Without a named owner this becomes orphaned infrastructure that a future team is afraid to remove.
Scaling. Resolution cost is trivial at typical config volumes (tens to low hundreds of values) and can be cached at boot. The scaling concern is not throughput, it is cardinality: if placeholders are resolved per-request against a large data source, or if resolution is ever applied to user-supplied strings, cost and risk both change shape. Constrain it to boot-time and config-time resolution and the scaling question disappears.
Failure modes to instrument.
${foo} leaking into a rendered value or, worse, into a query. Fail loud at boot./config debug endpoints. Redact resolved values in the resolver's __toString/logging path.Ramp-up. Low. The API surface is small enough that any mid-level Laravel developer is productive in under an hour once the wrapper and the written placeholder contract exist. The ramp-up risk sits with consumers, not authors: teams must know the difference between config-layer placeholders and Blade templating, or we will end up with two interpolation dialects in one codebase. Address that in the docs step, not after adoption.
How can I help you explore Laravel packages today?