feat: Add Global Context feature#9970
Conversation
|
Before deciding whether we need this, we should ask if other frameworks have already faced it. I did some research. Laravel introduced a first-class Context facade in v11. The idea is simple: set data once in middleware and have it automatically available everywhere (including in log entries) without passing values through every method. It even propagates context into queued jobs, so a Symfony approaches it differently. It uses What this PR is proposing is closer to Laravel model: a single, consistent concept that works the same whether we're handling HTTP, or running a command. Why is context useful? When something breaks in production, we want every log entry from that request to have the same |
|
@michalsn Yes. When I myself created few pure API projects with CI 4, I almost everytime have to create custom solution for global context management. Also for better traceability and monitoring, central global context is a good idea (I think). With just one boolean toggle to turn on or off the logging of this context will enable devs to switch between dev environment (where all log levels are enabled and log files fill up really quickly) and prod environment (where not all log levels are enabled but you need traceability) really easily without much headache. For normal website projects, this is not much useful as you have sessions and cookies. But I guess this feature would serve as helpful addon rather than unnecessary debt. But again, decision makers are community members and maintainers, not me. |
|
Haven't worked with this. The attributes in the symfony contain a twig. |
Do you mean that Symfony request attributes are accessible from templates directly? |
michalsn
left a comment
There was a problem hiding this comment.
Overall, I think the new Context service is a valuable addition and the implementation is heading in a good direction.
I have one suggestion: context data should be displayed in the Toolbar under the "Vars" section, with a clear separation between normal and hidden data. But this may also be done in a separate PR.
| * **NOTE:** This **DOES NOT** include any data that has been marked as hidden | ||
| * using the `setHidden()` method of the Context class. | ||
| */ | ||
| public bool $logGlobalContext = false; |
There was a problem hiding this comment.
This probably should be true by default.
| public function get(string $key, mixed $default = null): mixed | ||
| { | ||
| return $this->data[$key] ?? $default; | ||
| } |
There was a problem hiding this comment.
Dot notation is widely used for nested data access in the framework already. Given that, it would be good to align Context behavior as well, so dotted keys are treated as paths for nested arrays rather than plain string keys.
I suggest handling this consistently across get()/has()/remove() (and their hidden variants).
There was a problem hiding this comment.
For dot notation, the ArrayHelper.php has method for getting values and to check if value exists in array or not, but it doesn't have any for modifying or removing any. Would you like to create those methods in ArrayHelper first and then use those in here, or I would just declare private methods in Context.php itself for now, and then in separate PR we would just add that in ArrayHelper.php?
There was a problem hiding this comment.
Also, for methods like getOnly and getExcept, we need to think about ArrayHelper methods for that too.
There was a problem hiding this comment.
Good point. I've started implementing this in ArrayHelper and will send a PR today.
As I was working on it, I had second thoughts about whether we should add support for dot notation here, since most users will probably be working with flat arrays. Now, I'm not entirely sure this is the right approach (an overkill), but I'd be interested to hear what others think.
| public function missing(string $key): bool | ||
| { | ||
| return ! $this->has($key); | ||
| } |
There was a problem hiding this comment.
I'm not a big fan of all the missing* methods, since we don't usually implement these in the framework. We generally prefer working directly with ! $this->has() when needed.
| * | ||
| * @var bool | ||
| */ | ||
| protected $logGlobalContext; |
There was a problem hiding this comment.
| protected $logGlobalContext; | |
| protected bool $logGlobalContext = true |
| $this->logCache = []; | ||
| } | ||
|
|
||
| $this->logGlobalContext = $config->logGlobalContext; |
There was a problem hiding this comment.
| $this->logGlobalContext = $config->logGlobalContext; | |
| $this->logGlobalContext = $config->logGlobalContext ?? $this->logGlobalContext; |
| if ($this->logGlobalContext) { | ||
| $globalContext = service('context')->getAll(); | ||
| if (is_array($globalContext) && $globalContext !== []) { | ||
| $message .= ' ' . json_encode($globalContext); | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
The check is_array($globalContext) is not needed here as this variable is guaranteed to be an array.
Appending global context JSON directly to the message is acceptable for file-based output. However, for non-file handlers (e.g. external log systems, or even ChromeLogger), this is not ideal because context becomes part of the message string rather than structured metadata.
I think we should merge this PR with the current logger behavior, then follow up with a separate PR to redesign logger/handler integration so handlers can receive structured global context cleanly.
I'm happy to prepare that logger follow-up PR after/if this one is merged.
|
@datamweb instead of downvoting, I think it would be more beneficial for all to hear your thoughts on this. |
Request available as https://symfony.com/doc/current/templates.html#the-app-global-variable I haven't tested a symfony in a long time. There was something like that once: |
|
Thanks @neznaika0. I believe this is a topic for a separate discussion, as it would involve changes to the Parser to make the context automatically available. It's something we can consider in the future, although I'm not a big fan of the idea. For now, let's focus on the Context class itself. |
|
@paulbalandan To be completely honest, I read this entire PR yesterday. I initially held back from commenting because I truly didn't want to drain the energy of an active contributor. The core concept of a Context is highly valuable and modern, but my downvote stems from severe concerns regarding the implementation and the default behavior. Here is a breakdown of my technical concerns: Recently, we have been moving towards FrankenPHP, and a part of the CI4 community relies on the CodeIgniter4-Burner library (which enables high-performance servers like RoadRunner, OpenSwoole, and Workerman). In these persistent, long-running worker environments, the application does not die after a request. If the Context class is implemented as a shared service or global state without a bulletproof, automatic reset() hook explicitly tied to the end of the HTTP lifecycle, we will encounter State Bleeding. Metadata from Request A (e.g., As developers begin adopting this new feature in 4.8.0 to store general request data (emails, IPs, phone), they naturally won't mark everything as hidden (since they aren't strict "passwords"). Because I am not against the feature itself, but I strongly advocate for the following before we merge this into the Core: Keep If you believe I am misunderstanding any part of the implementation or its guarantees, please feel free to correct me. |
|
You are right to call out the risk of accidental context logging. I had proposed As for worker mode, |
@michalsn Thanks for clarifying! class AuthFilter implements FilterInterface
{
public function __construct() {
$this->context = service('context'); // Cached once per worker
}
}Since the AuthFilter instance stays alive across multiple requests, wouldn’t If so, should we add a strict warning in the docs advising developers to never store |
|
@datamweb Filters do not persist between requests. We reset pretty much everything - one way or another. |
Description
This PR adds Global Context feature for requests.
Normal context is automatically added to logs if
$logGlobalContextis enabled inapp/Config/Logger.phpfile. This provides better monitoring for any logs or exceptions along with maintaining security with hidden context feature.Checklist: