Copy link to clipboard
Copied
Whew! This is a new one on me! One of those times CF simply isn't making any logical sense to how I understand it. I've even gone through CF10 Application Server and IIS Admin service restarts and still getting the same problem.
In short, I have a template component which has a variable called rawCode. It is the current request's generated HTML that is to be output to the buffer. I am inside a method inside the template component, and I dump: THIS.rawCode.
Sure enough, it contains what I expect.
I then call a method from within this method (that method exists in the template component as well)
<cfinvoke component="#THIS#" method="writeOutputToBuffer">
All that method does right now is:
<cfoutput>#THIS.rawOutput#</cfoutput>
But guess what? At that point, there's nothing! Nothing is presented to the user. I had it dump THIS.rawCode and BOOM, the rawCode is there and populated with what I wante returned to the user. Has anyone seen something like this before? I'm sure there's some rare thing I'm doing wrong here, like some recently unknown "CF10 can only support referencing a variable 20 times per request" rule or something as crazy.
Does writeOutputToBuffer() have output="true"?
Jason
Copy link to clipboard
Copied
You should step back a little at this point. I'm not sure if this was on my side or not, and I'm a proponent in the belief that communication through text-only mediums can often lead to miscues and inferred inflection of emotion that isn't there, but nobody is arguing with you.
Hey guys, I know you've kissed and made up 'n' all, but just from someone on the periphery...
Aegis, Jason did have a point that you tend to waffle a bit about your explorations and go off on tangents rather than either just doing what we've asked, or if you have: letting is know that you've done what we asked. The findings and wafflings are good (I do mean this), but TBH, it'd be great if you could just focus on the problem you've raised, let us help you with it, get it sorted, then we can cover the peripheral musing or asides or what-have-you separately. It's difficult to work out where we are at with getting the problem solved some times. I was in exactly the same position as Jason was last night, not knowing whether you'd actually tried what was suggested or not.
Jason: yeah, slightly surly there mate (and, yes, pot-kettle etc). But TBH, I thought it wasn't entirely misplaced. Well the message was correct if not perhaps the delivery.
Anyway: this post is motly pointless, but it was my reading of the latter part of the thread, anyhow.
Cheers.
--
Adam
Copy link to clipboard
Copied
You've hit the nail on the head with 1 key word; Focus.
My learning condition is unique in that I have a hard time maintaining focus, and as studies into the 'working memory' (the area responsible for short-term memory) have shown, people like myself who others would consider are 'absent minded' have a core problem with focus, which is vital to maintaining a high-functioning working/short-term memory. This is the reason why you see me 'waffling'.
I know it's a fault of mine, and situations like this one on the forum (and HUNDREDS at work) constantly remind me of my short-coming, causing me to have to bring it to attention over and over if I were to preface all forum posts with it. What compounds that sad state of affairs is, being a perfectionist, and realizing my memory is FAR from perfect (and causes others this kind of distress), causes me to hate myself for something I really have little control over.
So for what it's worth, I'm sorry if it seems I'm going off key. You guys remind me of co-workers I have who don't have those focus issues, and when they try to teach me something, they present multiple new ideas and things I don't yet have a firm grasp on (or as we saw here, THOUGHT I knew, but found out I didn't know how they actually worked) which just derails me completely and forces me to ask them to present the topics in a single-threaded approach.
On the other side of people like myself who have focus issues, the good thing they say about them is that the lack of focus makes them hyper vigilant about their surroundings (the very same surrounding-generated cues which cause them to lose focus) I have saved my own life and others thanks to how aware I am of my environment along with quick reflexes; but there have been many more times where I'd gladly give all that up just to have more focus.
Copy link to clipboard
Copied
I was having an issue with converting some of my INVOKES but using what I learned I changed:
<cfinvoke component="#APPLICATION.paths.appAbsComsFQDN#app" method="processXMLPathingVars" data="#LOCAL.moduleConfigData#" returnvariable="LOCAL.moduleConfigData">
to this:
<cfset LOCAL.moduleConfigData = createObject( 'component', APPLICATION.paths.appAbsComsFQDN & 'app' ).processXMLPathingVars( data = LOCAL.moduleConfigData )>
And it seemed to do the job. (Plus, like you guys are saying, is much less verbose. I think when you first start off in CF, the ability to be verbose eases you into using it; but after you get better at CF, you just want your code to take the least amount of space (and processing time) to be as optimal as possible.
Jason, on the second point you made, I'm looking for clarification. You stated that when you use CFINVOKE to create an object, that object cannot be reused. So, if I do something like this:
<cfset REQUEST.template = createObject( 'component', 'www.app.coms.template' )>
vs.
<cfinvoke component="www.app.coms.template" returnvariable="REQUEST.template">
Now, I hopefully understand that the REQUEST scope is only existing for as long as the request; it's persistence is that long, right? Unlike an APPLICATION scope, which is persistent over multiple requests. But during the request, would not the application be able to reference REQUEST.template (and all the methods it has) via either of the code examples above? I admit, I was using the latter, but you guys have taught me some new things and I'd like to tighten up my coding practices to be more optimal.
Copy link to clipboard
Copied
>> <cfinvoke component="www.app.coms.template" returnvariable="REQUEST.template">
Where is the method you are invoking? If infact you meant:
<cfinvoke component="www.app.coms.template" returnvariable="REQUEST.template" method="init">
Where init() returns an instance of the object, then yes, you could reuse that object, but it only works if you have an init method and the init method returns an instance of the object. Not all objects are guaranteed to have such a method. createObject() will create an instance of the object and return it regardless of the availability of an init() method.
Jason
Copy link to clipboard
Copied
I have to admit that when it comes to the whole 'objects returning THIS' area of OOP, I'm not fully 'there' yet. I am attempting to learn CF in an OOP-oriented as possible. Maybe if I give you a background into what I've done so far, you can say "That's fine" or "That's a bad coding habit".
I have 5 components which have such commonly accessed functions in them, I store them into the APPLICATION scope in a structure called 'coms' (APPLICATION.coms) These are : data.cfc, error.cfc, security.cfc, template.cfc and utility.cfc. So after application initialization, I have the following 5 objects in a persistent scope:
When I create them, I basically do a CFDIRECTORY of all '.cfc' files in the designated folder, and I loop over them in the following fashion:
<cfloop query="LOCAL.fwComponents">
<cfset structInsert( APPLICATION.coms, listFirst( name, '.' ), createObject( 'component', 'www.apps.coms.' & listFirst( name, '.' ) ), true )>
</cfloop>
So if I understand things, so far, I've created 5 persistent component objects, right? Even though I'm delving into comprehending them as objects, I have a later function called "instantiate FWComponentInstances" where I pass params off to that object's init() function in order to customize its instance.
Having assumed this is all good, I realized I don't need to send the THIS.datasource variable to the APPLICATION.coms.data component object's init() function, because since that component extends the root application, it shares the THIS.datasource variable, and has access to it anyways.
Anyways, onRequestStart() I create 2 request-based instances off the APPLICATION instance components, namely:
<cfset structInsert( REQUEST, 'error', APPLICATION.coms.error, true )>
<cfset structInsert( REQUEST, 'template', APPLICATION.coms.template, true)>
My mindset behind this was that 'For every user who makes a request, they need an instance copy of these 2 components because the request could have it's own error (while someone elses does not) so I don't want to share that, and the same with the template component (it uses a variable called 'rawCode' that is the request's generated code, and since each user has their own request, we use an instance rather than the object in the APPLICATION scope.
I am reading through: http://objectorientedcoldfusion.org/ right now (answers some questions, raises others), in hopes I have the right methodology going forward.
(UPDATE: I have completely revoked the invoke on my site! I understand BKBK's note that CFINVOKE can do more than just component instantiation, and that's where it gets its power, but since component invokation was all I was currently using it for on my site, I have replaced it with CFSET-based calls.)
Copy link to clipboard
Copied
Aegis Kleais wrote:
Anyways, onRequestStart() I create 2 request-based instances off the APPLICATION instance components, namely:
<cfset structInsert( REQUEST, 'error', APPLICATION.coms.error, true )>
<cfset structInsert( REQUEST, 'template', APPLICATION.coms.template, true)>
No, you aren't creating 2 request-based instances off the APPLICATION scope. You are adding two references to the object in the application scope. The objects that are shared by all users. Your users are not gettign their own instance of the objects per request, they are all sharing the same objects.
structInsert() does nto make a copy of an object, it creates a new reference to the object. If you want each user to have their own instance of the objects on every request, then you need to createObject() on every request, probably in onRequestStart().
If however all you need is for each user to have access to these singletons, then you can access them directly from the APPLICATION scope, there is no need to make a reference in the request scope.
What you need depends on what those objects are actually doing.
Jason
Copy link to clipboard
Copied
So, what I'm doing is creating pointers to the same object then, right? Unless I specifically say:
<cfset REQUEST.template = createObject( 'component', 'www.app.coms.template' )>
then doing it in the previous way I showed was simply pointing back to the APPLICATION.coms.template object.
Looks like I'll need to change my code (but I'm going to continue reading about OOP before doing so to see if I better grasp these concepts)
Of the 5 components (data, error, security, template and utility), I segregated them as follows:
REQUEST-scoped components (because these components deal with user-based requests and functionality, I stored them into the REQUEST scope)
APPLICATION-scoped components (because these hold non-user-based functionality, things that all users make use of and are not based on the user's unique request needs)
Jason, I think I see what you mean with my code. structInsert() was making a key that POINTED to something that was instantiated into the APPLICATION, so in effect, it was not going to be uniquely instanced to the user's request. I think I really need to read up on the OOP functionality of CF to catch myself from doing these types of mistakes where I'm assuming I"m getting one thing, but in fact, getting another. Thanks for the catch there. I will update my site accordingly.
Copy link to clipboard
Copied
This has been an interesting read on objectorientedcoldfusion.org, but it raised a couple questions.
1. To my knowledge, when a component extends another (like our template.cfc extending the root application.cfc), then the child has access to the parent's private functions and variables. Currently, my template.cfc had variables that I placed into the THIS scope, but after reading the article, I think I need to change them to the VARIABLES scope, making them private to the component. If I create a instance of this component via: <cfset REQUEST.template = createObject( 'www.app.coms.template' ).init()>, do I have to create "Getters" and "Setters" in order to simply get the value of VARIABLES-scoped variables in that component? ie, if there was a VARIABLES.myName = 'Aegis', is it possible to just get its value by saying <cfoutput>#REQUEST.template.myName#</cfoutput> or is that variable so private that I would have to call a function in the template.cfc that returns that value? like <cfoutput>#REQUEST.template.getName()#</cfoutput>? The site doesn't really say.
It DID, however show me what the init() function is, and how it can customize the instantiated copy of that component. Very powerful. A lot of things are beginning to click, and it's been thanks to you guys explaining these rather advanced intricacies. If I understand it correctly, if you had a component called dog.cfc, and it had a variable which held the name of the dog (defaulted to nothing), I could instantiate 2 dogs like:
<cfset REQUEST.dog1 = createObject( 'www.app.coms.dog' ).init( name='HeyYou' )>
<cfset REQUEST.dog2 = createObject( 'www.app.coms.dog' ).init( name='Mr.ChewyBiteUms' )>
Each has it's own copy of internal variables and access to that component's methods, which can reference those unique instance variables to perform in different ways.
Copy link to clipboard
Copied
Aegis Kleais wrote:
It DID, however show me what the init() function is, and how it can customize the instantiated copy of that component. Very powerful. A lot of things are beginning to click, and it's been thanks to you guys explaining these rather advanced intricacies. If I understand it correctly, if you had a component called dog.cfc, and it had a variable which held the name of the dog (defaulted to nothing), I could instantiate 2 dogs like:
<cfset REQUEST.dog1 = createObject( 'www.app.coms.dog' ).init( name='HeyYou' )>
<cfset REQUEST.dog2 = createObject( 'www.app.coms.dog' ).init( name='Mr.ChewyBiteUms' )>
Each has it's own copy of internal variables and access to that component's methods, which can reference those unique instance variables to perform in different ways.
That might indeed be so. However, there is one point I hope you will pay attention to. It relates to maintenance and is variously called "The Law of Demeter", "Principle of Least Knowledge", "Only talk to your friends" or "Don't talk to strangers".
This is a software design rule that says that an object should avoid invoking the methods of a member object returned by another method. In other words, the rule says your code should, when invoking a method, use as few dots as possible, ideally just one. Thus, a.methodA() is preferable to a.b.methodB().
The following pseudocode illustrates this:
component A{
methodA () {
b = createobject("B").init();
return b;
}
}
component B{
methodB () {
// functionality needed by A
}
}
Consider the code a.b.methodB(). The object a creates the object b by a method call, and uses b to call methodB(). This kind of chaining makes a component to be dependent on the internal structure of others. If you wish to change a component in such circumstances, you will have to rework all its callers.
I'll borrow this line of code from you:
REQUEST.dog = createObject( 'www.app.coms.dog' ).init( name='HeyYou' );
Let us combine it with an often quoted Demeter example.
REQUEST.dog.getBody().getTail().wag();
Programmers are advised to avoid writing code-chains like that. This is bad because that one line depends on the structure of three different objects. This style of coding creates structural dependencies (coupling) between unrelated objects. If you use it throughout, your software will be a nightmare to maintain.
The solution is to re-engineer the example as follows:
REQUEST.dog.expressHappiness();
This leaves it up to the implementation of the dog to decide what to do when the bone appears.
Copy link to clipboard
Copied
- APPLICATION.coms.data
- APPLICATION.coms.error
- APPLICATION.coms.security
- APPLICATION.coms.template
- APPLICATION.coms.utility
[...]
Having assumed this is all good, I realized I don't need to send the THIS.datasource variable to the APPLICATION.coms.data component object's init() function, because since that component extends the root application, it shares the THIS.datasource variable, and has access to it anyways.
I meant to comment on this. Those CFCs you mention should not extend Application.cfc. Only other Application.cfcs should extend an Application.cfc. When using OO inheritance, you ought to follow the "is a" rule. An Apple IS A Fruit, so Apple.cfc can extend Fruit.cfc. An Apple is NOT an Orange, so Apple.cfc should not extend Orange.cfc.
Data is not an Application, so it should not extend Application.cfc.
You are using inheritance to make variable access more convenient, which is something one ought not do.
There is another rule "has a" which would work (kind of) here. A Data.cfc might HAVE A application context (ie: some settings from the Application.cfc), so this suggests Data.cfc should get initialised with an applicationContext object (or perhaps just a struct, to save too much horsing around), which would hold DSN values etc.
But to be honest, Data.cfc probably just needs the DSN passed into it, yes? So don't mess around with application context objects/structs; just give its init() a DSN argument, and initialise it accordingly.
Or use ColdSpring to manage all of this sort of thing for you.
--
Adam
Copy link to clipboard
Copied
Adam. That makes complete sense now that I have a futher grasp. I will cut off all extension of those 5 core components and then see if I can change the framework/applicaiton so that they pass what is needed. I have successfully already modified my REQUEST.template component and moved certain vars that are needed by other components into its THIS scope, leaving the rest in its private VARIABLES scope, and after adjusting code where it was needed, was able to remove the "getPrivateVar()" function I made in the template component. Much cleaner. I'll try to remember these "is a" and "has a" rules for more structured. development.
BKBK. I'm trying to wrap my ahead around your info. I've gone so far as to create substructures with a semantic name to them (increasing the 'dot count'), for example.
<cfset structInsert( APPLICATION, coms, {}, true)>
<cfset structInsert( APPLICATION.coms, createObject( 'www.app.template' ), true )>
Now I can call APPLICATION.coms.template (though 'coms' is more of a placeholder than calling 'APPLICATION.template'. I think this has to do with my overly aggressive nature to "organize" things. Having everything be in the root APPLICATION scope just seems messier to me vs this 'coms' structure, which I know "holds application components".
What I took from your post (and correct me if I'm wrong) is that we should take as direct as possible route from object to method, without the need to transverse from one to the other for the same task. Am I "barking" up the right tree there?
Copy link to clipboard
Copied
<cfset structInsert( APPLICATION, coms, {}, true)>
<cfset structInsert( APPLICATION.coms, createObject( 'www.app.template' ), true )>
One tip regarding this code. structInsert() is a bit "CF 4.5". Since as long as I can remember structInsert() has not been neded, and one can just do this:
application.coms = {};
ie: setting the struct key directly without the function call.
And at no point would you have ever needed both those lines of code, as the second overwrites the first anyhow.
As of CF8, one can use s shorthand notation to populate a struct, too:
myStruct = {
someKey = "Some Value",
anotherKey = "Another Value"
};
etc.
Similarly with arrays:
numbers = [1,2,3,4];
Using this otation makes the code look a bit cleaner and easier to read (IMO, obviously).
--
Adam
Copy link to clipboard
Copied
Funny you should say that.
Remember how I said I was a bit of an organization freak? I found that when I do:
<cfset APPLICATION.coms = {}> and then dump the object, it's seen as 'APPLICATION.COMS'.
When I do <cfset structInsert( APPLICATION, 'coms', {}, true )>, and then dump the object, it's seen as 'APPLICATION.coms' (respecting the casing).
I think I just need to get past the fact that CF is case insensitive in this matter and has a fondness for uppercasing everything anyways.
I have been using "new" to replace createObject where I can. I know you can omit createObject()'s first argument if you're making a component, but I've been using the syntax of:
<cfset structInsert( LOCAL.retVar, 'error', new '#APPLICATION.paths.fwAbsComsFQDN#error'(), true )>
Which I will now change to
<cfset LOCAL.retVar.error = new '#APPLICATION.paths.fwAbsComsFQDN#error'()>
To follow your guideline. Much thanks! I love all the freaking changes I've made to my framework/application because of this new knowledge.
Copy link to clipboard
Copied
You can do <cfset APPLICATION['coms'] = {}> and the casing will be maintained.
-Carl V.
Copy link to clipboard
Copied
Carl! My OCD thanks you.
Copy link to clipboard
Copied
Just so you know, casing on variable names is generally unimportant unless you work with AJAX requests. ColdFusion is case-insensitive, while JavaScript is case-sensitive.
-Carl V.
Copy link to clipboard
Copied
myStruct = {
"someKey" = "Some Value",
"anotherKey" = "Another Value"
};
This also preserves case.
--
Adam
Copy link to clipboard
Copied
Adam -
Looks like it works. I can just send over the vars that the method it's calling is expecting. Seems to work fine.
I was wondering about that structure shorthand. Initially, I thought I would HAVE to quote the name of the variables (else CF would look for a variable with that name) but I noted I didn't have too. CF saw it as an argument name.
I've removed all structInsert() calls and am now going through my simple <cfset> commands and changing them to, what is that?, 'associative' syntax? In other words, instead of
<cfset THIS.name = 'appName'>
I'll now use
<cfset THIS[ 'name' ] = 'appName'>
Only slightly more characters, but if it helps to ensure casing, I'm a happy camper.
Copy link to clipboard
Copied
I've removed all structInsert() calls and am now going through my simple <cfset> commands and changing them to, what is that?, 'associative' syntax? In other words, instead of
Yeah, you usually hear the terms "dot notation" and "associative array syntax" to distinguish between the two. Or "bracket notation" for the latter too.
Another benefit of the associative-array syntax is that the struct keys don't have the same restrictions on them as dot-notation keys do (starts with a letter, underscore or currency symbol; can contain only those and numbers after that). With associative array notation they key can be anything. EG: myStruct[chr(7)] = true is legit. chr(7) is the "audible bell" character, and is not printable.
--
Adam
Copy link to clipboard
Copied
@Carl - Yeah, it was an OCD thing. I like first-letter lowercase + camel casing like (firstName, sumBeforeCalculation, etc.) Variable names, functions.... It just looks most pleasing to me Full caps is just aesthetically ugly, even though the user will never see those things.
@Adam - Neat tidbit there. This week has had so many revelations for me, I'm trying to keep the ball rolling. Will be working a Saturday tomorrow with co-workers on the framework; think they're gonna be amazed at all the progress.
Copy link to clipboard
Copied
@Aegis, I totally get it. I agree with the aesthetics issue. I tend to write code using the camel casing for consistency, but I usually don't pay too much attention to the way CF mangles it in <cfdump> output. Except when I do...
Copy link to clipboard
Copied
Adam **UPDATE**
And it works!
I removed all the EXTENDS attributes and made it so that during the creation of the APPLICATION.coms.data component, I sent the THIS scope as an ARGUMENTCOLLECTION so I could reference the application-set THIS.datasource value. It stored it into the data component's VARIABLES.datasource, and with a couple other reference changes, it was up and running.
I don't like the idea of passing the WHOLE THIS scope over as an argument collect so that the function has any variable it needs from the application.cfc to instantitate those framework components, but I think it's just passing over a reference or pointer, right? Not like it's duplicating the THIS scope and wasting memory with some type of duplication. Probably just says "Here's a pointer to the THIS scope variables, use what you want".
Copy link to clipboard
Copied
Good stuff, and yes, correct: it's a reference, not all the actual data. Personally, I'm a bit pendantic and always just pass exactly what's needed and nothing more, but there are arguments both ways, and I'm not really married to either.
--
Adam
Copy link to clipboard
Copied
Speaking of pedantics...
Could I send a custom structure with just the keys I want? Something like:
<cfset APPLICATION.coms = new '#THIS.appRootURL#coms.app'().instantiateFWComs(
argumentCollection = {
datasource = THIS.datasource,
appName = THIS.name
}
)>
vs.
<cfset APPLICATION.coms = new '#THIS.appRootURL#coms.app'().instantiateFWComs(
argumentCollection = THIS
)>
That way, like you said, I don't need to send over pointers to anymore than the function will need.
Copy link to clipboard
Copied
Could I send a custom structure with just the keys I want? Something like:
Best way to find out... is to try it...
--
Adam