"Unable to resolve service" in production only after enabling laminas config cache
0 reputation · 08 Jun 2024, 15:14 UTC
0 reputation · 08 Jun 2024, 15:14 UTC
Our Laminas MVC application runs cleanly on developer machines, but in production the ServiceManager throws an exception of the form Unable to resolve service \"X\" to a factory for a service whose factory is defined in a module that has been registered in modules.config.php for weeks.
The relevant difference between environments is module_listener_options in application.config.php: production has config_cache_enabled (and the module map cache) turned on, writing the merged configuration into data/cache, while local development does not. We also use the conventional config/autoload/{,*.}{global,local}.php glob, and *.local.php files are excluded from deployment.
What we cannot settle is the intended ownership of the merged-config cache lifecycle. The framework appears to make no decision about when the cached configuration becomes invalid, so every deploy strategy seems to be a convention we must invent ourselves: wipe data/cache on each release, disable the cache entirely, or key the cache file to the release identifier.
Assuming a current laminas-mvc / laminas-modulemanager version pinned in composer.json:
Unable to resolve service error, or should we suspect the missing *.local.php overrides first?data/cache the intended practice?32275 reputation · 08 Jun 2024, 16:25 UTC
A stale merged-config cache is the most likely cause of the production-only Unable to resolve service error. Missing *.local.php overrides typically produce different symptoms (missing values, wrong credentials) and rarely break factory resolution for services defined in a module's own configuration.
Laminas-modulemanager has no built-in automatic cache invalidation. The documented and community-accepted practice is to delete data/cache on every deploy. Since laminas-modulemanager 3.5+, you can also set a custom config_cache_key (e.g., a git commit hash) to version the cache without manual deletion.
Cache behavior is stable across 2.x and 3.x releases; the only version-dependent feature is the config_cache_key option introduced in 3.5. Verify your pinned version to know which strategy is available.
The merged-config cache serializes the complete ServiceManager configuration (factories, aliases, initializers). When a module adds, moves, or renames a factory but the cache is not cleared, the ServiceManager reads the outdated definition and cannot resolve the service. This only appears in production because config_cache_enabled is true there and false locally.
*.local.php files are a secondary suspectLocal overrides usually affect environment-specific values (database DSN, API keys). They do not remove factory definitions that come from a module's module.config.php. If the missing service's factory is defined in the module itself, the absence of *.local.php files will not cause this error.
data/cache/* after composer install and before serving traffic.'config_cache_key' => getenv('DEPLOY_VERSION') ?: 'default' in module_listener_options and pass a unique value per release (e.g., DEPLOY_VERSION=$(git rev-parse HEAD)).Disabling config_cache_enabled in production is not recommended; merging configuration on every request has a measurable performance cost.
# Check laminas-modulemanager version
composer show laminas/laminas-modulemanager
# Inspect the cached config (replace 'production' with your environment)
cat data/cache/module-config-cache.production.php | grep -n 'service_name'
# Clear cache manually (if not using config_cache_key)
rm -rf data/cache/*
composer install --no-dev.data/cache or ensure a unique config_cache_key is provided.modules.config.php is up to date.opcache.validate_timestamps=0 requires restart or cache clear on deploy).If the missing service's factory is defined inside a closure that reads an environment variable only set locally, the error would persist even with a fresh cache. Check whether getConfig() in the module returns a closure that behaves differently per environment.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.