Lucee 5 features reviewed: static

In the weeks coming until the final release of Lucee 5, we will look at all features Lucee provides in detail. The first pick is the new static modifier/scope.

What is this good for?

Every instance (object instantiated from a component) has a “this” and a “variables” scope, these scopes represent the instance itself, they are unique to every instance, so if you have two instances of the same component each of them have their own “this” and “variables” scope.

However, what if you would like to share data between them, you need to do it outside the component, for example in the application scope. This is where the static scope comes into the game, the static scope is shared between all instances of the same component. So if you need a place to store data for an instance that is not specific to the particular instance or you want to share information between all instances, then the static scope is what you are looking for.

Static functions

Lucee 5 supports static functions, variables and constructors. We will start by looking at functions. We will use the following example throughout this post. First we will create a component called “Person” which is a component that collects information about a person:

//Person
component {
   public function init(required String firstName, required String lastName) {
     this.firstName=arguments.firstName;
     this.lastName=arguments.lastName;
   }
}

Now, let’s say we want to produce a couple of “person” objects based on a query from a datasource, so we do the following:

function createPeople() {
     local.qry=QueryExecute("select lastName, firstName from people");
     local.people=[];
     loop query="#qry#" {
        people[qry.currentrow] = new Person(firstName, lastName);
     }
     return people;
}

Now where do we store this function? In a template named personUtil.cfm or in a component called PersonUtil.cfc that holds this instance? Surely it would be much better to have this method with the “Person” component so we have everything related to people in one place and if something changes we only need to review one location.

Therefore we create the following:

//Person
component {
   public function init(required String firstName, required String lastName) {
     ...
   }
   public function createPeople() {
       ...
   }
}

But to use this instance function we have to create a instance from “person” for no reason like this:

people=new Person().createPeople();

so this is also not a good choice. So let’s change the example a little bit.

//Person
component {
   public function init(required String firstName, required String lastName) {
     ...
   }
   public static function createPeople() {
       ...
   }
}

So, all we changed in the above was to add “static” to the “createPeople” function which makes the function a static function instead of an instance function, so this function is no longer related to an instance and because of that I don’t need a instance anymore. Instead to call this function you would use the following pattern:

people = Person::createPeople();

As you can see, to call a static function of a component is as easy as specifying the package (component path) and name along with the double colon notation and then the function name you wish to call. If the component was in a subdirectory or multiple sub-directories then you specify the path using the dot notation, as you do when creating an instance of the component. For example:

If the path to your component was:

/myPath/toMy/Component/Person.cfc

then the call to the function would look like:

people = myPath.toMy.Component.Person::createPeople();

This is not the only example where a static function makes sense. As another example every function that is not reading or writing instance specific data is better off being a static function instead of an instance function.

So let’s say we have a function in our person component to cleanup names:

//Person
component {
   public function init(required String firstName, required String lastName) {
     this.firstName=static.cleanUp(arguments.firstName);
     this.lastName=static.cleanUp(arguments.lastName);
   }
   public static function cleanUp(stringValue) {
       return trim(arguments.stringValue);
   }
}

This function is not accessing the “this” or “variables” scopes so it doesn’t need to be an instance function therefore it should be a static function.

Now, I’m sure you are thinking “why should I care, this also works with a instance function” and you would be right, but there are two reasons a static function makes more sense.

First of all everybody that sees this function knows the first time they see it that this function is not related to instance specific data because you cannot access the “this” or “variables” scope inside a static function. This gives you the distinction between a static and instance function.

This also has an impact on performance, a static function only has to exist once, independent of how many instances you have, a instance function has to exist for every single instance.

Static functions therefore allow you to do tasks that are related to the component but not to instances of the component.

So far all the examples have missed an important element that is essential for static functions, static variables, let’s take a look at that next.

Static variables

Static functions of course don’t have access to the “this” or “variables” scopes because they are not related to a specific instance, so how can they store data? This is achieved via a new scope, the static scope.

Like instances have the “this” and “variables” scope, the static context has the static scope. The static scope are variables related to the static representation of the component, which we refer to as the “component representation”.

Please note: Static variables are persistent across calls and live from the time the component is first accessed until either the server is restarted or the code of the component is changed, which causes the components static elements to be reloaded.

Here is an example of this:

//Person
component {
   if(isNull(static.instanceCount)) 
      static.instanceCount=1;
   else
      static.instanceCount++; 

   public static numeric function personInstancesCreated() {
       return isNull(static.instanceCount))?0:static.instanceCount;
   }
}

The function “personInstancesCreated” is returns the number of person instances created and this is stored in the static scope.

So we can store information related to people but not directly related to a specific person. In the example above we set or increment “instanceCount” when the constructor (component body) is executed, so every time an instance is instantiated.

However this means that inside the function we need to check if the static variable “static.instanceCount” exists already, which is a bit messy, so we need a better way to do this and that is where “static constructors” come in.

Static Constructor

The first time you try to access the static scope or an instance is created from a specific component, Lucee instantiates the static context before the instance is generated, meaning Lucee instantiates a single component representation for the static variables and functions of the component that can then be used and which are not included within any instances of the component.

At this time you can also initialize your own variables, this is necessary because you may be accessing the static scope before any instances of the component are created.

Now we can improve the “count” example from before by using a static constructor as follows:

//Person
component {
   static {
       static.instanceCount=0;
   }

   public function init(required String firstName, required String lastName) {
     this.firstName=arguments.firstName;
     this.lastName=arguments.lastName;
     static.instanceCount++; 
   }

   public static numeric function personInstancesCreated() {
       return static.instanceCount;
   }
}

Now we have a static ( the body of static { } ) constructor that is executed before the static scope is accessed. This way we can make sure that “instanceCount” always exists in the static scope.

Do I really need this?

Now you might be asking yourself:

“I’ve been doing CFML for some time and I never needed this feature so do I really need this?”

As an example, this is like getting a larger screen on your computer and after a while you can not understand how you managed to work on a small screen as you did before. The static feature is a new tool for your toolkit that allows you to do things better than you have done in the past.

Also, if you been doing CFML for a long time you have probably already simulated a static scope, maybe without even recognising it. A popular way to do it, is to use the function “getComponentMetadata”. This function allows you to store custom data in the component struct and that then can be used as a static scope. This is a side effect of the language and of course it is limited but it works. Another strategy to simulate a static scope is to store the info in a shared scope like the application scope.

The first time we talked about the static scope was when a ticket was raised because with Railo you could not use getComponentMetadata to simulate a static scope and at this time the discussion was started on how we could have a real static scope in CFML.

So that is “static” in Lucee, we hope you will like it because we do!

Please note that to use the static scope you will need release 5.0.0.45 or newer.

4 Likes