Problem: When requesting the WSDL for a CFC, I get the following error: Variable FORM is undefined. It happens in this line of code, in the OnRequestStart method in application.cfc
<cfif structKeyExists(form,'resetappvars')>
<cfset OnApplicationStart() />
</cfif>
If I request a specific method, it works fine. I have considered using cfparam to create a default form struct if none exists, but that seems like an ugly hack and I worry it will actually create the form struct in the variables or this scope of the CFC. Maybe this is a legitimate bug as well?
Note: This only happens when I request the WSDL, if I invoke a method directly - the code executes as expected without problems.
Update: Application.cfc code sample - just add any CFC to your app and request it with ?wsdl to see the issue. This has been tested (and failed) on ColdFusion 7 and ColdFusion 8.
<cfcomponent output="false">
<cffunction name="OnApplicationStart" access="public" returntype="boolean" output="false" hint="Fires when the application is first created.">
<cfset application.dsn = "my_dsn" />
<cfreturn true />
</cffunction>
<cffunction name="OnRequestStart" access="public" returntype="boolean" output="false" hint="Fires at first part of page processing.">
<cfargument name="TargetPage" type="string" required="true" />
<cfif structKeyExists(form,'resetappvars')>
<cfset OnApplicationStart() />
</cfif>
<cfreturn true />
</cffunction>
</cfcomponent>
Maybe try adding a:
<cfif IsDefined("form")>...</cfif>
around the above code?
You could also cfparam the variable you're looking for then just change your logic a little (assuming resetAppVars is a boolean:
<cfparam name="form.resetAppVars" default="false" />
...
<cfif form.resetAppVars>
<cfset OnApplicationStart() />
</cfif>
Edit: I'm not sure if the above code could be considered a hack, but it seems pretty standard CF, to me.
This post of Ben Nadel gives detailed list of scopes available for different types of requests.
By reading it you can easily find out that form scope is not available in given context, but url is.
I've heard it's just a matter of opinion, but it seems to me that it is improper to reference your form scope within a CFC, as there is no guarantee that the form scope will be available when your cfc is invoked and when your method is called. It is better to ensure that any data that needs to be available to the method is provided explicitly to your object. This can be done either by including an argument:
<cfargument name="resetAppVars" type="boolean" required="false" default="false" />
Then you check arguments.resetAppVars, and it is always defined, but defaulted to false.
Or by creating an attribute on your object and creating an explicit set method:
(at the top of your cfc)
<cfset this.resetAppVars = false />
<cffunction name="setResetAppVars" access="public" returnType="void" output="false">
<cfargument name="flagValue" type="boolean" required="true" />
<cfset this.resetAppVars = arguments.flagValue />
</cffunction>
In which case you will check against this.resetAppVars. You can also scope this locally using <cfset var resetAppVars = false /> as the declaration, which makes it a private attribute of your object, and is probably proper, so code that invokes the object cannot improperly overwrite this variable with a non-boolean type. In that case, you would simply refer directly to resetAppvars in your test, instead of using this scope.
You could also do this:
<cfif NOT isSoapRequest()>...
and stick your remaining logic inside that chunk.
Related
I am new to Mura and have a lot of existing code that I am trying to utilize. I have a globalFunction.cfc file that has a lot of functions that I need to have access to for the existing code. Previously I always extended my application.cfc to the global function so they where always there. With Mura I am not sure where to include it and still keep the installation "upgrade safe".
Any suggestions are appreciated.
In your [site]/includes folder is an Application.cfc. I believe that is the one you are looking to have extend your globalFunction.cfc. It is update safe.
Lance,
You can just put any functions you're wanting to use throughout your site in your eventHandler or contentRenderer files in your theme's folder. These are update safe, and depending on how you're wanting to use them, you can use one for display and the other for function.
EventHandler Ex:
<!--- PAGE - Default --->
<cffunction name="onPageDefaultBodyRender" output="true" returntype="any">
<cfargument name="$">
<cfif $.getcontentID() neq "00000000000000000000000000000000001">#$.dspInclude('/themes/MYTHEME/display_objects/bodies/dsp_body_default.cfm')#</cfif>
</cffunction>
ContentRenderer Ex:
<cffunction name="removeLinks" returntype="string" access="public">
<cfargument name="str" default="" required="true">
<cfset str=reReplace(str, "<[[:space:]]*[aA].*?>(.*?)<[[:space:]]*/[[:space:]]*a[[:space:]]*>","\1","all") />
<cfreturn trim(str) />
</cffunction>
The EventHandler here just puts out an different body if its on the home page, where the contentRenderer removes any links if i use $.removeLinks(MYURLSTRING).
HTH
I am trying to convert an application I support from application.cfm to application.cfc. I followed Ben Nadel's ColdFusion Application.cfc Tutorial And Application.cfc Reference, but my pages cannot access any of the variables in the APPLICATION scope, without adding APPLICATION. to the variable. As a side note, this application uses 0 cfc's currently.
here is my application.cfc.
<cfcomponent displayname="Application" hint="Handle the application" output="true">
<cfset THIS.Name = "AAS" />
<cfset THIS.ApplicationTimeout = CreateTimeSpan( 0, 60, 0 , 0) />
<cfset THIS.SessionManagement = true />
<cfset THIS.setClientCookies = false />
<cfset THIS.versionNum = '1'>
<cfset THIS.genericUserID = 'o005265'>
<cfset THIS.genericPassword = 'zo005265'>
<cffunction
name="OnApplicationStart"
hint="Fires when the application is first created."
access="public"
output="false"
returntype="boolean">
<cfset APPLICATION.aasURL = 'http://127.0.0.1:8500/aaas'>
<cfset APPLICATION.dsn = 've0_aaas'>
<cfset APPLICATION.aas_system = 'development (studio)'>
<cfreturn true />
</cffunction>
</cfcomponent>
Basically I just copied what was in the application.cfm page, and figured it would work the same. I am guessing that I have to invoke this somewhere? That is the only thing that I can think of. Any help would be great.
--- EDIT ---
I have added the OnRequest and OnApplicationStart methods after #EvikJames answer
<cffunction name="OnApplicationStart" access="public" returntype="boolean" output="false" hint="Fires when the application is first created.">
<!--- Return out. --->
<cfset APPLICATION.aasURL = 'http://127.0.0.1:8500/aaas'>
<cfset APPLICATION.datasource = 've0_aaas'>
<cfset APPLICATION.aas_system = 'development (studio)'>
<cfreturn true />
</cffunction>
<cffunction name="OnRequest" access="public" returntype="void" output="true" hint="Fires after pre page processing is complete.">
<!--- Define arguments. --->
<cfargument name="TargetPage" type="string" required="true">
<!--- Include the requested page. --->
<cfinclude template="#ARGUMENTS.TargetPage#" />
<cfset VARIABLES.dsn = APPLICATION.dsn>
<cfset VARIABLES.aasURL = APPLICATION.aasURL>
<cfset VARIABLES.aas_system = APPLICATION.aas_system>
<!--- Return out. --->
<cfreturn />
</cffunction>
You aren't really trying to use "application" variables (which always need to be scoped). I suspect your old application.cfm page had something like.
<cfapplicatin name="blah"...>
<cfset dsn = 'mydsn'/>
And then you were able to do:
<cfquery datasource="#dsn#">
This approach does not utilze the application scope ... it is merely taking advantage of the fact that your application.cfm always runs no matter what. What it is actually doing is putting variables in the variables scope. Because CF always checks the "variables" scope first you soemthing like #dsn# works - but that is not the same as the application scope.
To mimic this behavior in Application.cfc (as has been suggested) you will need to put your variable in the "onRequest()" function instead of the "onApplicationstart()" function - like so:
<cffunction name="onRequest">
<cfset dsn = 'mydsn'/>
</cffunction>
That is expected. To reference application variables you need to prefix them with application.
In your onApplication start method, do this:
<cfset APPLICATION.datasource = 'MyDSN'>
In your onRequest method, do this:
<cfset VARIABLES.DSN = APPLICATION.datasource>
Then, this query will work:
<cfquery datasource="#dsn#">
// sql
</cfquery>
I should add that when you are fully upgraded, you can remove all of the code above just set the default datasource:
<cfset THIS.datasource = 'MyDSN'>
If the variable is in the application scope, you will always need to identify it that way in your .cfm pages. If you use a variable name without the scope prefix, the application scope is not looked at.
if you want to declare variables in the application.cfc that can be accessed without the application scope in your other pages, simply declare the variables outside of any functions.
<component>
<cfset this.name = "applicationName">
<cfset otherVar = 'something'>
<cfset otherVar2 = 'something else'>
<cffunction name="onApplicationStart>.....</cffunction>
</component>
otherVar and otherVar2 can be called without scope prefix on all .cfm pages.
It sounds like you were not originally using application scoped variables. If the variables were not originally scope with "application." then they were simply in "variables scope" (confusing wording I know) which is accessible by the cfm page hit and others included. That is one big change when moving between application.cfm and application.cfc. The general idea there follows the principle that included CFM files share variables scope and CFC files do not.
If the only change you have to make is changing #dsn# to #appplication.dsn# then just do it and get it over with. There are tools such as WinGrep or even Notepad++ which have find and replace across multiple files.
I'm trying to refactor all of my CFCs to avoid using SESSION and APPLICATION variables (not an easy task).
However, in this application, SESSION variables are used in every database call, since different logged in users may be accessing different databases and schemas:
<cfquery name="qEmployees" datasource="#SESSION.DataSourceName#">
SELECT *
FROM #SESSION.DatabaseSchema#.Employees
</cfquery>
I don't want to go through the trouble of passing these two SESSION variables to every method call that accesses the database. This is especially the case since I don't want to pass DSNs and Schema Names in remote AJAX calls.
What is best practice for doing this - for all Scopes that shouldn't be used in CFCs?
I think that since the datasource truly is variable I'd pass it into every function as an optional parameter and set the default value to a variables scoped dsn attribute. I'd set the variables scoped DSN in the CFC's constructor. That way you only have to pass in the DSN for the AJAX calls.
<cffunction name="doFoo" access="remote"...>
<cfargument name="dsn" type="String" required="false" default="#variables.datasource#" />
</cffunction>
I'd use the session scope of your app to store the users dsn name and use that var to pass to the AJAX call.
You should create an "init" method that will serve as a constructor for your CFC. You can then instantiate the CFCs and store them in a shared scope, most likely the application scope. From here, to use this CFC via AJAX, I typically will create a remote facade. Basically this is another CFC that will directly access the CFC instance in the application scope. It will implement the methods you need to access via Ajax, expose them using access="remote" giving your application access to the access="public" methods from the actual CFC. In this case it is generally accepted that the remote facade can access the application scope directly as part of the design pattern.
A simple example:
example.cfc:
<cfcomponent output="false">
<cffunction name="init" access="public" output="false" returntype="any">
<cfargument name="dsn" type="string" required="true" />
<cfset variables.dsn = arguments.dsn />
<cfreturn this />
</cffunction>
<cffunction name="doStuff" access="public" output="false" returntype="query">
<cfset var q = "" />
<cfquery name="q" datasource="#variables.dsn#">
select stuff from tblStuff
</cfquery>
<cfreturn q />
</cffunction>
</cfcomponent>
In your Application.cfc onApplicationStart() method:
<cfset application.example = createObject("component","example").init(dsn = "somedsn") />
remote.cfc:
<cfcomponent output="false">
<cffunction name="doStuff" access="remote" returntype="query">
<cfreturn application.example.doStuff() />
</cffunction>
</cfcomponent>
Can you set your datasource variables in the onRequest or onRequestStart functions in your Application.cfc
<cffunction name="onSessionStart">
<cfset session.dsn = _users_personal_dsn_ />
</cffunction>
<cffunction name="onRequestStart" >
<cfset dsn = "#session.dsn#" />
</cffunction>
<cfquery name="qEmployees" datasource="#dsn#">
SELECT *
FROM #SESSION.DatabaseSchema#.Employees
</cfquery>
etc.
not sure if that will work [not tested - actually feels a bit sloppy]
-sean
The scope you choose (for any variation of this question, not just for DSNs) should be based on whether the lifetime of the value is the same as the lifetime of the scope.
In our application, the DSN is just set once in the lifetime of the application, so we have an application.config struct that gets created (parsed from a file) in onApplicationStart, and within it is application.config.dsn
If your value really does change between sessions, but not over the life of a session, go ahead and use the session scope.
If your value could change for any given request, but not in the middle of a request, put it in the request scope.
That said, still heed ryan's advice and add optional arguments that only default to this value: being flexible is always the best.
My suggestion for this is to create a base class and then have your components that need database access extend that component. It doesn't have to be in the immediate parent hierarchy but somewhere down the line.
They goal is to do two things, keep the cfc abstracted from the main program and keep it easily configurable. This accomplishes both.
So your CFC that queries the database would look something like this :
<cfcomponent extends="DataAccessBase">
<cffunction name="myFunction" access="public" returntype="string">
<cfquery datasource="#getDSN()#" name="qStuff">select * from table</cfquery>
</cffunction>
The key above is the extends="DataAccessBase" portion. This adds the layer of abstraction where you can control the data access at one configurable point, but it's not tied to the application itself, leaving the component abstracted from where it's implemented.
Your DataAccessBase.cfc could look something like this:
<cfcomponent>
<cffunction name="loadSettings">
<cfparam name="request.settings" default="#structNew()#">
<cfparam name="request.settigns.loaded" default="false">
<cfif request.settings.loaded eq false>
<!--- load settings from resource bundle etc --->
<cfset request.settings.dsn = 'myDSN'>
<cfset request.settings.loaded = true>
</cfif>
</cffunction>
<cffunction name="getDsn" access="public" returntype="string">
<cfset loadSettings()>
<cfreturn request.settings.dsn>
</cffunction>
You can of course get more intricate with how you configure and store the settings etc, but that's out of scope of the question I think. :)
I don't see any reason to pass the DSN with every method call. Yes, it works, but it's not necessary. The components are developed with a built-in assumption of the datastructure so you know that it is not going to change from a addItem() call to a updateItem() call, thus its duplication of work which means additional points of failure. :P
Make sense?
Here is my coldfusion web service. When I use soapUI tool to call 'test', i am receiving this
"<ns1:stackTrace xmlns:ns1="http://xml.apache.org/axis/">org.xml.sax.SAXParseException: Premature end of file."
error.
can anyone help me on this issue? I already seached online, but no luck. any code issue?
do I need to use <cfproperty> tag?
<cfcomponent output="false">
<!--- initialisation --->
<cffunction
name="init"
output="false"
hint="return an initialized object.">
<!--- Return THIS reference. --->
<cfreturn THIS />
</cffunction>
<!--- ping --->
<cffunction
name="test"
access="remote"
returntype="numeric"
output="false"
hint="return an true = 0.">
<!--- declare local variables --->
<cfset var local = 0 />
<!--- Return 0. --->
<cfreturn local />
</cffunction>
</cfcomponent>
Couple questions: are you on CF9? What happens when you invoke it straight up (not through SoapUI, but as a http call in a browser)?
Also, I would strongly recommend against using "Local" as a variable name in a function - CF9 introduced some changes in how variables are scoped within a function and uses that as the name for that constrained scope.
I simply want to define a function in application.cfc and expose it application wide to all requests. Preferably the "assignment" would only happen on application startup.
Is the preferred method to do something along the lines of this:
<CFCOMPONENT OUTPUT="FALSE">
<CFSET this.name = "Website">
<CFSET this.clientManagement = true>
<CFSET this.SessionManagement = true>
<CFFUNCTION NAME="GetProperty" OUTPUT="False">
<CFARGUMENT NAME="Property">
<CFRETURN this.Props[Property]>
</CFFUNCTION>
<CFFUNCTION NAME="OnApplicationStart" OUTPUT="FALSE">
<CFSET Application.GetProperty = GetProperty>
.
.
.
or is there something better?
By default, GetProperty will be visible in Variables scope already, this can be sufficient for many usages (in .cfm templates).
If you want to use these methods directly in the components, referencing them in the Application scope is fine.
Though I do this with Request scope in the onRequestStart(), it's just my personal preference. Something like this:
request.udf = {};
request.udf.halt = halt;
Please note that best practice in general is incapsulating the objects and having them referenced in variables scope of the host object. I typically do this when initializing the object, simply pass previously created objects as init() arguments.
P.S. Nowadays it is recommended to use lower case for tags and their attributes. Kind of good coding practices.
The best way to store site specific config data is probably going to be to create a new component named something such as SiteConfig.cfc with methods such as getProperty(propertyName) and setProperty(propertyName, value). You would then store this CFC in the application scope by doing the following inside Application.cfc's onApplicationStart method like:
<cfset application.siteConfig = createObject("component", "SiteConfig").init() />
Back to your original question though about storing a UDF in the Application scope, below is a way to do that. The basis is that in onApplicationStart you will create a new application persisted struct with your site's config properties like siteName and whatever else. Then a function is stored in a CFM file which is cfincluded only in onApplicationStart, then copied into the application scope. This means that all your regular page CFM files can use application.getProperty(propertyName).
Since the function is only created once and stored in the application scope it satisfies your original question's requirements about "assignment would only happen on application startup".
Hope this helps a bit!
getProperty.function.cfm
<cffunction name="getProperty" output="false">
<cfargument name="propertyName" type="string" required="true" />
<cfreturn application.config[propertyName] />
</cffunction>
Application.cfc
<cffunction name="onApplicationStart" output="false">
<cfset application.config = structNew() />
<cfset application.config.siteName = "My App's Display Name" />
<cfinclude template="getProperty.function.cfm" />
<cfset application.getProperty = variables.getProperty />
</cffunction>
test.cfm
<cfset propertyValue = application.getProperty("siteName") />
<cfdump var="#propertyValue#" />
You might consider creating a seperate "properties" CFC and instanciating it as
a singleton in the SERVER scope then it will be available from any CFML page even
if it isn't part of an application. If you go this route then there is no "server
start" event to bind to. Instead you can put this in the contructor of application.cfc
or in the body of application.cfm
<cfif not structkeyexists(server,"properties")>
<cflock name ="loadProperties"
timeout ="10"
type ="exclusive"
>
<cfif not structkeyexists(server,"properties")>
<cfset server.properties =
createObject("component","path-to-props.cfc")
.init({..inital properties..})
>
</cfif>
</cflock>
</cfif>
The lock code is to prevent the overhead of creating and assigning the UDF on every
request. This also allows the properties instance to persist so that having a
properties.SetProperty() function will work
you might also want to use the technique discussed here