Blog

17
February 2014

Gavin Pickin

Techie Gotcha - Application CFC inheritance Paths with Mappings - Silly Me

CFML Language, CFML Server, Dependency Injection, Techie Gotchas

There is someone, somewhere, beating their head into their desk and saying to themselves, either very quietly, or yelling it at the top of their lungs, "What the {appropriate bad word} was I thinking, I'm an idiot". It might not be you right now (you're reading this blog post, so its probably not you), it might not be you tomorrow, but we all have those moments, where, something seemingly simple, gets the best of us... because its only simple once you see the light. Hindsight is 20/20, I believe its better to share those moments, so that everyone else can learn from your mistakes, even if it proves you have those moments, and it might prove you have them more of those moments than most people :) 

Today, I have one of those moments to share with you, and today, its about Application.cfc Inheritance Paths with Mappings, and how I finally "clicked" about something I had always wondered about, but hadn't really looked into properly.

If you have not read many of my other blog posts, you won't know that I look after a large number of sites (in comparison to developers who work on a few products or a few projects for their company), over several servers, with a lot of legacy code. We're in the process of migrating from Windows CF8 and CF9 servers to Centos CF9 and Railo, and we're trying to bring our legacy code back into the right century when we work on those projects, including Source Control, using Application.cfc (a lot were built before CFCs even existed) and trying to update to newer Frameworks like FW/1 and or ColdBox, or at least some of their libraries, like TestBox for Unit Testing, DI/1 and Wirebox for Dependency Injection, etc. 

In the process of doing some of these said migrations, or preparation for migrations, I came across an issue that we had noticed several times actually, but with time not being on our side, as life goes, we fixed the symptoms of the issue, but didn't really figure out the base issue, and that was, why our CFCs Inheritance did not work as we expected. As I mentioned earlier, I figured this out, but it wasn't until I really thought about it, did I realize I was just thinking about it all wrong. 

If you want to laugh at me, go ahead, hell, I laughed a little at myself too.

What were the symptoms?

Some of our CFCs would throw errors, almost randomly it seemed, with references to global variables, specifically the issue was usually the Application variables. 

We have all looked at Application Inheritance, so when a file is called… ColdFusion / Railo will traverse up directories until a Application.cfm or cfc is found. 

If you map a directory, like /cms to the root, your code can invoke a cfc or create an instance of a cfc with cms.cfcs.utils using the mapping to path out the cfc. You can pass a reference to the application scope if you're using IOC where you give your CFC everything it needs, or you can reference application.variablename from inside the CFC if you're not using IOC and pulling what variables your CFC needs. Either way, the CFC works, it can see the application.dsn and it can look up other important information from the application scope.

Note: I now know better, that we should pass in the reference, in case we refactor etc, IOC allows us to retain control, but, this is legacy code, not all of it mine, and younger me was not as well informed of better and best practices. 

What's the big deal you're thinking? 

Times have changed, this cool little cool called "AJAX" came about, have you heard about it? Its pretty cool, you should look into it. Usually when people are using Ajax with ColdFusion, it doesn't call index.cfm to route through the framework (which you could do, and is actually a really nice way to resolve these types of issues by the way), you can and do call the CFC directly using a path like this /cms/cfcs/utils.cfc?method=getPageCount and pass in the arguments to the method you're calling. 

This CFC cannot see the application scope? Why not? It sees it most of the time, but why can it not see when its being instantiated by the direct url? Each and every ajax call creates a new object, so any variables you pass into the CFC to set it up would need to be passed each and every time, but wait, we aren't using IOC, this is old school done wrong, pull my variables approach, so this doesn't apply. Why can't I see the Application scope? The CFC can normally see the CFC, whats different with Ajax?

Pretty obvious right?
Maybe, maybe not? 

When any of my pages call that CFC, those pages, hit an index.cfm… in /websites/www.mydomain.com/index.cfm

When ColdFusion looks for Application.cfm or Application.cfc it looks in /websites/www.mydomain.com/
and finds /websites/www.mydomain.com/Application.cfc (if we upgraded them over)
or /websites/www.mydomain.com/application.cfm (if the legacy is still cooking).

So, every CFC call made from inside that page load, which starts with index.cfm has already loaded the Application.cfm/cfc based on the entry point, the index.cfm. If we have a /admin or /login /secretentrance it too has an Application.cfc and index.cfm so the entry point is almost always the index.cfm so it has no problem finding the Application file in the path, no need to even traverse.

Now, say we load another page, /includes/loadfile.cfm which we use to pull content from outside of the web root (because you know you shouldn't put uploaded files in the web root - although, I bet there are loads of people still doing that, I know our legacy does, even though our Application.cfc and Application.cfm stop requests from files not in our whitelist), so this file is websites/www.mydomain.com/includes/loadfile.cfm. ColdFusion goes up a directory, and ta-da, there is the Application file, it works, no problem.

So why does our /cms/cfcs/utils.cfc file not see the application?

Its full path is /websites/_includes/cms/cfcs/utils.cfc
Now do you see it?

There is no Application.cfc in /websites/_includes/cms/cfcs/
There is no Application.cfc in /websites/_includes/cms/
There is no Application.cfc in /websites/_includes/
There is no Application.cfc in /websites/
There is no Application.cfc in /

Duh, of course not. 

For some reason, the entry point being index.cfm tricked me into thinking that calling the CFC from inside the request, and outside the request was basically the same thing. It can be, but not when you have a virtual directory mapped in your web server… because the traversing happens on the entry point path.

When I call the /cms/cfcs/utils.cfc it doesn't have /websites/www.mydomain.com/Application.cfc in its path.

The Solution?

The first thought was, I'll just add a Mapping in the Application.cfc file.
Umn, the file never sees the Application.cfc, so how can it map itself so it can find the Application.cfc that it needs to find to map itself, you get the idea.

We could try and map the cms at the ColdFusion server level, but obviously, if you have a lot of different sites, with different frameworks, you don't want a lot of Global mappings, I assume it would work, I'll have to experiment with that later and update this. Railo makes it easy, I'll have to test it there too, and see if it works from a Mapping, and traverses the Actual Path and the Mapped path or not.
This is a little HOMEWORK for Me.

How do we solve this problem?

We cannot move the CMS into each and every site, we lose the benefits of Multi-tenant, and we obviously don't want to have to manage all of those code bases.

Luckily, ColdFusion is a really cool language… and we can do some pretty nifty stuff.

I say: Why don't I just throw an Application.cfc into the /cms/cfcs/ folder, or into the /cms/ folder?
You might say: if this is shared by 50 sites, how would it work, doesn't the Application.cfc have to match the main Application for it to work right?
I say: Yes, yes it does?
You say: 
How does that work?

Simple.. 1 line in the /websites/_includes/cms/cfcs/Application.cfc file

<cfinclude template="/Application.cfc">

Wait, didn't we just say ColdFusion couldn't traverse and find the Application.cfc file?
Yes, it couldn't Traverse the Actual Folder Structure, but, if you include /Application.cfc ColdFusion is smart enough to look in the webroot for the actual site, and convert that to the Actual Path, and do its thing.

It is pretty cool. It includes the whole file, like it was a copy… and everything will work.

NOTE - Just be aware, if your Application.cfc file sets any Paths, you will need to test that the path is the actual Webroot, not the path of the Application.cfc that is including the Application.cfc from the root… otherwise, it will obviously cause a lot of havoc when pathing anything else based on those Application variables.

As I mentioned before, not a really difficult issue, when you see the answer, and being my issue, sometimes you can see the trees for the forest, sometimes its best to step back and look at the problem in a different light, and you will see the solution too.

I still cannot believe that it got me, I admit, even I'm a dummy sometimes, well, actually more than I'd like…at least I wasn't the only one to not see it, and its good to finally figure it out, even if its one of those DOH moments.

Hopefully this helps someone else out there that maybe runs into the same problem.

Fair warning

This solution isn't necessarily the best way to do it, as usual with Programming there are several ways to solve a problem, some great ideas, good ideas, and some poor ones too. 

I mentioned you didn't have to hit your cfc directly… you can route all requests through your framework, you can make a proxy cfc for remote access, you can use Application.cfc to catch all requests (assuming you fix your mapping issue - the reason for this article), and a lot of other ways, I will try to revisit this shortly, and offer some other ways to work around this type of issue, check back if you are interested.

Better and Best Practices

For the record, talking about Better and Best Practices, your main cfcs should never be located in a web accessible directory. You can call them from ColdFusion using a ColdFusion Mapping, meaning ColdFusion can see and talk to them, but the browser can't, which will make them inaccessible, therefore more secure. Although, leaving a Simple CFC with only Remote Access Functions that act as a Proxy to your main CFCs hidden behind a mapping is a solid way to do this.

We actually briefly discussed a few better/best practices in this post. I have been trying to "find" or "locate" a series of better / best practices to use as a checklist to ask yourselves as you work on a project, but there are so many around. More homework, create a series on different best practices, and why they are said to be best practices.

Why do I call them Better / Best Practices?

Some of you might wonder why I call them Better / Best Practices, when most people just call them Best Practices. Funnily enough, the great resource known as twitter lead me to a discussion on this... and how some people do not like the term Best Practice, as it deems the practice to be the BEST, and means there is no better way. Our industry changes all the time, and the Best Practices 10 years ago are definitely not the Best Practices now (or we wouldn't have issues with Legacy code), and if you have the Best Practice, you might never look for new or better ways to do things. Better Practices acknowledge they are better ways to do things, than the common or entry level way, but leaves the idea open that its an evolution, there is no right answer, just lots of answers, and you might be able to find a Better Practice tomorrow, that the Better Practice of today. I forget the person who blogged about it, I apologize for not crediting them. Any readers (yeah you in the corner) remember, let me know, and I'd be happy to credit them with the thought.

Thanks for reading, sorry it turned into a really long post. Hope you had a good laugh at my expense, or learned something.

Have a good one,
Gavin

Blog Search