Wednesday, 17 December 2014

Fixing CFScript: CFLDAP

G'day:
This is the first in a series of brief articles I'm going to write to demonstrate how Adobe (and, for that matter, Railo) could have implemented functionality that was formerly the preserve of tags in CFScript. The first (mostly random) example is <cfldap>. I have picked <cfldap> because someone mentioned it on IRC the other day. And I hated the advice I had to give them by way of implementing their needs in CFScript.

The problem CFML developers are faced with is that both Adobe and Railo are eroding the quality of the CFML language by implemented cross-cutting "generic" solutions for shortfalls in CFScript functionality, rather than addressing each piece of functionality professionally and thoughtfully.

Railo have implemented a generic solution which the CFML Advisory Committee came up with, years ago. The basic model is this:



For a given tag, <cfsomething attr1="value1" attr2="value2">:
  1. remove the <cf
  2. and the >
  3. call it as a pseudo function sort of thing, eg: something(attr1="value1", attr2="value2");
  4. if the tag has a body, eg: <cfsavecontent> then just use curly braces.
So we get:

savecontent(variable="theNameOfTheVariableToHoldTheContentAsAString") {
    // statements
}

This is a shit approach. It's just writing tags in script, which is completely missing the point. The object of the exercise was never to implement tags in a script-friendly way. It was to provide the functionality that was previously only available in tags in a script-friendly way. Writing tags in script is not "script-friendly". It's "script-horrific". Whoever came up with it on the CFML Advisory should be pilloried. And then Railo should also be pilloried for adopting it. Because it's shit. And they are all clever enough to know shit when they taste it, but they still went ahead and did it.

The Adobe ColdFusion Team are even worse, if that's possible. They still didn't "get" the object of the exercise, instead falling back to the "tags as script" idea, but they ignored their own advisory and the precedent already set in CFML, instead doing it this way:

  1. remove just the <
  2. and the >
  3. call it as a pseudo function sort of thing, eg: cfsomething(attr1="value1", attr2="value2"), but defintely don't implement it as an actual function;
  4. if the tag has a body, eg: <cfsavecontent> then just use curly braces.
So we get:

cfsavecontent(variable="theNameOfTheVariableToHoldTheContentAsAString") {
    // statements
}

(bad example, I know, as savecontent{} predates the generic syntax. Which makes this worse, as it means ColdFusion has a mishmash of both the Advisory's syntax and their own utter shit).

I'm sorry, Adobe, but for doing that... for ignoring the advisory and ignoring Railo's existing work, you are nothing short of [imagine the absolutely worst swear word in your language here]. Even if there wasn't the existing guideline and precedent, this just completely bites as a language design choice. You're morons. Not all of you. Just the fuckwit who sanctioned this approach. But you,, whoever you are, are a fucking moron.

OK, so that's the "you catch more flies with honey than you do with vinegar" notion completely out the window. Let's talk about fixing this.

<cfldap> is the first candidate.


LDAP querying functionality has been implemented for CFScript in two (substandard) ways. Firstly, ColdFusion 9 provided ldap.cfc along with the rest of its CFC-based solutions to CFScript functionality. If I thought the generic syntax I describe above is bad, this is an even worse approach. It's completely at odds with the rest of the language, and is shockingly poorly written to boot (look at the code if you don't believe me). Secondly... this new generic syntax.

Screw that.

The first pitfall of the generic syntax was demonstrated above in my savecontent{} example:

cfsavecontent variable="theNameOfTheVariableToHoldTheContentAsAString" {
    // statements
}

That's not how anyone writes this sort of code. It should - at the very least - be this:

theActualVariable = cfsavecontent {
    // statements
}

One doesn't pass an attribute into a piece of functionality and have a variable set with that name. What is this: 1996? No. CFScript code (and code in any other bloody language) works as above: the result of some functionality (right hand side) being assigned to a variable (left hand side).

savecontent{} has a slight mitigating circumstance here as it needs the block level construct (although this is also a poor approach, see "How about this for savecontent?"), but LDAP calls don't have this consideration. It's purely a matter of calling some functionality and assigning the results to a variable.

CFLDAP does have over 20 attributes, which might seem a challenge here. But it needn't be if we group them sensibly:

result = queryLdap(actionParams, connectionParams, queryParams);

Three arguments are OK, I think. Each of those param arguments would be structs, which include the one or more of the following attributes of <cfldap>:

actionParams:

  • action = "action"
  • modifyType = "replace|add|delete"


connectionParams:

  • server = "server name"
  • password = "password"
  • port = "port number"
  • rebind = "yes|no"
  • referral = "number of allowed hops"
  • secure = "multifield security string"
  • timeout = "milliseconds"
  • username = "user name"
  • clientcert = "path to client certificate"
  • clientcertpassword = "password for the client certificate"
  • usetls = "true|false"


queryParams:

  • attributes = "attribute, attribute"
  • delimiter = "delimiter character"
  • dn = "distinguished name"
  • filter = "filter"
  • maxRows = "number"
  • returnAsBinary = "column name, column name"
  • scope = "scope"
  • separator = "separator character"
  • sort = "attribute[, attribute]..."
  • sortControl = "nocase|desc|asc"
  • start = "distinguished name"
  • startRow = "row number"

(those're just copied from the docs).

It'd actually be quite nice to be able to define an LDAP connection like one can a datasource. This stuff generally doesn't need to be defined in the code every time one needs to make a call, after all.

This makes the LDAP-querying functionality easy, predictable, and in-keeping with CFScript's existing corpus. And this should be a primary language design consideration when implementing anything: does it fit with the rest of the language? This is something neither the Railo nor the Adobe language designers (I am certain neither organisation as anyone in that role, actually) seem to consider.

What do you think? I have fallen short of actually raising a ticket for this yet, but I will shortly... (RAILO-3286, ColdFusion: 3911232)

--
Adam