Thursday, 16 January 2014

Unexpected writeOutput() and <cfoutput> difference

G'day:
Sorry, mentioned this on Twitter a few days ago now:


...but not finding any time for writing at the moment. I'll make a start, and see how far I get.

Here's some sample code:

<!--- test.cfm --->
<cffunction name="usingCfoutput" output="true">
    TEXT WITHIN usingCfoutput():<br>
    <cfoutput>#generateContent()#</cfoutput>
</cffunction>

<cffunction name="usingWriteOutput" output="true">
    TEXT WITHIN usingWriteOutput():<br>
    <cfscript>writeOutput(generateContent());</cfscript>
</cffunction>


<cffunction name="generateContent" output="false">
    <cfset var s = "">
    <cfsavecontent variable="s">
        BEFORE CFOUTPUT<br>
        <cfoutput>IN OUTPUT<br></cfoutput>
        AFTER CFOUTPUT<br>
    </cfsavecontent>
    <cfreturn s>
</cffunction>

<cfsetting enablecfoutputonly="true">

<cfoutput>Calling usingCfoutput():<br></cfoutput>
<cfset usingCfoutput()>

<cfoutput><hr></cfoutput>

<cfoutput>Calling usingWriteOutput():<br></cfoutput>
<cfset usingWriteOutput()>

Basically we've got:
  • a function generateContent(), which generates and captures some text, returning it;
  • a function usingCfOutput() that calls generateContent() from within <cfoutput> tags;
  • another function usingWriteOutput() which does the same thing, except via writeOutput().
For all intents and purposes, this should be the same, eh?
  • Oh, and I have a cheeky little <cfsetting> tag in there too.
... but anyway, that should yield comparable results, right (and, yes, obviously they don't or I'd not be writing this article).

Here's the output:

Calling usingCfoutput():
BEFORE CFOUTPUT
IN OUTPUT
AFTER CFOUTPUT


Calling usingWriteOutput():
IN OUTPUT


So when using usingCFoutput(), the <cfoutput> tags around the call to generateContent() are enough to fulfill the enablecfoutputonly restriction that <cfsetting> has set. So everything within generateContent() counts as being in <cfoutput> tags.

However when using usingWriteOutput(), the writeOutput() around the call to generateContent() does not confer enough "outputness" to count towards fulfilling the enablecfoutputonly restriction.

Now I know the setting is enablecfoutputonly, and I fully expect Adobe to go "well duh, it does what it says", and then close any ticket I raise as "NotABug/AsDesigned", but I think that's horseshit. writeOutput() is supposed to be the equivalent of <cfoutput>, so it should work the same here. Oh, actually... it's even documented as supposedly working the way I expect it to:

writeOutput():
Appends text to the page-output stream. This function writes to the page-output stream regardless of conditions established by the cfsetting tag.
So I guess this is a bug then. I shall raise it accordingly.

Another thing I notice is that output="true" on a function is also documented as follows:

yes: the entire function body is processed as if it were in a cfoutput tag. Variables names surrounded by number signs (#) are automatically replaced with their values.

So even if my writeOutput() wasn't enough in my example above, the output="true" should have worked equivalently. I added a third function to the test code thus:

<cffunction name="usingOutputTrue" output="true">
    TEXT WITHIN usingWriteOutput():<br>
    <cfscript>writeOutput(generateContent());</cfscript>
</cffunction>

And I took the output="true" off the other functions. Their behaviour did not change as a result of that, and usingOutputTrue() behaved like usingWriteOutput(). So: wrong.

I suppose the fly in the ointment here is the <cfsavecontent>. Because in this situation nothing's really being output. It's being captured. However I think the documented intent of writeOutput() and output="true" ought to have them behave the same was as <cfoutput> tags.

I changed my code slightly to not use <cfsavecontent>:

<cffunction name="usingCfoutput">
    TEXT WITHIN usingCfoutput():<br>
    <cfoutput>#generateContent()#</cfoutput>
</cffunction>

<cffunction name="usingWriteOutput">
    TEXT WITHIN usingWriteOutput():<br>
    <cfscript>writeOutput(generateContent());</cfscript>
</cffunction>

<cffunction name="usingOutputTrue" output="true">
    TEXT WITHIN usingWriteOutput():<br>
    <cfscript>writeOutput(generateContent());</cfscript>
</cffunction>

<cffunction name="generateContent">
    <cfset var s = "">
        BEFORE CFOUTPUT<br>
        <cfoutput>IN OUTPUT<br></cfoutput>
        AFTER CFOUTPUT<br>
    <cfreturn s>
</cffunction>

(The calling code was the same). And it behaves the same as before:

Calling usingCfoutput():
BEFORE CFOUTPUT
IN OUTPUT
AFTER CFOUTPUT


Calling usingWriteOutput():
IN OUTPUT


Calling usingOutputTrue():
IN OUTPUT




So it's not <cfsavecontent>. It's just "CFML not working properly" (I say "CFML", because Railo and OpenBD behave the same as ColdFusion here).

I've raised a bug for this: 3694950.

--
Adam