Fusebox Core File Architecture and Principles

Since the introduction of XML in Fusebox 4, the Fusebox core files have essentially become a compiler, translating from a specific XML language to CFML / PHP. The names of the core files in Fusebox 4.x emphasize this:

  • runtime
  • loader
  • transformer
  • parser

The entry file included the runtime, determined if the loader was needed and, if loaded, transformed the XML into an internal representation (the full fuseQ) and then the parser ran through that structure and generated CFML / PHP.

The basic principles still hold true in the Fusebox 5 core files but the implementation has changed dramatically. Fusebox 5 is a full-on "recursive descent compiler" which reads in the source code (XML) and parses it to create a tree of objects that matches the structure of the XML. The core then walks the tree of objects and asks each one to compile itself (to CFML / PHP).

Core File Structure

Whilst these notes apply primarily to the ColdFusion version of Fusebox, the PHP version follows the same structure very closely. Where the PHP version differs significantly, a specific PHP Note has been added.

PHP Note: throughout these notes, files are shown to have .cfc or .cfm extensions (ColdFusion Component or ColdFusion Markup) but in the PHP version, all files have a .php extension.

Construction and Compilation

If you look in most of the fusebox*.cfc files in the core, you will see an init() method which is used to construct a node in the tree from the XML and a compile() method which generates the CFML or PHP code. This structure is most clearly visible in fuseboxAction.cfc (which represents a <fuseaction> in a circuit):

fuseboxAction.init():

  • set variables scope data from arguments (which are taken from the XML)
  • loop over child tags and create a fuseboxVerb for each (see note below)

fuseboxAction.compile():

  • loop over child verb objects and ask each one to compile itself

Note: not all children of a <fuseaction> are fuseboxVerb objects! fuseboxAction.init() uses the fuseboxFactory object to create the actual verb objects. It mostly creates fuseboxVerb objects but <do> and <fuseaction> (as a 'verb' inside a global fuseaction) are represented as fuseboxDoFuseaction objects instead because they have a different compilation process.

PHP Note: the PHP version uses constructors (named the same as the PHP class) where the ColdFusion version uses init(). The fuseboxApplication.php class has an init() method for application specific initialization.

Thread Safety in Code Generation

Because the object tree is created effectively once and stored in application scope (as children of the fuseboxApplication object which is stored in application scope), there are a couple of thread safety issues that have to be worked around. That's what fuseboxWriter.cfc and fuseboxLexiconCompiler.cfc are all about:

fuseboxWriter.cfc

For each request that actually has to create a parsed file, a new fuseboxWriter object is created and all compile() calls use that unique-per-request writer object to get CFML / PHP code written into the parsed file.

fuseboxLexiconCompiler.cfc

Because verbs are implemented as simple .cfm (.php) files, they have to be included by a CFC in order to get their job done. Those verb files set variables (in the fb_ struct) as they process the request (twice - in "start" and "end" mode). In order for those variables to be unique-per-request, a new fuseboxLexiconCompiler object is created for each compile() call on each verb. The factory manages fuseboxLexiconCompiler objects in a pool so that the overhead of CFC construction is avoided.

Lifecycle at Startup - Parsing

When Fusebox is started up (or reloaded), the following rough lifecycle happens:

  • fusebox5 creates fuseboxApplication
    • fuseboxApplication.init() loads fusebox.xml
      • for each declared circuit
        • create fuseboxCircuit
          • fuseboxCircuit.init() loads circuit.xml
            • for each declared fuseaction
              • create fuseactionAction
                • fuseboxAction.init() parses the verbs
                  • for each verb in this fuseaction
                    • create fuseboxVerb (or fuseboxDoFuseaction)
                      • fuseboxVerb.init() parses its children recursively

<prefuseaction> / <postfuseaction> are also treated as <fuseaction>s - they are fuseboxAction objects with fuseboxVerb / fuseboxDoFuseaction objects as children.

<globalfuseactions> are treated as <fuseaction>s (and fusebox.xml is viewed as a circuit for that) with only <do> (deprecated) and <fuseaction> as legal verbs - <preprocess>, <postprocess> and <appinit> are fuseboxAction objects with fuseboxDoFuseaction objects as children.

Lifecycle at Request Time - Compilation

When a compile request comes in, a similar recursive set of calls is made:

  • fusebox5 calls fuseboxApplication.compileRequest()
    • which calls fuseboxApplication.compile()
      • which looks up the circuit and calls fuseboxCircuit.compile()
        • which looks up the fuseaction and calls fuseboxAction.compile()
          • which loops over its children calling compile() on them
            • fuseboxDoFuseaction.compile() recursively calls:
              • either fuseboxApplication.compile() for action="ct.fa"
              • or fuseboxCircuit.compile() for action="fa"
            • fuseboxVerb.compile() creates a fuseboxLexiconCompiler object and:
              • calls fuseboxLexiconCompiler.compile() in "start" mode
              • recursively calls compile() on its verb / do children
              • calls fuseboxLexiconCompiler.compile() in "end" mode

Plugin points and pre/post fuseactions are handled by specific code in fuseboxApplication.compile(), fuseboxCircuit.compile() and fuseboxDoFuseaction.compile().