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.
| 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.
There was a problem hiding this comment.
Well, I guess adding the methods for modifying and removing dot notation in arrays in ArrayHelper would be a great feature to have in 4.8. and since if those methods are getting added, then having that thing here would be a addon instead of overkill.
So, my vote goes to adding dot notation in Context.
|
@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. |
| public function getOnly(array|string $keys): array | ||
| { | ||
| if (is_string($keys)) { | ||
| $keys = [$keys]; | ||
| } | ||
|
|
||
| return array_filter($this->data, static fn ($k): bool => in_array($k, $keys, true), ARRAY_FILTER_USE_KEY); | ||
| } |
There was a problem hiding this comment.
I ran a local benchmark using the following setup:
Context data size: ~1,000 entries, Requested keys: 5, Iterations: 50,000
On my system, the results were:
Old Method (array_filter): 5.7311 seconds
New Method (array_intersect): 0.1905 seconds
Performance Gain: 30.1x faster
Even though a typical application is unlikely to store this many entries in the Context, I believe the framework core should be optimized for worst‑case scenarios by default.
| public function getOnly(array|string $keys): array | |
| { | |
| if (is_string($keys)) { | |
| $keys = [$keys]; | |
| } | |
| return array_filter($this->data, static fn ($k): bool => in_array($k, $keys, true), ARRAY_FILTER_USE_KEY); | |
| } | |
| public function getOnly(array|string $keys): array | |
| { | |
| $keys = (array) $keys; | |
| return array_intersect_key($this->data, array_flip($keys)); | |
| } |
There was a problem hiding this comment.
That's really good performance boost. But with new suggestion of supporting dot notation, I guess this method would change completely.
| public function getExcept(array|string $keys): array | ||
| { | ||
| if (is_string($keys)) { | ||
| $keys = [$keys]; | ||
| } | ||
|
|
||
| return array_filter($this->data, static fn ($k): bool => ! in_array($k, $keys, true), ARRAY_FILTER_USE_KEY); | ||
| } |
There was a problem hiding this comment.
Old Method (array_filter): 6.3526 seconds
New Method (array_diff_key): 0.689 seconds
Performance Gain: 9.2x faster
| public function getExcept(array|string $keys): array | |
| { | |
| if (is_string($keys)) { | |
| $keys = [$keys]; | |
| } | |
| return array_filter($this->data, static fn ($k): bool => ! in_array($k, $keys, true), ARRAY_FILTER_USE_KEY); | |
| } | |
| public function getExcept(array|string $keys): array | |
| { | |
| $keys = (array) $keys; | |
| return array_diff_key($this->data, array_flip($keys)); | |
| } |
@michalsn A major part of my security concerns is now resolved. Thanks! |
|
Hello all. I was looking at the discussion and I think, we can also do one more thing for this feature. What if we introduce a third array $shared?? The normal as well as hidden data will be resetted after every request. (Though I haven't seen much implementation of Worker mode and I don't know much how services are cleared and reinitialised) This is just a thought. This shared array will only benefit when worker mode is enabled. Else everything will be normal. This is just a thought. |
|
@patel-vansh Technically, this would be doable if a dedicated method were provided. However, I'm not sure about the practical benefits - why would we need this? I don't recall other frameworks offering this feature. |
|
@michalsn Yeah, I think we can leave $shared array for now. If we need that in future, we can always add that. |
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: