Application configuration system
The application configuration system is the part of your project configuration that requires active user intervention. It can start with the installer itself, which we used to install the Jet sample application, where you need to enter the data to connect to the database. Next, you can imagine configuration tools in the administration of your project, with which an advanced user can set certain options - what exactly is up to you :-)
In summary, we often need:
- To have configuration values stored somewhere
- At the level of our application, we need to access these values through a clearly defined interface, so that it is clear what options the application is working with and where, and so that the application is sustainable in the long term.
- We need to develop various tools for the user to be able to modify some of the options.
- These tools will, by rule, be about forms, capturing, validating and then saving the values to a configuration file.
Therefore, it doesn't matter whether we are dealing with database connection settings in the installer, or setting outgoing mail addresses, or VAT rate within the e-shop, it is always the same in principle. And Jet provides a way to make such work as easy as possible.
Using configuration in an application
Let's first show how an application uses such configuration. The sample application also uses this configuration, but in fact its use is "stored" directly in Jet (not part of the application space). Therefore, for the sake of demonstration, let's make up a situation that is, so to speak, from real life and create an example from scratch.
You are developing an e-shop and it has some standard shipping and delivery time (goods it has in stock), which is determined by the number of working days. You use this to inform the customer when the goods will be delivered. Let's assume that most of the time the e-shop needs 2 working days for delivery, but it can happen that neither the e-shop nor the carriers can keep up during the main e-shopping seasons. And you definitely don't want to confuse the customer, that's not right. You also don't want to generate inquiries to the e-shop sales department, bad reviews and so on. It's also definitely not appropriate for you as developers to spend time on trivial configuration changes :-) Surely we always have better things to do. That's why we will make this figure (and many others) configurable and create a tool in the administration whereby someone in charge of the e-shop can change the number of days needed for shipping and delivery as the situation arises (And since it's a PHP Jet application, we will of course have rights, logging and so on - but that's another chapter).
Okay, here we go. First, we'll create a class to represent and define the configuration. It will only have one parameter to start with, but a real application will of course have many more:
namespace JetApplication;
use Jet\Config;
use Jet\Config_Definition;
use Jet\Form_Definition;
use Jet\Form_Field;
#[Config_Definition(
name: 'eshop'
)]
class EShopConfig extends Config {
#[Config_Definition(
type: Config::TYPE_INT,
is_required: true,
)]
#[Form_Definition(
type: Form_Field::TYPE_INT,
label: 'Delivery days:'
)]
protected int $delivery_days = 2;
public function deliveryDays(): int
{
return $this->delivery_days;
}
}
This defines the configuration and at this point we have already secured:
- The configuration file will be loaded and saved
- The configuration can be validated
- We can easily get a form, which can be used to change the configuration by mapping classes to forms.
- We have a clear, unambiguous and understandable system for how the rest of the application will work with configuration values.
Reading configuration values
The application (i.e. our imaginary e-shop) will definitely need to read that number of days. In principle, it's simple:
(new EShopConfig())->deliveryDays();
But doing it only like that is definitely not a good idea. The configuration instance needs to be kept somewhere as a singleton. Where exactly is up to you. Let's see how our sample class could be modified:
class EShopConfig extends Config {
//... ... ... ...
//... ... ... ...
//... ... ... ...
protected static ?EShopConfig $config = null;
public static function get() : static
{
if(static::$config===null) {
static::$config = new static();
}
return static::$config;
}
}
And then the usage looks like this:
EShopConfig::get()->deliveryDays();
How to create a configuration tool
At this point we have a configuration definition, the application can start using it, everything is saved and loaded (later we will show how and where) and now it is time to develop an administration tool, which can be used by the head of the sales department of the e-shop to set this value as needed.
Let's immediately consider that the ideal PHP Jet application should be modular, meaning that this tool should be modular, will definitely use MVC, and will use a forms system. The tool should definitely use permission checking and logging. These are all obvious things that belong in other chapters, but let's just show the finished controller of our hypothetical module Admin.EShopConfig. We'll pre-generate the module itself using the Jet Studio tool and write the controller:
namespace JetApplicationModule\Admin\EShopConfig;
use Jet\Config;
use JetApplication\EShopConfig;
use Jet\Logger;
use Jet\Http_Headers;
use Jet\MVC_Controller_Default;
use Jet\Tr;
class Controller_Main extends MVC_Controller_Default
{
public function default_Action() : void
{
Config::setBeTolerant(true);
$config = new EShopConfig();
$form = $config->createForm( 'eshop_config_form' );
if($form->catch()) {
$ok = true;
try {
$config->saveConfigFile();
} catch(\Exception $e) {
$ok = false;
$form->setCommonMessage(
Tr::_('Something went wrong: %error%', ['error'=>$e->getMessage()])
);
}
if($ok) {
Logger::info(
event: 'eshop_config_updated',
event_message: 'EShop configuration has been updated',
context_object_data: $config
);
Http_Headers::reload();
}
}
$this->view->setVar( 'form', $form );
$this->output( 'default' );
}
}
At this point we have already done:
- generating the form
- capturing and validating it
- storing the configuration
- capturing any problems
- logging the operation
- and by default checking permissions
Now all that's left is to view the form in view:
<?php
namespace JetApplicationModule\Admin\EShopConfig;
use Jet\Form;
use Jet\Form_Renderer;
use Jet\MVC_View;
use Jet\UI;
use Jet\UI_messages;
/**
* @var MVC_View $this
* @var Form $form
*/
$form = $this->getRaw('form');
$form->renderer()
->setDefaultLabelWidth([Form_Renderer::LJ_SIZE_MEDIUM=>1])
->setDefaultFieldWidth([Form_Renderer::LJ_SIZE_MEDIUM=>2]);
?>
<?= $form->start(); ?>
<div class="row toolbar" id="main-toolbar">
<div class="col-md-12">
<?php if( !$form->getIsReadonly() ): ?>
<?= UI::button_save() ?>
<?php endif; ?>
</div>
</div>
<div class="row">
<div class="col-md-12" id="main-col">
<?php if($form->getCommonMessage()): ?>
<?=UI_messages::createDanger( $form->getCommonMessage() ) ?>
<?php endif ?>
<?= $form->field( 'delivery_days' ) ?>
</div>
</div>
<?= $form->end(); ?>
And that's it. The configuration tool is ready:
If we need additional configurable values in the project, the tab will only need to add a definition. So let's say we need some generic text value.
We will do the following:
- The JetApplication\EShopConfig class is supplemented by the definition and the corresponding getter (and optionally setter):
#[Config_Definition(
type: Config::TYPE_STRING,
)]
#[Form_Definition(
type: Form_Field::TYPE_INPUT,
label: 'Some text:'
)]
protected string $some_text = '';
public function someText(): string
{
return $this->some_text;
} - The controller does not need to be interfered with in any way
- We'll just fill in the view:
// ... ... ...
<?= $form->field( 'delivery_days' ) ?>
<?= $form->field( 'some_text' ) ?>
// ... ... ...
And that's all... Of course everything can be further modified, customized, there should be no limitations. But this example was meant to show what Jet\Config is for and how it is used.
So we have shown the basic principle and in the next chapters we will go through everything in detail and in depth.