RendererEncapsulator passes view-variables to "next" rendered view

Description

hey luis!

i'm not sure if this is a bug, but i thought i give some feedback anyway ...

i have been using cb6 for a few weeks now (in development) - and one of the first changes i noticed was the new "behaviour" of the renderer.
we used to local-scope all our view-variables, which doesn't work anymore (at least not the way we did it) - because we just declared it once and then used the variables without explicitly calling the local-sope anymore. those variables now end up in "variables.local" - which is not an official scope (not yet .

so with cb6 and the rendererEncapsulator.cfc i thought it would be the easiest way to just put everyting directly into the variables-scope. it's encapsulated, right? but then I discovered by chance that variables which are set within a view (A), are visible (and therefore potentially accessible by reference) by views, which are rendered within the above view (A).

at the moment, i assume that this is not really a problem, since i wouldn't set a struct key (or append to an array) without declaring the struct (or array) beforehand. this would "decouple" the reference and everything is just fine. but anyway, i'm not sure if this could lead to some kind of race conditions or whatever?

if this behaviour is not intentional , it could even easily be solved by passing the renderer (instead of variables) to the customtag - and retrieve the renderer's variables by method-call (see screenshots below).

i would be pleased about some feedback ...

and by the way: if it somehow were possible to get our beloved local-scope back in place, we would really appreciate that. otherwise, the cb6-migration will be a hell of work

greetings from basel
roman

Activity

Show:
Brad Wood
May 26, 2020, 11:11 PM

Hi Using the local scope directly in a view was never supported per say, but rather a “happy accident” due to the fact that the views used to be cfincluded directly inside of a renderer transient. .cfm files have no local scope, so it’s not really a scope you ever should have been using. We could create a local struct in the variables scope for backwards compatibility but it still wouldn’t be included in the CF engine’s scope lookup order so it wouldn’t work for implicit scoping.

but then I discovered by chance that variables which are set within a view (A), are visible (and therefore potentially accessible by reference) by views, which are rendered within the above view (A).

So far as I know, this is not the case. Can you please provide an example of this? The code in your screenshot does not show this. In fact, it shows the opposite. The subview has its own variables scope which is correct and your “struct” variable is not shared between them. If you wish to pass things between views, use the prc or rc structs or use the view args feature of renderView().

if this behaviour is not intentional , it could even easily be solved by passing the renderer (instead of variables) to the customtag - and retrieve the renderer's variables by method-call (see screenshots below).

This would be bad and would actually defeat the purpose of the changes. The renderer is now a singleton for performance reasons, so the cfmodule call was put in place specifically to encapsulate the variables scope of each view instead of letting views trash the variables scope of the renderer CFC. By passing the entire variables scope by reference, you would be making the singleton renderer not thread safe. This is why we moved to wrapping each cfinclude in a cfmodule tag. The disappearance of the local scope is just an unfortunate byproduct of how the underlying code now works.

and by the way: if it somehow were possible to get our beloved local-scope back in place, we would really appreciate that. otherwise, the cb6-migration will be a hell of work

Again, .cfm files have no local scope. The previous implementation simply allowed you to access the local scope of the function in the renderer that was including the view file. You should be using the variables scope for vars “local” to a .cfm. For data you want to share for the entire request, use the rc or prc structs, which are automatically passed by reference for this exact purpose.

Roman Knecht
May 27, 2020, 8:19 AM

Hi Brad

thanks for your fast and detailed feedback.

first: i actually really appreciate the idea (and also your solution to this) of encapsualting the view variables. this is the only reason why we ever started to make use of "the happy accident" of "having" a local-scope within views (altough there is none because of the cfm).

because rendering multiple views meant to include multiple views into the same instance of the renderer (within a single request) - each one writing variables to the variables-scope of this same instance. so we actually had cases where this led to race-conditions (a subview overwriting variables of the parent view). of course this can also be solved by just using unique variable-names ;) but for some reason we started to local-scope them (just to be sure this cannot happen).

so don't get me wrong when i say "beloved local-scope". it was only a workaround. no more and no less. and i also was somehow looking forward to just eliminiate all occurrences of "local." in our views and just use the variables-scope of the thread-safe encapsulator (which would make migration to cb6 less complex).

secondly: no, i don't want to pass variables - which is my actual point. if i want to pass variables i'd use the "args"-param of the renderView()-method (or, as you mentioned, use rc/prc).

all i want is to be safe, that the call of renderView() within a view does not lead to conflicts with variables of the same name.

two days ago, i just split a single view into a "main view" and some kind of "partial" (for better reuseability). but i forgot to also move the variables (which were set at the top of the "main view" within a cfscript-block) into the partial. surprisingly lucee did not throw an error as i would have expected - which is how i got here.

you can find my sample-handler and the two views in the attachments (sample.zip). i assume you should get the result seen in screenshot "1_result1.png" of the ticket (at least with Lucee 5.3.4.80).

you are right about "subview1" having an own variables-scope.

all i'm actually saying is, that all the variables from the rendering of "index.cfm" will be passed to the rendering of "subview.cfm" by renderViewComposite() - which (in my understanding) in this case is executed "within the first customtag-call" and therefore passes the variables-scope of the customtag, not the variables-scope of the renderer - and therefore passing all the "locally" set variables from index.cfm to subview.cfm. (?)

so, my "solution" (if we want to call it like this) would only make sure, that we always retrieve the variables dirctly from the renderer-instance, not from the customtag. i don't want to "misuse" the renderer for something - as i know it's a singleton. i don't want to change that - because i also like better performance ;)

i hope i could better make my point this time. and thanks again for your time!

roman

Brad Wood
May 27, 2020, 6:07 PM

Thanks for the sample code, that helps me see what’s happening. So the variables inside the views are not leaking all the way back up into the renderer singleton, but an interesting behavior of CFML is allowing views rendered inside another view via the “renderView()” call to “see” the variables scope of the top-level view. What happens is the renderView() UDF is copied from the renderer’s variable scope into the variables scope of the custom tag. This “detaches” the UDF from the variables scope of the renderer so now when code inside the renderView() UDF references the “variables” scope, it’s “seeing” the variables scope of the custom tag, which is where the UDF now lives. This allows the nested cfmodule tag to see the parent cfmodule tag’s variables scope. Variables set in the sub view will not make it back to the parent view, but the subview will see variables set in the parent view.

I can fix this very easily to keep the variables scope of the parent view from spilling into the child view. I just need to pass along the attributes.renderervariables struct if it exists, which is a point to the original variables scope of the renderer, which is what we wanted in the first place.

Brad Wood
May 27, 2020, 7:43 PM

Please test the latest snapshot build and confirm that the variables scope doesn’t leak into sub views any longer.

Roman Knecht
May 27, 2020, 8:24 PM

Tested and confirmed. Thanks!

Assignee

Brad Wood

Reporter

Roman Knecht

Labels

Fix versions

Priority

Major
Configure