Skip to content

Commit 0a81628

Browse files
committed
improve doc for static functions
1 parent c470ce8 commit 0a81628

File tree

2 files changed

+154
-144
lines changed

2 files changed

+154
-144
lines changed

docs/recipes/static-mocking.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<!--
2+
{
3+
"title": "Mocking Static Functions",
4+
"id": "static-mocking",
5+
"categories": ["testing"],
6+
"description": "How to mock static functions in Lucee for better testability without unnecessary wrappers.",
7+
"keywords": [
8+
"Static Functions",
9+
"Mocking",
10+
"Unit Testing"
11+
]
12+
}
13+
-->
14+
15+
# Mocking Static Functions in Lucee
16+
17+
## Understanding Static Functions in Lucee
18+
19+
In Lucee, static functions belong to the **class** rather than an instance of a component. This means they can be called directly using the class name:
20+
21+
```
22+
// Test.cfc
23+
component {
24+
public static function myStaticFunction() {
25+
return "static";
26+
}
27+
}
28+
```
29+
30+
Calling the static function:
31+
32+
```
33+
dump(Test::myStaticFunction()); // Outputs: "static"
34+
```
35+
36+
Unlike instance functions, static functions:
37+
- Do not require an object instance to be called.
38+
- Are shared across all instances of the component.
39+
- Can be accessed **exactly like instance functions**.
40+
41+
## Accessing Static Functions via Instances
42+
43+
In Lucee, a static function can **also** be accessed through an instance of the component, just like an instance function:
44+
45+
```
46+
testInstance = new Test();
47+
dump(testInstance.myStaticFunction()); // Outputs: "static"
48+
```
49+
50+
## Mocking Static Functions
51+
52+
One key advantage of this behavior is that **static functions can be mocked just like instance functions**.
53+
54+
Example:
55+
56+
```
57+
testInstance = new Test();
58+
// overlay static function with diffrent behaviour
59+
testInstance.myStaticFunction = function() {
60+
return "mockstatic";
61+
};
62+
63+
dump(Test::myStaticFunction()); // Outputs: "static"
64+
dump(testInstance.myStaticFunction()); // Outputs: "mockstatic"
65+
```
66+
67+
### Why This Matters for Testing
68+
- Static functions can be dynamically **mocked per instance** without modifying the component.
69+
- This allows for **cleaner test code** and avoids unnecessary duplication.
70+
71+
## The Benefit of Static Functions
72+
73+
1. **No Difference in Access** – Static functions work exactly like instance functions.
74+
2. **Ease of Mocking** – Static functions can be mocked at the instance level, avoiding unnecessary wrappers.
75+
3. **Consistency** – Static functions ensure a uniform implementation across instances while still allowing for instance-level customization when needed.
76+
4. **Overlay vs. Overwrite** – When an instance function is redefined (mocked), it overwrites the original implementation for that instance. With static methods, defining an instance-level function of the same name overlays the static method for that instance only, while the original static method remains accessible via the class.
77+
78+
## Conclusion
79+
80+
Lucee’s handling of static functions provides a powerful way to structure shared functionality while maintaining flexibility in testing. Instead of introducing redundant instance function wrappers, developers can take advantage of the fact that static functions are accessible like instance functions and can be dynamically mocked per instance. This results in a cleaner, more maintainable codebase.
81+
Lines changed: 73 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
<!--
22
{
3-
"title": "Static scope in components",
3+
"title": "Static Scope in Components",
44
"id": "static-scope-in-components",
55
"categories": [
66
"component",
77
"scopes",
88
"static"
99
],
10-
"description": "Static scope in components is needed to create an instance of cfc and call its method.",
10+
"description": "Understanding the static scope in Lucee components and how it can be used for shared data and functions.",
1111
"keywords": [
1212
"Static scope",
1313
"Components",
@@ -19,197 +19,126 @@
1919
}
2020
-->
2121

22-
# Static scope in components
22+
# Static Scope in Components
2323

24-
Static scope in components is needed to create an instance of cfc and call its method. It is used to avoid creating an instance each time you use the cfc.
24+
## Understanding Static Scope
2525

26-
You can create an object in the Application init() function, and make it at application scope, so you can directly call the methods.
26+
The **static scope** in Lucee components allows variables and functions to be shared across all instances of a component. This avoids the need to create a new instance every time a function is called, improving efficiency and consistency.
2727

28-
We explain this methodology with a simple example below:
28+
Static scope was introduced in Lucee 5.0 and provides a way to store shared state at the component level rather than per instance.
2929

30-
## Example 1
30+
## Defining Static Variables
3131

32-
```luceescript
33-
// index.cfm
34-
directory sort="name" action="list" directory=getDirectoryFromPath(getCurrentTemplatePath()) filter="example*.cfm" name="dir";
35-
loop query=dir {
36-
echo('<a href="#dir.name#">#dir.name#</a><br>');
37-
}
38-
```
39-
40-
1. Create a constructor of the component. It is the instance of the current path and also create new function hey().
41-
42-
```luceescript
43-
// Example0.cfc
44-
Component {
45-
public function init() {
46-
dump("create an instance of " & listLast(getCurrentTemplatePath(),'\/'));
47-
}
48-
public function hey() {
49-
dump("Salve!");
50-
}
51-
}
52-
```
32+
A static variable is shared among all instances of a component and retains its value across multiple calls:
5333

54-
2. Next, we instantiate the component four times, and then call the hey() function. Run this example00.cfm page in the browser. It shows five dumps. Four dumps coming from inside of the constructor and the fifth dump is from hey(). Note that the init() function is private, so you cannot load it from outside the component. Therefore, you have no access to the message within init() from the cfscript in the example below.
55-
56-
```luceescript
57-
// example0.cfm
58-
new Example0();
59-
new Example0();
60-
new Example0();
61-
cfc = new Example0();
62-
cfc.hey();
6334
```
64-
65-
## Example 2
66-
67-
As our code gets more complicated, we need to make some additions to it.
68-
69-
- One option is to create the Component in the application scope or server scope, or to use the function GetComponentMetaData to store components in a more persistent manner.
70-
71-
The static scope for components was introduced in Lucee 5.0. This is a scope that is shared with all instances of the same component.
72-
73-
Here is an example showing the use of static scope:
74-
75-
```luceescript
76-
// Example1.cfc
77-
Component {
35+
component {
7836
static var counter = 0;
37+
7938
public function init() {
8039
static.counter++;
81-
dump("create an instance of " & listLast(getCurrentTemplatePath(),'\/') & " " & static.counter);
40+
dump("Instance created: " & static.counter);
8241
}
42+
8343
public function getCount() {
8444
return static.counter;
8545
}
8646
}
8747
```
8848

89-
Here, the variable `counter` is defined in the static scope. This means that all instances of Example1.cfc share this variable.
90-
91-
2. In the following example, we call the Example1() function three times. Each time, the `counter` variable is incremented and shared across all instances.
49+
When multiple instances of this component are created, the `counter` variable is incremented across all instances:
9250

93-
```luceescript
94-
// example1.cfm
95-
new Example1();
96-
new Example1();
97-
new Example1();
9851
```
99-
100-
## Example 3
101-
102-
1. Another example is using the static scope to store the result of a time-consuming operation that does not need to be recomputed every time.
103-
104-
```luceescript
105-
// Example2.cfc
106-
Component {
107-
static var data = [];
108-
public function init() {
109-
if (arrayLen(static.data) == 0) {
110-
for (i = 1; i <= 100; i++) {
111-
arrayAppend(static.data, i * i);
112-
}
113-
}
114-
dump(static.data);
115-
}
116-
}
52+
new Example();
53+
new Example();
54+
new Example();
11755
```
11856

119-
Here, the array `data` is defined in the static scope, which means it will be computed only once and shared across all instances.
120-
121-
2. In the following example, we call the Example2() function twice. The array `data` is computed only once and reused in the second instance.
57+
Each instance shares the same `counter` value, demonstrating the persistent nature of static variables.
12258

123-
```luceescript
124-
// example2.cfm
125-
new Example2();
126-
new Example2();
127-
```
128-
129-
## Example 4
59+
## Using Static Functions
13060

131-
1. The static scope can also be used for functions. In this example, we define a static function that is available to all instances.
61+
A static function can be called directly on the component itself, without needing an instance:
13262

133-
```luceescript
134-
// Example3.cfc
135-
Component {
63+
```
64+
component {
13665
public static function hello() {
13766
return "Hello, World!";
13867
}
13968
}
14069
```
14170

142-
2. In the following example, we call the static function `hello` without creating an instance of Example3.
71+
Calling a static function without instantiating the component:
14372

144-
```luceescript
145-
// example3.cfm
146-
dump(Example3::hello());
73+
```
74+
dump(Example::hello());
14775
```
14876

149-
## Example 5
77+
## Static Methods vs. Instance Methods
15078

151-
1. The static scope can be used to count the number of instances created from a component.
79+
- **Instance Methods**: Defined without `static` and tied to an object instance.
80+
- **Static Methods**: Defined with `static` and shared across all instances.
15281

153-
```luceescript
154-
// Example4.cfc
155-
Component {
156-
static var counter = 0;
157-
public function init() {
158-
static.counter++;
159-
dump(static.counter & " instances used so far");
82+
Example:
83+
84+
```
85+
component {
86+
public static function staticMethod() {
87+
return "I am static";
88+
}
89+
90+
public function instanceMethod() {
91+
return "I am an instance method";
16092
}
16193
}
16294
```
16395

164-
2. In the following example, we call the Example4() function five times. Each time the function is called, the count of `counter` in the static scope increases.
96+
Usage:
16597

166-
```luceescript
167-
// example4.cfm
168-
new Example4();
169-
new Example4();
170-
new Example4();
171-
new Example4();
172-
new Example4();
17398
```
99+
obj = new Example();
100+
dump(obj.instanceMethod()); // Works only on an instance
174101
175-
## Example 6
102+
dump(Example::staticMethod()); // Works without an instance
103+
```
176104

177-
1. We can also use the static scope to store constant data like HOST, PORT.
105+
## Accessing Static Methods via Instances
178106

179-
- If we store the instance in the variable scope, you will run into problems when you have a thousand components or it gets loaded a thousand times. This is a waste of time and memory storage.
180-
- The static scope means that a variable only exists once and is independent of how many instances you have. So it is more memory efficient to do it that way. You can also do the same for functions.
107+
Lucee allows static methods to be accessed the same way as instance methods:
181108

182-
```luceescript
183-
// Example5.cfc
184-
Component {
185-
static {
186-
static.HOST = "lucee.org";
187-
static.PORT = 8080;
188-
}
189-
public static function splitFullName(required string fullName) {
190-
var arr = listToArray(fullName, " ");
191-
return {'lastname': arr[1], 'firstname': arr[2]};
192-
}
193-
public function init(required string fullName) {
194-
variables.fullname = static.splitFullName(fullName);
195-
}
196-
public string function getLastName() {
197-
return variables.fullname.lastname;
198-
}
199-
}
200109
```
110+
obj = new Example();
111+
dump(obj.staticMethod()); // Outputs: "I am static"
112+
```
113+
114+
This means static methods do not require special handling and can be called via an instance or the class itself.
201115

202-
2. In the following example, we call the Example5() function in two ways. It has a function splitFullName() that does not need to access anything (read or write data from the disks) and a variable scope that doesn't have to be part of the instance. It returns the firstname and lastname.
116+
## Mocking Static Methods
203117

204-
```luceescript
205-
// example5.cfm
206-
person = new Example5("Sobchak Walter");
207-
dump(person.getLastName());
208-
dump(Example5::splitFullName("Quintana Jesus"));
118+
A key advantage of Lucee’s implementation is that **static methods can be accessed just like instance methods** and **can be mocked per instance**:
119+
120+
```
121+
obj = new Example();
122+
obj.staticMethod = function() {
123+
return "Mocked static method";
124+
};
125+
126+
dump(Example::staticMethod()); // Outputs: "I am static"
127+
dump(obj.staticMethod()); // Outputs: "Mocked static method"
209128
```
210129

211-
## Footnotes
130+
This means:
131+
- Static functions can be dynamically modified per instance without affecting the original class.
132+
- No need for redundant instance wrappers for testing.
133+
134+
## Benefits of Static Scope
135+
136+
1. **Performance Optimization** – Avoids redundant instantiations.
137+
2. **Shared State** – Useful for counters, caching, and global configurations.
138+
3. **Mocking Flexibility** – Allows instance-level modifications for testing while keeping the original static method intact.
139+
4. **Overlay vs. Overwrite** – When an instance function is redefined, it **overwrites** the original implementation for that instance. With static methods, defining an instance-level function of the same name **overlays** the static method for that instance only, while the original static method remains accessible via the class.
140+
141+
## Conclusion
212142

213-
[Lucee 5 features reviewed: static](https://dev.lucee.org/t/lucee-5-features-reviewed-static/433)
143+
Static scope in Lucee enables shared variables and functions across instances, improving efficiency and making testing more flexible. By understanding how to use static variables, functions, and mocking techniques, developers can write cleaner, more maintainable code.
214144

215-
[Video: Lucee Static Scopes in Component Code](https://www.youtube.com/watch?v=B5ILIAbXBzo&feature=youtu.be)

0 commit comments

Comments
 (0)