Introducing ColdBox 7: Revolutionizing Web Development with Cutting-Edge Features and Unparalleled Performance
We are thrilled to announce the highly anticipated release of ColdBox 7, the latest version of the acclaimed web development HMVC framework for ColdFusion (CFML). ColdBox 7 introduces groundbreaking features and advancements, elevating the development experience to new heights and empowering developers to create exceptional web applications and APIs.
Designed to meet the evolving needs of modern web development, ColdBox 7 boasts a range of powerful features that streamline the development process and enhance productivity. With its robust HMVC architecture and developer-friendly tools, ColdBox 7 enables developers to deliver high-performance, scalable, and maintainable web applications and APIs with ease.
Key Features of ColdBox 7:
-
ColdBox HMVC: The Hierarchical Model-View-Controller (HMVC) architecture allows developers to build complex applications by organizing modules into a structured hierarchy. This promotes code reusability, enhances modularity, and facilitates collaboration among developers.
-
ColdBox CLI: ColdBox 7 seamlessly integrates with CommandBox, a powerful command-line interface and package manager for ColdFusion. This integration simplifies dependency management, streamlines application setup, and enhances the overall development workflow with code generation.
-
WireBox Enhancements: WireBox has been completely streamlined and every line of code profiled to squeeze the last bit of performance for object creation and wiring. It also introduce powerful new object oriented concepts such as Object Delegation, Lazy Properties and Property Observers.
-
Modular Injectors: ColdBox 7 introduces an experimental feature were each module can have its own hierarchy of WireBox injectors. This allows for the encapsulation of dependencies within a module, which will allow for multi-versioned modules to co-exist within a ColdBox application.
-
Scheduled Tasks: ColdBox 7 introduces even more options when declaring and runing scheduled tasks in a portable and human way. It also lays the foundation in its API for CBTasks HQ, a module to visualize and manage scheduled tasks in a cluster.
Developers can access comprehensive documentation, tutorials, and sample applications to jumpstart their development process and leverage the full potential of the framework: https://coldbox.ortusbooks.com/
In this blog post we will review the major updates of this release, but for the full what's new, please refer to our what's new guide: https://coldbox.ortusbooks.com/intro/release-history/whats-new-with-7.0.0
Installing ColdBox, WireBox, CacheBox or LogBox
You can leverage CommandBox to install any of our frameworks via the CLI:
install coldbox # If you are using standalone libraries, then install those install wirebox install cachebox install logbox
Updating ColdBox, WireBox, CacheBox, LogBox
update coldbox # If you are using standalone libraries, then update those update wirebox update cachebox update logbox
LTS Support
For all ColdBox releases, updates are provided for 12 months and security fixes are provided for 2 years after the next major release.
Version | Release | Updates | Security Fixes |
---|---|---|---|
6.x | 2022 | 2023 | 2025 |
7.x | 2023 | 2024 | 2026 |
8.x | 2024 | 2025 | 2027 |
9.x | 2025 | 2026 | 2028 |
Engine Support
![](jpg/coldbox-engine-support.jpg)
This release drops support for Adobe 2016 and adds support for Adobe 2023 and Lucee 6 (Beta). Please note that there are still issues with Adobe 2023 as it is still in Beta.
ColdBox CLI
We now have an official CLI for ColdBox, which lives outside CommandBox. It will always be included with CommandBox, but it now has its own life cycles, and it will support each major version of ColdBox as well.
install coldbox-cli coldbox --help
The new CLI has all the previous goodness but now also v7 support and many other great features like migration creation, API testing, and more. You can find the source for the CLI here: https://github.com/coldbox/coldbox-cli
VSCode ColdBox Extension
![](png/image%20(4).png)
We have updated our VSCode ColdBox extension now to support the latest version of snippets and skeletons for code generation.
Application Templates
All the application templates have been updated in our ColdBox Templates Org: https://github.com/coldbox-templates. They have been updated to support our LTS strategy, so now we can have templates based on each major iteration of ColdBox.
Here is a listing of the latest supported templates:
Template | Slug | Description |
---|---|---|
Default | default | The default ColdBox application template |
Elixir | elixir | The default template with ColdBox elixir support for asset pipelines |
Modern (experimental) | modern | A fresh new approach to ColdBox applications that are non-root based. Still experimental |
Rest | rest | A base REST API using ColdBox |
Rest HMVC | rest-hmvc | An HMVC REST API using modules |
Super Simple | supersimple | Barebones conventions baby! |
WireBox Updates
__ ___ ____
\ \ / (_) | _ \
\ \ /\ / / _ _ __ ___| |_) | _____ __
\ \/ \/ / | | '__/ _ \ _ < / _ \ \/ /
\ /\ / | | | | __/ |_) | (_) > <
\/ \/ |_|_| \___|____/ \___/_/\_\
WireBox has gotten tons of love in this release, with several additions, bug fixes, and improvements.
Transient Request Cache
![](png/wirebox-transient-cache.png)
This feature is one of the most impactful for applications that leverage DI on transient objects, especially ORM-related applications. WireBox will now, by default, cache the signatures of the injections and delegations for you, so they are only done once per instance type. This addition has brought speed improvements of over 585% in Lucee and Adobe ColdFusion. You read that right, 585% performance increases. This is really a game changer for ORM-heavy applications that use DI and delegations. Go try it; you don't have to do a thing. Install and run it!
If this is not for you or there are issues in your system because of it, we have a setting for it to turn it off. Open the WireBox.cfc
binder and add it as a config item.
// Config DSL wirebox : { transientInjectionCache : false } // Binder Call binder.transientInjectionCache( false )
You can also disable the cache on a per-CFC basis by using the transientCache=false
annotation in your component declaration:
component transientCache=false{ }
WireBox Delegators
WireBox supports the concept of object delegation in a simple, expressive DSL. You can now add a delegate
annotation to injections or use the delegates
annotations to components to inject and absorb the object's methods into yourself.
// Inject and use as a delegate property name="memory" inject delegate // Delegate Component component name="computer" delegates="Memory"{ }
In object-oriented programming, an object delegator is a programming technique where an object delegates some of its responsibilities to another object. The delegating object passes the responsibility for handling a particular task to the delegate object. This allows the delegating object to focus on its core responsibilities while the delegate object handles the delegated task.
Basically, a way to inject/proxy calls from one object to the other and avoid the overuse of inheritance, and avoid runtime mixins. WireBox provides a set of rules for method lookup and dispatching that will allow you to provide delegation easily in your CFML applications. This feature is similar to traits in PHP or object delegators in Kotlin.
You can use it to encapsulate behavior on small, focused, and testable classes that can be brought in as traits into ANY component without abusing inheritance. In contrast, object delegation is a more flexible approach that allows objects to delegate tasks to any other object, regardless of its class hierarchy. Finally, object delegation can help to improve code performance by allowing objects to use specialized delegate objects for specific tasks.
![](png/image%20(3).png)
Let's look at an example of how we would use delegation without this feature:
component name="Memory"{ function init(){ return reset() } function reset(){ variables.data = [] return this; } function read( index ){ return variables.data[ arguments.index ] } function write( data ){ variables.data.append( arguments.data ) } }
Now let's look at the computer
component name="computer"{ // Inject a memory object via WireBox property name="memory" inject; // read delegator proxy method function read( index ){ return variables.memory.read( argumentCollection = arguments ) } // write delegator proxy method function write( data ){ return variables.memory.read( argumentCollection = arguments ) } }
As you can see, in the traditional approach we must type and inject and know every detail of the delegated methods. Now let's delegalize it via WireBox:
component name="computer"{ // Inject and use as a delegate property name="memory" inject delegate } computer = getInstance( "Computer" ) computer.read( index ) computer.write( data )
Or use the shorthand notation via the delegates
annotation of components:
component name="computer" delegates="Memory"{ // code } computer = getInstance( "Computer" ) computer.read( index ) computer.write( data )
You can also do prefixes, suffixes, method includes, excludes, and even add as many delegates as you want:
component name="computer" delegates=">Memory, <Disk=read,sleep" } component name="computer"{ property name="authorizable." inject="provider:Authorizable@cbsecurity" delegate; }
Read more about delegates here: https://wirebox.ortusbooks.com/usage/wirebox-delegators
Core Delegates
Now that we have seen what delegators are, WireBox offers core delegators to your application via the @coreDelegates
namespace
- Async - This delegate is useful to interact with the AsyncManager and the most used functionality for asynchronous programming
- DateTime - Leverage the date time helper
- Env - Talk to environment variables
- Flow - Several fluent flow methods
- JsonUtil - JSON utilities
- StringUtil - String utilities
- Population - Population utilities
So let's say you have a service that needs to populate objects and work with the system environment:
component delegates="population@coreDelegates, Env@coreDelegates"{ }
WireBox Property Observers
WireBox supports the concepts of component property observers. Meaning that you can define a function that will be called for you when the setter
for that property has been called and thus observe the property changes.
You will accomplish this by tagging a property with an annotation called observed
then by convention, it will look for a function called: {propertyName}Observer
by convention. This function will receive three arguments:
newValue
: The value is set into the propertyoldValue
: The old value of the property, including nullproperty
: The name of the property
component{ property name="data" observed; /** * Observer for data changes. Anytime data is set, it will be called * * @new The new value * @old The old value * @property The name of the property observed */ function dataObserver( newValue, oldValue, property ){ // Execute after data is set } }
WireBox Lazy Properties
WireBox supports the concept of marking properties in your components as lazy
. This will allow the property to be constructed ONCE when requested ONLY (lazy loaded). This way, you can take advantage of the construction of the property being lazy-loaded.
Internally, we will generate a getter method for you that will make sure to construct your property via a builder function you will provide, lock the request (by default), store it in the variables
scope, and return it to you.
Note: With lazy properties, you must use the getter only to retrieve the property
component{ // Lazy property: Constructed by convention via the buildUtil() method property name="util" lazy; /** * Build a util object lazyily. * The first time you call it, it will lock, build it, and store it by convention as 'variables.util' */ function buildUtil(){ return new coldbox.system.core.util.Util(); } }
Read more about Lazy Properties here: https://wirebox.ortusbooks.com/advanced-topics/lazy-properties
onInjectorMissingDependency
event
A new event called onInjectorMissingDependency
is now registered in Wirebox. It will be called whenever a dependency cannot be located. The data
sent into the event will contain:
- name - The name of the requested dependency
- initArguments - The
init
arguments, if passed - targetObject - The target object that requested the dependency
- injector - The injector in use building the dependency
If you return in the data
struct an element called, instance
we will return that as the dependency, else the normal exception will be thrown.
// Simple listener example listen( "onInjectorMissingDependency", (data,event,rc,prc)=>{ if( data.name == "OldLegacyOne" ){ data.instance = myNewObject(); } });
Population Enhancements
- The object populator now caches ORM entity maps, so they are only loaded once, and the population with ORM objects accelerates tremendously.
- The object populator caches relational metadata for a faster population of the same type of objects
Mass Population Config: this.population
This new convention allows for objects to encapsulate the way the mass population of data is treated. This way, you don’t have to scrub or pass include excludes lists via population arguments; it can all be nicely encapsulated in the targeted objects:
this.population = { include : [ "firstName", "lastName", "username", "role" ], exclude : [ "id", "password", "lastLogin" ] };
The populator will look for a this.population
struct with the following keys:
include
: an array of property names to allow population ONLYexclude
: an array of property names to NEVER allow population
The population methods also get a new argument called: ignoreTargetLists
which defaults to false, meaning it inspects the objects for these population markers. If you pass it as true
then the markers will be ignored. This is great for the population of objects from queries or an array of structs or mementos that YOU have control of.
populateFromStruct( target : userService.newUser(), memento : record, ignoreTargetLists : true }
Schedule Tasks Revamped
![](png/project.png)
Thanks to Giancarlo Gomez, scheduled tasks get a big revamp in ColdBox 7. Here are the updates:
New Properties
annually
You can now task on an annual basisdelayTimeUnit
used to work with delays regardless of the setting in the chaindebug
used for debug output during task executionsfirstBusinessDay
boolean to flag the task as on the first business day schedulelastBusinessDay
boolean to flag the task as on the last business day scheduletaskTime
log of time of day for first business day and last business day tasksstartTime
limits tasks to run on or after a specific time of dayendTime
limits tasks to run on or before a specific time of daymeta
The user can use this struct to store any data for the task ( helpful for building UIs and not having to manage outside of the task )stats.nextRun
ColdBox now determines the next run for the task in its scheduler interval
Updated Functions
run( boolean force = false )
Added theforce
argument, to allow running the task on demand, even if it is paused.validateTime()
Sets minutes if missing from time entry and returns time value if successful - removes a lot of repetitive code
New Functions
debug()
Used for setting debug setting ( can also be done in task init )startOnTime()
used to set variables.startTimeendOnTime()
used to set variables.endTimebetween()
used to set variables.startTime and variables.endTime in one callsetMeta()
used to set variables.metasetMetaKey()
used to add / save a key to variables.metadeleteMetaKey()
used to delete a key from variables.meta
New Private Functions
getLastBusinessDayOfTheMonth()
getFirstBusinessDayOfTheMonth()
setInitialNextRunTime()
setInitialDelayPeriodAndTimeUnit()
setNextRunTime()
debugLog()
Module Updates
Config Object Override
In ColdBox 7, you can now store the module configurations outside of the config/Coldbox.cfc
. Especially in an application with many modules and many configs, the modulesettings
would get really unruly and long. Now you can bring separation. This new convention will allow module override configurations to exist as their own configuration file within the application’s config folder.
config/modules/{moduleName}.cfc
The configuration CFC will have one configure()
method that is expected to return a struct of configuration settings as you did before in the moduleSettings
component{ function configure(){ return { key : value }; } }
Injections
Just like a ModuleConfig
this configuration override also gets many injections:
* controller
* coldboxVersion
* appMapping
* moduleMapping
* modulePath
* logBox
* log
* wirebox
* binder
* cachebox
* getJavaSystem
* getSystemSetting
* getSystemProperty
* getEnv
* appRouter
* router
Module Injectors
We have an experimental feature in ColdBox 7 to enable per-module injectors. This will create a hierarchy of injections and dependency lookups for modules. This is in preparation for future capabilities to allow for multi-named modules in an application. In order to activate this feature you need to use the this.moduleInjector = true
in your ModuleConfig.cfc
this.moduleInjector = true
Once enabled, please note that your module injector will have a unique name, and a link to the parent and root injectors and will ONLY know about itself and its children. Everything will be encapsulated under itself. No more global dependencies, we are now in module-only dependencies. This means each module must declare its dependencies beforehand.
ModuleConfig Injections
If the module injector is enabled, you will also get different injections in your ModuleConfig
Injection | Description |
---|---|
binder | The root injector binder |
rootWireBox | The root injector or global injector. |
wirebox | This is now a reference to the module's injector. |
Root Helpers
The supertype also has methods to interact with the root and module injector getRootWireBox()
and getWireBox() for the module injector.
getRootWireBox().getInstance( "GlobalModel" )
Injection Awareness
Every module also can inject/use its models
without the need to namespace them.
// Before, using the @contacts address property name="contactService" inject="contactService@contacts"; // If using module injectors property name="contactService" inject="contactService";
Config/Settings Awareness
You can now use the {this}
placeholder in injections for module configurations-settings, and ColdBox will automatically replace it with the current module it's being injected in:
property name="mySettings" inject="coldbox:moduleSettings: {this}" property name="myConfig" inject="coldbox:moduleConfig: {this}"
This is a great way to keep your injections clean without adhering to the module name.
ColdBox Delegates
Since WireBox introduced delegates, we have taken advantage of this great reusable feature throughout ColdBox. We have also created several delegates for your convenience that can be used via its name and the @cbDelegates
namespace:
Delegate | Purpose |
---|---|
AppModes | Methods to let you know in which tier you are on and more |
Interceptor | Announce interceptions |
Locators | Locate files and/or directories |
Population | Populate objects |
Rendering | Render views and layouts |
Settings | Interact with ColdBox/Module Settings |
Let's say you have a security service that needs to get settings, announce events and render views:
component name="SecurityService" delegates="settings@cbDelegates, rendering@cbDelegates, interceptor@cbDelegates"{ function login(){ if( getSetting( "logEnabled" ) ){ ... var viewInfo = view( "security/logdata" ); } ... announce( "onLogin" ); } }
Here you can find more information about the CBDelegates: https://s3.amazonaws.com/apidocs.ortussolutions.com/coldbox/7.0.0/coldbox/system/web/delegates/package-summary.html
User Identifier Providers
In previous versions of ColdBox, it would auto-detect unique request identifiers for usage in Flash Ram, storages, etc., following this schema:
- If we have
session
enabled, use thejessionId
orsession
URL Token - If we have cookies enabled, use the
cfid/cftoken
- If we have in the
URL
thecfid/cftoken
- Create a unique request-based tracking identifier:
cbUserTrackingId
However, you can now decide what will be the unique identifier for requests, flash RAM, etc by providing it via a coldbox.identifierProvider
as a closure/lambda in your config/Coldbox.cfc
coldbox : { ... identifierProvider : () => { // My own logic to provide a unique tracking id return myTrackingID } ... }
If this closure exists, ColdBox will use the return value as the unique identifier. A new method has also been added to the ColdBox Controller so you can retrieve this value:
controller.getUserSessionIdentifier()
The supertype as well so all handlers/layouts/views/interceptors can get the user identifier:
function getUserSessionIdentifier()
App Mode Helpers
ColdBox 7 introduces opinionated helpers to the FrameworkSuperType
so you can determine if you are in three modes: production, development, and testing by looking at the environment
setting:
function isProduction() function isDevelopment() function isTesting()
Mode | Environment |
---|---|
inProduction() == true | production |
inTesting() == true | testing |
inDevelopment() == true | development or local |
These super-type methods delegate to the ColdBox Controller. So that means that if you needed to change their behavior, you could do so via a Controller Decorator.
You can also use them via our new AppModes@cbDelegates
delegate in any model:
component delegate="AppModes@cbDelegates"{}
Resource Route Names
When you register resourceful routes now, they will get assigned a name so you can use it for validation via routeIs()
or for route link creation via route()
Verb | Route | Event | Route Name |
---|---|---|---|
GET | /photos | photos.index | photos |
GET | /photos/new | photos.new | photos.new |
POST | /photos | photos.create | photos |
GET | /photos/:id | photos.show | photos.process |
GET | /photos/:id/edit | photos.edit | photos.edit |
PUT/PATCH | /photos/:id | photos.update | photos.process |
DELETE | /photos/:id | photos.delete | photos.process |
Baby got back()!
The framework super type now sports a back()
function so you can use it to redirect back to the referer in a request.
function save( event, rc, prc ){ ... save your work // Go back to where you came from back(); }
RequestContext
Routing/Pathing Enhancements
The RequestContext has a few new methods to assist you when working with routes, paths, and URLs.
Method | Purpose |
---|---|
routeIs( name ):boolean | Verify if the passed name is the current route |
getUrl( withQuery:boolean ) | Returns the entire URL, including the protocol, host, mapping, path info, and query string. |
getPath( withQuery:boolean ) | Return the relative path of the current request. |
getPathSegments():array | Get all of the URL path segments from the requested path. |
getPathSegment( index, defaultValue ) | Get a single path segment by position |
Native DateHelper
The FrameworkSuperType
now has a getDateTimeHelper(), getIsoTime()
methods to get access to the ColdBox coldbox.system.async.time.DateTimeHelper
to assist you with all your date/time/timezone needs and generate an iso8601 formatted string from the incoming date/time.
getDateTimeHelper().toLocalDateTime( now(), "Americas/Central" ) getDateTimeHelper().getSystemTimezone() getDateTimeHelper().parse( "2018-05-11T13:35: 11Z" ) getIsoTime()
You can also use the new date time helper as a delegate in your models:
component delegate="DateTime@coreDelegates"{ }
Whoops! Upgrades
Whoops got even more love:
- SQL Syntax Highlighting
- JSON Pretty Printing
- JSON highlighting
- Debug Mode to show source code
- Production detection to avoid showing source code
- Rendering performance improvements
![](png/image.png)
![](png/image%20(3)%20(1).png)
RESTFul Exception Responses
The RESTFul handlers have been updated to present more useful debugging when exceptions are detected in any call to any resource. You will now get the following new items in the response:
environment
- A snapshot of the current routes, URLs and event
exception
- A stack frame, detail, and type
{ "data": { "environment": { "currentRoutedUrl": "restfulHandler/anError/", "timestamp": "2023-04-26T20:21: 05Z", "currentRoute": ":handler/:action/", "currentEvent": "restfulHandler.anError" }, "exception": { "stack": [ "/Users/lmajano/Sites/projects/coldbox-platform/system/web/context/RequestContext.cfc:1625", "/Users/lmajano/Sites/projects/coldbox-platform/test-harness/handlers/restfulHandler.cfc:42", "/Users/lmajano/Sites/projects/coldbox-platform/system/RestHandler.cfc:58", "/Users/lmajano/Sites/projects/coldbox-platform/system/web/Controller.cfc:998", "/Users/lmajano/Sites/projects/coldbox-platform/system/web/Controller.cfc:713", "/Users/lmajano/Sites/projects/coldbox-platform/test-harness/models/ControllerDecorator.cfc:71", "/Users/lmajano/Sites/projects/coldbox-platform/system/Bootstrap.cfc:290", "/Users/lmajano/Sites/projects/coldbox-platform/system/Bootstrap.cfc:506", "/Users/lmajano/Sites/projects/coldbox-platform/test-harness/Application.cfc:90" ], "detail": "The type you sent dddd is not a valid rendering type. Valid types are JSON,JSONP,JSONT,XML,WDDX,TEXT,PLAIN,PDF", "type": "RequestContext.InvalidRenderTypeException", "extendedInfo": "" } }, "error": true, "pagination": { "totalPages": 1, "maxRows": 0, "offset": 0, "page": 1, "totalRecords": 0 }, "messages": [ "An exception ocurred: Invalid rendering type" ] }
ColdBox DebugMode
ColdBox now has a global debugMode
setting, which is used internally for presenting sources in Whoops and extra debugging parameters in our RESTFul handler. However, it can also be used by module developers or developers to dictate if your application is in debug mode or not. This is just a fancy way to say we trust the user doing the requests.
{% hint style="info" %} The default value is false {% endhint %}
coldbox : { ... debugMode : true ... }
You also have a inDebugMode()
method in the main ColdBox Controller and the FrameworkSuperType that will let you know if you are in that mode or not.
boolean function inDebugMode() // Debug mode report if( inDebugMode() ){ include "BugReport.cfm"; }
You can also use the AppModes@cbDelegates
delegate to get access to the inDebugMode
and other methods in your models:
component delegates="AppModes@cbDelegates"{ ... if( inDebugMode() ){ } ... }
Closure Logging
In previous versions, you had to use the canxxxx
method to determine if you can log at a certain level:
if( log.canInfo() ){ log.info( "This is a log message", data ); } if( log.canDebug() ){ log.debug( "This is a debug message", data ); }
In ColdBox 7, you can use a one-liner by leveraging a lambda function:
log.info( () => "This is a log message", data ) log.debug( () => "This is a debug message", data )
Read More
This is just the tip of the iceberg of all the great features and enhancements of ColdBox 7. Please read more here: https://coldbox.ortusbooks.com/intro/release-history/whats-new-with-7.0.0
Add Your Comment