Blog

27
February 2014

Gavin Pickin

Adding ColdBox's WireBox to Legacy Site - Installation 1 2 3 Go

CFML Language, ColdBox, Dependency Injection, WireBox

I'm dealing with a the task of bringing more and more of my legacy code projects into this decade, especially when we're doing upgrade and continuous work, so we can eliminate some of the headaches as we move forward. One of the steps is ensuring we are using our CFC components properly, so today, I am going to install Wirebox into our app, which is easy, but this series, we'll go through the other steps to being able to use Wirebox properly. I use that word like I know what Properly is, I guess we'll find out soon if I do know, or not.

Download and Map ColdBox / WireBox Standalone 

First of all, you do not need to download all of ColdBox.
WireBox is a standalone library, and can be used by itself.

For me, I'm going to grab ColdBox standalone, and store it in a location we can map to it for shared use. We could just get the wirebox bundle, but I want to use TestBox and other pieces, so I'll download the whole Core, then I can just use the pieces I want in the apps I want to use them in. I assume you can go find the ColdBox files and map it. Just remember, you want to map the coldbox directory to /coldbox. If you look in your folder, and you see a coldbox folder in it, you've mapped it 1 directory too high. If you see the "system" folder, you have the right one.

If you want to just get the WireBox standalone, make sure you map WireBox to the Wirebox directory, again, make sure you see the system folder in that folder. The only difference in this article is that mapping to the Injector.

Usually the folder you unzip has coldbox in it, with some license, read me files etc. 

I will probably do a post on this later, to make sure its simple enough for everyone. Check back for this.

Instantiate the WireBox Injector to Manage our Objects

First thing we want to do is create an Injector. This is the base for everything we do with Wirebox. The Injector is what manages our objects, and creates new ones, or passes you a reference to existing ones. Normally, I have a series of code that does this, but the Injector will take care of all of that for me. Later, the Injector can also do some magic on the objects we ask for, this is where Dependency Injection comes in, but first, lets create an injector.

Since you want to use the same injector, throughout your complete application (note, you might want several injectors in large apps, where each injector looks after a module or piece of your app, but this isn't that big, one injector will do nicely). So inside the Application file? we're going to add this code to instantiate our injector in OnApplicationStart()

<cfset application.injector = createObject("component","coldbox.system.ioc.Injector").init()>

or if you are using WireBox Standalone

<cfset application.injector = createObject("component","wirebox.system.ioc.Injector").init()>

Now, when our application starts, we'll have our WireBox Injector. 
That is it, it is installed, and ready to use.

You might ask, is it really that easy? and I say, yes, actually it is.

Note: WireBox can actually hoist itself into the Application itself, with certain settings, but this is just a beginner guide.

So how do we use WireBox? How do we use the Injector?

Instead of this

<cfobject name="ThemeService" component="CFCs.Theme">

 

We do 

<cfset ThemeService = application.injector.getInstance("CFCs.Theme")>

 

Now, what if we wanted to get an Object out of our Application scope, if it has been instantiated already.
We might do something like this

<cfif structKeyExists("application.singletons", "themeService">
    <cfobject name="application.singletons.ThemeService" component="CFCs.Theme">
</cfif>
<cfset ThemeService = application.singletons.ThemeService>

 

Or we can do the following (read the note below)*

<cfset ThemeService = application.injector.getInstance("CFCs.Theme")>

 

* How does WireBox know we want a this CFC in the application scope?

That is a good question, WireBox lets you give it settings 2 ways. 

  1. ?With Annotations written into the CFCs themselves? so each CFC tells Wirebox how to use it.
  2. Or a Central WireBox Configuration file, written in CFML called a Config Binder (I'll use Binder from now on)
  3. I guess there is a 3rd way, a combination, but remember, if you have an annotation, the binder can override that setting.

So lets start with the Annotations.

Open your CFC and in the component declaration do this

component name="Theme" scope="application" {

 

Or the cfcomponent tag do this

<cfcomponent name="Theme" scope="application">

 

Then, when Wirebox gets the Instance, it reads that scope annotation, and places it into the application scope. You can do the same with Aliases, setting the CFC as a Singleton, and almost all of the other Wirebox Configuration

Note: When you set a CFC as a singleton, it defaults to a special scope that lasts as long as the injector does? if you store it in the application, it lasts as long as the application does. You can force the injector to clear the singletons with 

application.injector.clearSingletons(); // assuming the injector is names and scoped this way

 

Using the WireBox Binder Configuration File

Now, the Binder Config method is pretty nice. You can set up all of your cfcs from one location, which makes it easy to see the settings for all of your cfcs, without having to open up 20+ files to see which ones are singletons, which scope are they in, etc. 

Now, one of the best things about Wirebox and using the Binder method, is giving your CFCs Aliases? so you no longer have to use the full path. Any CFC you map (or define) in your Binder, Wirebox automatically creates an alias for you, matching the name of the CFC. Lets have a look.

To use a Binder, we initialize the Injector a little differently.

<cfset application.injector = createObject("component","coldbox.system.ioc.Injector").init("WireboxConfig")>

Or if you are using WireBox standalone

<cfset application.injector = createObject("component","wirebox.system.ioc.Injector").init("WireboxConfig")>

 

This time, we're passing something to the Init? which is the WireboxConfig object I made. Its just a CFC? you can use full or absolute path, and the WireboxConfig.cfc looks something like this.

component extends="coldbox.system.ioc.config.Binder"{

    function configure(){

        map("theme")
            .to("CFCs.theme")
            .asSingleton();
    }
}

 

The component extends the ion.config.binder, that's how it inherits all of the important pieces.
It has 1 function, which is configure. This is how you setup all the mappings, or bindings you want Wirebox to handle.

I have just one right now? I map the alias "THEME" to the CFC located at "CFCs.theme" and I want to treat it asSingleton. I think the DSL or Domain Specific Language the ColdBox team use here is really nice, and readable. Wirebox is based on conventions, so I could do that even easier, and use the following code.

component extends="coldbox.system.ioc.config.Binder"{

    function configure(){

        mapPath("CFCs.theme")
            .asSingleton();
    }
}

 

Now, I told Wirebox, I want to map the CFC at CFCs.theme as a singleton, and Wirebox gives the CFC an Alias, by convention, of Theme.
To use this binder mapped CFC, I just use the following code.

<cfset ThemeService = application.injector.getInstance("Theme")>

 

Wirebox looks through its list of Mapped CFCs, and sees Theme? and says, I got that one, and then uses the settings from the Binder. 
Just because you define how you want each and every CFC to work, it doesn't mean that they are all Instantiated when the Injector is made. They are LAZY LOADED, so they are only created as they are needed. If you want them all Instantiated at once, or certain ones, you can add a setting, .asEagerInit() so the Injector will eagerly Init them before needed.

 

Now, there is a catch? One of those techie gotchas with the Binder.

If you use the code below something unexpected happens

<cfset ThemeService = application.injector.getInstance("CFCs.Theme")>

 

Wirebox looks through its list of CFCs mapped by Alias, and looks for CFCs.Theme? and it cannot find it, so it looks for the full path, and sets it up, but since there is no Mapped settings by Alias, it does not set it up with the Binder Config, it uses annotations, if any, and thats it.

But wait? We did set it up, didn't we? Yes? we did, but, by default, Wirebox maps it with the Alias, and if WireBox sees a full path, unless there is an alias that matches that full path, the binder settings do not apply.

I have another post here that explains how to resolve that issue

Recap

WireBox is installed, setup, and you can now use Annotations and the Binder Config method to set how WireBox wires your objects together. Next I'll give you more information on the full path issue, and we'll dive into Dependency Injection and see how that works.

Thanks for reading, as usual, let me know if I missed anything.

Gavin

Blog Search