Configuration Objects
Introduction
Configuration objects provide a common way to access and encapsulate business logic rules to a service based on the user input data. The configuration object is specific to each revision since that data is also different for each revision.
For simple rules, you probably do not need this as you can just access a model directly but sometimes conditions might grow large if there is a tendency to repeat multiple choices.
When to Use
The difference between the phase model data and the configuration object is roughly that the model data is the "raw" data that the configuration wraps to process or provide default values. They are not in way necessary but provide a way to simplify phase conditions or to encapsulate business logic around the user input data.
The phase models are loaded when necessary (ie. when entering the page or calling the QuotationFacade::getPhaseModel()
method for the first time). This means that whatever defaults the editor provides will not be available before the user enters the page. Therefore, it is good to provide some default values even though the user didn't visit that specific page.
For example, a condition like this:
condition: 'models["taxes"].SalesTaxChoice == "service" and models["taxes"].SalesTaxValue > 0'
Could be moved to the configuration object and rewritten as:
condition: 'configuration.taxes.has("SalesTax")'
When the condition is evaluated it will call the method in the configuration which can return if the condition is true or not based on the form selections.
Use cases:
- Transform selection values for display, eg. "firm" from the model data to "Firm Quotation"
- Provide a default value if nothing selected
- Provide options for selections.
- Wrap multiple nested phases into coherent whole, eg. the configuration for a motor might wrap the phase data from drive type and gear selection pages to provide access to the whole motor configuration.
- Provide an interface for other dependent software to access the configuration.
Usage
Configuration Manager
The configurations are fetched through the configuration manager service which can be fetched with the service id klaro_quotation.configuration_manager
. Use the getConfigurationMethod()
to load a configuration:
<?php
$configurationManager = $this->get('klaro_quotation.configuration_manager');
$myConfig = $configurationManager->getConfiguration($revision, 'my_configuration');
For every configuration you must provide the revision in whose context the data is in. If the configuration is already loaded for the given revision, then a cached copy is return instead.
NOTE: The configuration manager is always initialized in the Quotation Bundle controller methods with the current quotation and revision. If the configuration manager is used outside the controller contexts, the you should call the initialize()
methods explicitly. Othewise the methods to get the current revision or models will not work!
The configuration manager provides methods to get the current revision and to load phase models.
Method | Description |
---|---|
getCurrentQuotationRevision() | Get the revision that the manager was initialized with. |
getModelProvider() | Get access to the model provider. |
getPhaseModel() | Get a phase model |
getPhaseModelForCurrent() | Get a phase model for the current revision |
getPhaseData() | Load a phase model using phase id and the models configuration object (see "Phase Models" below) |
Adding the Configuration Service
Configurations can be added by tagging a service with the name klaro_quotation.configuration
. These can then be accessed in expressions through the configuration
variable. The tag alias defines the name that you can get the service from the configuration object.
All configuration objects must implement the interface Klaro\QuotationBundle\Api\ConfigurationInterface
. In some cases the configuration needs to be a list (for example if a phase is repeatable and one configuration matches to one phase), you might want to implement Klaro\QuotationBundle\Api\ConfigurationListInterface
.
To access a configuration list, use array access:
condition: 'configuration.lines[2].has("Valve")'
In practice, it is better to extend the abstract configuration class in Klaro\QuotationBundle\Configuration\BaseConfiguration
and Klaro\QuotationBundle\Configuration\BaseConfigurationList
which provide all the necessary implementations for object notation and array access. All you have to do then is the add the approriate getters and check functions to your sub class.
Below is the class diagram for configuration objects (includes also the existing ones, see "Available Configuration Objects" below).
After loading the configuration object, the configuration manager will call the object's setContext()
method to provide the revision and access to the configuration manager itself.
Example of a service configuration:
acme_test_configuration:
class: Acme\SomeBundle\Configuration\TestConfiguration
tags:
- { name: 'klaro_quotation.configuration', alias: 'acme_config' }
This can now be used in expressions with configuration.acme_config.has("Property")
. This will result in a call to TestConfiguration::has("Property")
method - or if it extends the BaseConfiguration
class, in a call to TestConfiguration::hasProperty()
.
Simple attribute access will trigger a get()
call:
condition: 'configuration.acme_config.SomeField == "OK"'
Is the same as:
condition: 'configuration.acme_config.get("SomeField") == "OK"'
This will call:
<?php
class TestConfiguration extends BaseConfiguration {
public function getSomeField() {
return $this->model->get('SomeField');
}
}
Configuration lists can be also looped:
<?php
// Assume "lines" is a configuration object that inherits BaseConfigurationList.
$lines = $configurationManager->getConfiguration($revision, 'lines');
$total = count($lines);
foreach($lines as $line) {
if($line->hasSomeFieldEnabled()) {
$value = $line->getSomeField();
}
}
Extending from the BaseConfiguration
also gives you helpers to easily access the context and load phase models:
<?php
class MyConfiguration extends BaseConfiguration {
public function getSomeField() {
$model = $this->getPhaseData('phaseId');
return $model->get('SomeField');
}
}
NOTE: Do not load the phase models in the constructor because the revision context has to be known before that. Load models either in the setContext()
method after getting the revision or lazy load them:
<?php
class MyConfiguration extends BaseConfiguration {
/** @var FormModelInterface */
protected $model;
public function __construct() {
parent::__construct();
$this->model = null;
}
public function getModel() {
if(!$this->model) {
$this->model = $this->getPhaseData('phaseId');
}
return $this->model;
}
public function getSomeField() {
return $this->getModel()->get('SomeField');
}
}
Available Configuration Objects
The Klaro\QuotationBundle\Configuration\BaseConfigs
class provides constants to the configuration aliases below.
Current User
The current user configuration object exists to provide a way to access the currently logged in user in condition. This is mainly useful for testing if the user a has a permission or not. The configuration alias is current_user
and is available also in conditions as user
.
condition: 'configuration.current_user.has("Role", "ALLOW_DISCOUNT")'
Is the same as:
condition: 'user.has("Role", "ALLOW_DISCOUNT")'
In PHP:
<?php
$currentUser = $configurationManager->getConfiguration($revision, BaseConfigs::CURRENT_USER);
$currentUser->has('Role', 'ALLOW_DISCOUNT');
Phase Models
Accessing phase models is possible with models
configuration object. This is also available in phase conditions as in shorter form, so configuration.models
is the same as just models
.
To access the phase data, use the phase id with object or array notation:
models.general.SomeField
models["general"].SomeField
In PHP:
<?php
$models = $configurationManager->getConfiguration($revision, BaseConfigs::MODELS);
$value = $models["general"]->get('SomeField');
// or
$value = $models->general->get('SomeField');
For nested phases, use must use the array notation:
models["line/valves"].SomeField
$value = $models["line/valves"]->get('SomeField');
If a phase is repeatable, then you need also the ordinal:
models["repeatablePhase"][0].SomeField
<?php
$models = $configurationManager->getConfiguration($revision, BaseConfigs::MODELS);
$value = $models["repeatablePhase"][0]->get('SomeField');
// Get number of models:
$repeatablePhase = $models["repeatablePhase"];
$count = count($repeatablePhase);
// Loop
for($i = 0; $i < $count; ++$i) {
$model = $repeatablePhase[$i];
$value = $model->get('SomeField');
}
NOTE: The difference between using this configuration object and just calling QuotationFacade::getPhaseModel()
directly is that the phase id is used to load the actual phase definition. The model name is gotten from the phase definition which is then used to load the model with getPhaseModel()
. It also does the necessary wrapping to provide repeated models with array access.