CFC typing WTF….. ???

Okay – a bit of a rant… and this has probably been shown before, but this definatley threw me when I saw it..

1st off. I hate having to use a CF mapping when using CFCs. Totally defeats the purpose of portable/reusable code in my mind.

I've been playing alot with trying to keep my code non-mapping required. It's been taking it's toll, but I'm sorta-kinda getting there.

So with that in mind, I started a recent project with all my CFCs in one directory, so they could see each other, with the idea of refactoring into packages last, once I was happy with how it was working.

So I start refactoring, and I run into this fantastic doozie.

Example is:

Directory structure
/webapp/index.cfm
/webapp/Test.cfc
/webapp/com/dog.cfc

Code
— dog.cfc —
<cfcomponent name="dog">
</cfcomponent>
—————–

— Test.cfc —
<cfcomponent name="Test">

<cffunction name="init" hint="Constructor" access="public" returntype="Test" output="false">
<cfscript>
var dog = createObject("component", "com.Dog");
setDog(dog);

return this;
</cfscript>
</cffunction>

<cffunction name="setdog" access="public" returntype="void" output="false">
<cfargument name="dog" type="com.dog" required="true">
<cfset instance.dog = arguments.dog />
</cffunction>

</cfcomponent>
———————-

 

— index.cfm —
<cfscript>
createObject("component", "Test").init();
</cfscript>

——————–

Problem
So, basically, what happens is: You create a new 'Test', it creats a new 'com.Dog', and then when you go to set it via the setDog(), it tells you:

The argument DOG passed to function setdog() is not of type com.dog.

WTF? Your telling me, that you can work out in relation to the Test.cfc enough information to create a 'com.Dog', but you can't work out the same thing for typing on a cfargument???? Why bother giving us the one, if you can't give us the other?

I just wish that Coldfusion had a way that was either designated, or programtically determ a single root directory for all your CFCs to reside. Much like a J2EE app has /WEB-INF/classes for .class files to reside. That way you COULD have packages much like you can in Java, and not have to worry about portability issues with cfmappings.
(I also wish for the ability to jar up a series of CFCs and import them as a library, again like J2EE… but I think that is asking too much)

As it stands, I think I will experiment with having all my CFCs in one directory, and see what issues arise. It's not an ideal soultion by a long stretch, but it allows me to develop without mappings for now.

— end rant

Leave a Comment

Comments

  • Ryan Guill | March 31, 2005

    Why would you need a type="com.dog". I may be wrong, but the type i think is the name of the cfc without the mapping. It should just be type="dog"

  • Mark | March 31, 2005

    Dog.cfc is in the /com/ directory up from the Test.cfn

    If it was just ‘dog’ then it would expect it to be in the same directory, which it is not.

    If you can show me code that does what you are describing without an error, I’m all ears, but I tried your idea and got: ‘The argument DOG passed to function setdog() is not of type dog. ‘

  • Ryan Guill | March 31, 2005

    what happens if you set it to type="Any"?

  • Jared Rypka-Hauer | March 31, 2005

    I think your problem might be the lack of an init method in your dog.cfc! :)

    It’s not returning anything, which means that your argument’s type="com.dog" isn’t resolving because you’re effectively asking it to resolve "" = "com.dog" to true.

    If you put an init method in your dog.cfc, and have that method do nothing but cfreturn this you’ll more than likely find that it works. The returntype of either the init method or the pseudoconstructor (VERY bad idea) must match the type="" of the argument in question.

    So, use myArgument = createObject("component","com.dog").init() and cfreturn this in your dog’s init method… it should fix you up…

    Laterz!

    J

  • Mark | March 31, 2005

    Ryan – yep, that IS one option. But I like my data typing.

    Jared – Tried it, doesn’t work. (Didn’t think it would either).

    Your idea makes sense, but if it it was ultimately true, I wouldn’t be able to go from the root and have
    <cfargument name="dog" type="webapp.com.dog" required="true">
    and have that work. Thanks for trying tho! :o)

  • Jared Rypka-Hauer | March 31, 2005

    Umm…

    I’m not sure what to say, but this is something I’ve done dozens of times… so, it’s not a question that it works.

    It works.

    I’m not sure what the problem is, but, if you wanted to try something else email me and I’ll send you a zip of Clarion, which uses this exact process for typing.

    Thanks,
    J

  • Mark | March 31, 2005

    Jared, I’m emailing you my test bed now, I hope you can work out that I’m wrong, but I think you will find that I am not.

    As stated in my email –
    the only way it could possibly work, is if the this was done from root level in the application, which is why I specified it be tested from within a /webapp/index.cfm context.

    If anyone else wants the testbed to play with, comment here with your email, and I will email it to you.

  • Mike Rankin | March 31, 2005

    This works for me with a few changes.

    — dog.cfc —
    I added an init function with a returntype of "dog". It’s only content is to return this.
    —————–

    — Test.cfc —
    The init function quailifies the component with "myapp.com.dog" and calls its init function.

    The set dog function has the arguments type changed to myApp.com.dog
    —————–

    I’m not exactly sure what is going on under the hood here, but it seems like you can call the component using several path strategies, but as far as defining the data type, a relative path from the current directory won’t work. It either has to be in the same package that it’s being called from or the full package path has to be used in the data type.

  • Mark | March 31, 2005

    Mike –

    This is why I’m like ‘WTF’. Because you can CREATE a CFC using the relative path, but you can’t use a TYPE attribute on a cfargument from the same cfc using the same value.

    Using a mapping or defining the type attribute from root WORKS, but that really hinders code portability… and hence my original problem.

  • dave ross | March 31, 2005

    Jared- Mark is right… this is a known limitation of relative paths and CFCs. It really boils down to type="", returntype="", and extends="" being "compile-time" attributes. I guess the internal context that a compiled, instantiated CFC lives in cannot resolve relative CFC paths when checking types.

    Mark- I agree with you about the needed packaging/deployment options for CFCs. I couldn’t believe that MX7 contained NOTHING in terms of CFC enhancements. I pray for another redsky…

  • Jared Rypka-Hauer | March 31, 2005

    Dave, and all…

    Once I got into working wiht it, I realized exactly what it was that Mark was trying to accomplish… and at that point I realized that he was *right* insofar as trying to use a relative path as a type versus using a relative path as a pointer to a CFC file for createObject or cfobject.

    Once I realized what was being attemped I had my own WTF?? moment, because I don’t see the issue with using a fully-qualified CFC name as a type. I don’t see the impact on portability or development… my WTF moment was more like "WTF is the difference." ;)

    Then again, I’ve probably got an unduly simplistic view of the system… keeping a set of files in a static directory structure isn’t something that I’ve had a problem with, especially since I tend to keep my CFC libraries outside my application folders and instantiate them via fully qualified path…

    Anyway, I have to think about this a while… I’m not sure that adding the relative path to the mix would work out that well. Performace takes a hit via the time taken to analyze the search path. Strongly structured/well-typed designs would take a hit by relaxing the rules.

    Then again, maybe it’s not a bad idea… I’m contemplating it. :)

    Laterz!

    J

  • simeon | March 31, 2005

    I ran into this when I did my dao implementation of ray’s blog.cfc. I saw it as bump in the road that I did not care for but that was not such a big deal.

    However one of the reasons to go the OO aproach is to be able to ensure code reuse, and with the full path required in all types and returns, that makes it more difficult to do without a mapping.

    Just me experience, I would love to have someone show me the light. :)

  • Joe Rinehart | March 31, 2005

    One of the things I’ve done about this is to create a /com mapping, then store all CFCs underneath this (com.mySuperApp.data, com.webapp.dog, etc). Works sometimes, trips me up others.

    Another thing I’ve seen done is to set something like application.componentPath = "webapp.", and then defer component instantiation to a loader that does the equiv of createObject("component", "#componentpath#.webapp.dog).

  • Mike Rankin | March 31, 2005

    Simeon – I’ve always thought that the goal of code reuse regarding components was to be able to call the exact same component, not a copy of the component. In that sense, using the full path makes sense since it allows you to use components across applications.

    Joe – I think in the large scheme of things, the /com mapping is probably the best approach for environments where the applications make up the "digital nervous system" of the business using them. These environments are always in a state of modification and many times share a centralized database.

    I used to use the #componentpath# approach you mention, but for the life of me, I can’t remember why I stopped.

  • Greg | April 1, 2005

    "I just wish that Coldfusion had a way that was either designated, or programtically determ a single root directory for all your CFCs to reside. Much like a J2EE app has /WEB-INF/classes for .class files to reside."

    This is easy enough to accomplish. Pick a folder (I use c:CFusionMX7components) and add it to your custom tag path. A file that resides under that folder as compackageMyComponent.cfc can them be referenced from anywhere as com.package.MyComponent.

  • Sean Corfield | April 1, 2005

    I think it would clarify things to point out that createObject() is allowing you to use a relative path to create the CFC (effectively ./com/Dog.cfc) whereas cfargument is requiring the actual path for the type (effectively /webapp/com/Dog.cfc). I think that’s a bug in createObject() – it should also force you to use webapp.com.Dog instead of allowing the relative shortcut.

  • Mark | April 1, 2005

    To those who would solve this problem using a cfmapping or using ‘from root’ definitions –

    This is pretty much the best answer in terms of CF – but quite frankly, from a maintenence and reusibility standpoint it’s the absolute worst.

    So many possible issues arrise –

    — Mappings —
    1) What if the mapping I used on dev isn’t available on my shared host? I have to change ALL my cfcs that use that mapping for extends, cfargument and return statements.
    2) What if I’ve used this code on one project, and want to use it on another, but can’t use the same mapping name (dif server, same server, want own code base etc) – then I have I have to change ALL my cfcs that use that mapping for extends, cfargument and return statements.
    3) What if on a new project, I can’t get a mapping setup. now I have to change ALL my cfcs that use that mapping for extends, cfargument return statements, AND createObject calls.

    — From Root —
    1) Classic example – locally you develop your app from a directory on your local server. Production server is actually from root. You now have to change ALL my cfcs that use that mapping for extends, cfargument and return statements before you more to production.
    2) New project (particularly on an corprate intranet), you want to reuse code, when multiple apps are on the same server, there is *no way* you can get the same directory structure as where you developed the original code. I now have to change ALL my cfcs that use that mapping for extends, cfargument and return statements.

    I can think of so many more instances in which this becomes a maintenence nightmare, and makes it rediculously hard to write code libraries that you can easily drop into any project.

    We all deal with it, because we have no choice. But it’s very far from the ideal solution.

  • Elliot Russo | April 4, 2005

    I use virtual directories in IIS exclusivley to create these paths to the cfc root. I make them ‘unreadable’ in IIS but cf still manages to resolve them so it works well for me. I never rely on mappings and dislike sample code that requires me to make them. It also allows me to make it work on xp pro where I’m only able to run my apps as virtual directories themselves and on full IIS. In addition i can run multiple sites with different versions (builds) that have the same component library structure all by using virt dirs witout touching the cf admin or the jrun-web.xml file

  • _Acker | April 17, 2009

    Yo, I know this post is from like 2005, but even today 2009, I’m still struggling with this typing issue.

    Anyway, I wanted to find all the posts about this typing issue and report that I found a solution I hope that people will review and give feedback on.

    TO FIX YOUR TYPING ISSUES : I found that you can RESET/OVERRIDE a cfc name as so …. getMetaData( dog ).name = ‘dog’ … which removes the dot notation

    I find this is an override and really want to know other peoples thoughts… I want to retain typing my cfc’s but also retain portabiltity

  • Jared Rypka-Hauer | April 17, 2009

    _Acker: Just keep in mind that metadata is server-wide so if you change the meta.name key for one CFC instance you change it for all CFC instances for the life of the server. It resets on reboot, so you have to do it again. It will work, but this issue caught me out once and I wanted to make sure you were aware of it. The metadata cache is created once, then cached across the server. In doing what you’re doing you may actually be causing yourself other problems in the long run.

    Just FYI.

  • _Acker | April 21, 2009

    Jared Rypka-Hauer,

    Their is an additional getMetaData() attribute that points at the CFC … getMetaData().fullname

    This seems to be helping me always get the original cfc path but I have never used it before, but no errors thus far