{"record":{"id":"bc4c82742593d554","repo":"getgrav/grav","slug":"cannot-override-frozen-service-s","errorCode":null,"errorMessage":"Cannot override frozen service \"%s\".","messagePattern":"Cannot override frozen service \"(.+?)\"\\.","errorType":"exception","errorClass":"Pimple\\Exception\\FrozenServiceException","httpStatus":null,"severity":"error","filePath":"system/src/Pimple/Container.php","lineNumber":107,"sourceCode":"     * Allowing any PHP callable leads to difficult to debug problems\n     * as function names (strings) are callable (creating a function with\n     * the same name as an existing parameter would break your container).\n     *\n     * @param string $id    The unique identifier for the parameter or object\n     * @param mixed  $value The value of the parameter or a closure to define an object\n     *\n     * @return void\n     *\n     * @throws FrozenServiceException Prevent override of a frozen service\n     */\n    public function offsetSet(mixed $id, mixed $value): void\n    {\n        if (!is_string($id)) {\n            throw new InvalidServiceIdentifierException($id);\n        }\n\n        if (isset($this->frozen[$id])) {\n            throw new FrozenServiceException($id);\n        }\n\n        $this->values[$id] = $value;\n        $this->keys[$id] = true;\n    }\n\n    /**\n     * Gets a parameter or an object.\n     *\n     * @param string $id The unique identifier for the parameter or object\n     *\n     * @return mixed The value of the parameter or an object\n     *\n     * @throws UnknownIdentifierException If the identifier is not defined\n     */\n    public function offsetGet(mixed $id): mixed\n    {\n        if (!is_string($id)) {","sourceCodeStart":89,"sourceCodeEnd":125,"githubUrl":"https://github.com/getgrav/grav/blob/6040efed04efa69b8209448ed81308e7c24147c2/system/src/Pimple/Container.php#L89-L125","documentation":"Pimple stores shared services as definitions and freezes them after their first resolution in Container::offsetGet(). Once frozen, the service has a fixed instantiated value; offsetSet() refuses any later assignment and throws FrozenServiceException to preserve object identity for code that already received the service.","triggerScenarios":"Define $container['db'] as a closure, resolve it with $container['db'] or from a dependency of another service, then execute $container['db'] = $replacement. The second assignment reaches Container.php:106-108 and fails. The original resolution can also happen implicitly when another service or plugin fetches the ID first.","commonSituations":"A Grav plugin tries to replace $grav['twig'], $grav['config'], or another core service after boot has already resolved it; two plugins override the same service in an unpredictable order; test code fetches a service before replacing it.","solutions":["Move the override or service-provider registration before the first read of the service.","Use $container->extend($id, $extension) before the service is resolved when you want to decorate the original definition rather than replace it.","If replacement is intentional and you accept a new instance, call unset($container[$id]) first, then assign the new definition.","Use $container->factory(...) when every access should create a fresh instance and freezing is not desired.","Reorder plugin initialization so overrides happen during registration, not during a later hook."],"exampleFix":"// before\n$container['mailer'] = fn () => new Mailer('smtp-1');\n$old = $container['mailer']; // freezes the shared service\n$container['mailer'] = fn () => new Mailer('smtp-2'); // FrozenServiceException\n\n// after\n$container['mailer'] = fn () => new Mailer('smtp-1');\n$container->extend('mailer', fn ($mailer, $c) => $mailer->withHost('smtp-2'));\n$mailer = $container['mailer'];","handlingStrategy":"validation","validationCode":"if (isset($container[$id]) && $container->initialized($id)) {\n    throw new LogicException(sprintf('Cannot replace already resolved service \"%s\".', $id));\n}\n$container[$id] = $replacement;","typeGuard":"function isUnfrozenServiceId(Pimple\\Container $container, string $id): bool\n{\n    return isset($container[$id]) && !$container->initialized($id);\n}","tryCatchPattern":"try {\n    $container[$id] = $replacement;\n} catch (FrozenServiceException $e) {\n    throw new LogicException(sprintf('Register replacement for \"%s\" before first resolution.', $id), 0, $e);\n}","preventionTips":["Put overrides and extend() calls in service-provider registration code.","Never fetch a shared service before deciding to replace it.","Use initialized() in bootstrap assertions.","Prefer decorators passed through dependencies over late container mutation.","Treat frozen-service failures as initialization-order bugs, not transient errors."],"tags":["pimple","dependency-injection","php","frozen-service","service-override"],"backgroundTag":"frozen-service-override","analyzedSha":"6040efed04efa69b8209448ed81308e7c24147c2","analyzedAt":"2026-08-17T05:07:31.593Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}