Definition

As you'll have noticed in the introductory examples, Jet\Config uses attributes (PHP 8) for which Jet has its own little Jet\Attributes extension.

Both the class that represents the configuration definition and its properties, which represent the configuration values, must have the necessary attributes.

(A class can of course have other properties for its internal purposes completely without definitions)

We'll see an overview of the definition attributes below.

Class Definition

The class definition currently has only one parameter, which is the name of the configuration - the name parameter. This parameter is used to specify the name of the defined configuration and is mainly used for saving.

Thus, each class representing a configuration must specify the name of that configuration and must start with, for example:

namespace Jet;

#[
Config_Definition(name'db')]
class 
Db_Config extends Config
{
    
//... ... ...
}

Property definitions - configuration values

Here the situation is much more interesting :-)

Those properties of a class that have a configuration definition are treated as configuration values. It is true that class property name = configuration value name and under the same name (or better said key in this context) the value will be stored in the configuration file.

Of course, for a definition to be really useful for anything, you need to know a lot more information than just the name of the configuration value/key.

Let's show again a real example of a property/configuration value definition that is straight from the Jet library:

namespace Jet;

#[
Config_Definition(name'db')]
class 
Db_Config extends Config
{
    #[
Config_Definition(
        
typeConfig::TYPE_STRING,
        
is_requiredtrue,
    )]
    #[
Form_Definition(
        
typeForm::TYPE_SELECT,
        
is_requiredtrue,
        
label'Default connection:',
        
help_text'Connection name - default value for Db::get()',
        
select_options_creator: [
            
Db_Config::class,
            
'getConnectionsList'
        
],
        
error_messages: [
            
Form_Field::ERROR_CODE_EMPTY => 'Please select default connection',
            
Form_Field::ERROR_CODE_INVALID_VALUE => 'Please select default connection'
        
]
    )]
    protected 
string $default_connection_name 'default';

    public function 
getDefaultConnectionName(): string
    
{
        return 
$this->default_connection_name;
    }

    public function 
setDefaultConnectionNamestring $default_connection_name ): void
    
{
        
$this->default_connection_name $default_connection_name;
    }

    
//... ... ... 
}

As you can see, the property has a getter and a setter (which is a good habit that I highly recommend - it always pays off on large and long-term projects) and, most importantly, multiple attributes. Let's take a look at them.

Basic attributes

Atribut Meaning of
type Type of value. The type can be:
  • Config::TYPE_STRING
    Text value. It has other specific attributes - see below.
  • Config::TYPE_BOOL
    Bool value.
  • Config::TYPE_INT
    The whole number. It has other specific attributes - see below.
  • Config::TYPE_FLOAT
    Decimal. It has other specific attributes - see below.
  • Config::TYPE_ARRAY
    A simple array of arbitrary values.
  • Config::TYPE_SECTION
    Subsection Configuration - see below.
  • Config::TYPE_SECTIONS
    Several subsections of the configuration - for example, several database connections and so on. See below.
is_required Indicates whether the definition of the value is mandatory. That is, whether the value/key must be present in the configuration.

Specific attributes

Next, we will show the attributes bound to certain types of configuration values.

Config::TYPE_STRING

Atribut Meaning of
validation_regexp The text value can be optionally validated using a regular expression by which the configuration value is checked.

Config::TYPE_INT a Config::TYPE_FLOAT

Atribut Význam
min_value A numeric value may have a defined and verifiable minimum value
max_value A numeric value can have a defined and verifiable maximum value

Config::TYPE_SECTION a Config::TYPE_SECTIONS

Atribut Meaning of
section_creator_method_name The name of the factory method for creating configuration subsection definitions. However, this is a separate topic - see below

Atributy Form_Definition(*)

You will have noticed the Form_Definition attributes in the examples. This is not directly related to configuration. In Jet, you can actually define form on any class this way, thanks to mapping classes to forms. It's a general-purpose system that the configuration automatically accounts for, but which is generally applicable.

Configuration subsections

We have already introduced this topic in connection with the Config::TYPE_SECTION and Config::TYPE_SECTIONS types. Now we will explain it in more detail.

Configuration is far from being just a simple key -> value structure. We often need subsections, lists of subsections, both for clarity and especially for practical reasons. Let's show it again directly on a real example.

This typical example directly from the "guts" of Jet can be the configuration of the connection to the database Jet\Db.

This time, for the sake of illustration, we will not show the classes right away, but an example of an already saved configuration:

return [
    
'default_connection_name' => 'default',
    
'connections' => [
        
'default' => [
            
'driver' => 'mysql',
            
'name' => 'default',
            
'host' => 'localhost',
            
'port' => 3306,
            
'dbname' => 'our_db',
            
'charset' => 'utf8',
            
'username' => '*******',
            
'password' => '*******',
        ],
        
'erp' => [
            
'driver' => 'oci',
            
'dsn' => 'dbname=//111.222.111.222:1521/erp_db',
            
'username' => '*******',
            
'password' => '*******',
        ],
        
'some_small_db' => [
            
'driver' => 'sqlite',
            
'name' => 'default',
            
'path' => '/*****************************/application/data/db.sq3',
        ],

    ],
];

As is obvious, there can be more than one connection to a relational database.

And it is also obvious that each connection has a precise set of configuration values that must be defined, but at the same time each connection type can take a different form depending on the database type.

We'll see how to achieve this in a moment. First, let's go back to the Jet\Db_Config class, part of which we showed in this chapter.

The previous demonstration of the Jet\Db_Config configuration class was all about the 'default_connection_name' parameter/key.

Now let's show the part that deals with 'connections':

#[Config_Definition(name'db')]
class 
Db_Config extends Config
{
    #[
Config_Definition(
        
typeConfig::TYPE_SECTIONS,
        
section_creator_method_name'connectionConfigCreator'
    
)]
    protected ?array 
$connections null;

    public function 
connectionConfigCreator( array $data ): Db_Backend_Config
    
{
        return 
Factory_Db::getBackendConfigInstance$data );
    }

    
/**
     * @return Db_Backend_Config[]
     */
    
public function getConnections(): array
    {
        return 
$this->connections;
    }

    public function 
getConnectionstring $connection_name ): Db_Backend_Config|null
    
{
        if( !isset( 
$this->connections[$connection_name] ) ) {
            return 
null;
        }

        return 
$this->connections[$connection_name];
    }

    public function 
addConnectionstring $connection_nameDb_Backend_Config $connection_configuration ): void
    
{
        
$this->connections[$connection_name] = $connection_configuration;
    }
    
    public function 
deleteConnectionstring $connection_name ): void
    
{
        if( isset( 
$this->connections[$connection_name] ) ) {
            unset( 
$this->connections[$connection_name] );
        }
    }
}

As you can see, the class has a property $connections and it is defined as type Config::TYPE_SECTIONS.

At the same time, the name of the factory method is defined using the section_creator_method_name attribute. The factory method makes sure that the raw data in the form of a loaded associated field becomes a connection definition - that is, an instance of some appropriate class that represents the definition of a specific section of the configuration (here, a specific connection).

In this case, the definition of the configuration part is represented by the Jet\Db_Backend_Config class (or actually the Jet\Db_Backend_PDO_Config class, but let's not complicate things now :-) ).

Surely the use of factory( Factory_Db::getBackendConfigInstance( $data )) hasn't escaped your attention - so the class is interchangeable, but that's unrelated to the topic of configuration and its definition. Back to the topic :-)

Let's recap - if you want to have a subsection, or subsections, in your configuration, you need:

  • In the configuration definition, have property(s) of type Config::TYPE_SECTION (if it is to be a simple subsection), or Config::TYPE_SECTIONS (if it is to be a list as in this case).
  • You need to have a defining class for the section/sections. In this case, Db_Backend_Config, which already defines the configuration of a particular database connection.
  • In the main class defining the configuration, it is definitely useful to have additional methods for manipulating sections. Such as in this example for adding and removing connections and so on.

The definition of the configuration section can then be, for example, as follows:

#[Config_Definition]
abstract class 
Db_Backend_Config extends Config_Section
{
    #[
Config_Definition(
        
typeConfig::TYPE_STRING,
        
is_requiredtrue,
    )]
    #[
Form_Definition(
        
typeForm::TYPE_SELECT,
        
label'Driver',
        
help_text'PDO driver',
        
is_requiredtrue,
        
select_options_creator: [
            
self::class,
            
'getDrivers'
        
],
        
error_messages: [
            
Form_Field::ERROR_CODE_EMPTY => 'Please select driver',
            
Form_Field::ERROR_CODE_INVALID_VALUE => 'Please select driver'
        
]
    )]
    protected 
string $driver 'mysql';

    #[
Config_Definition(
        
typeConfig::TYPE_STRING,
        
is_requiredtrue,
    )]
    #[
Form_Definition(
        
typeForm_Field::TYPE_INPUT,
        
label'Connection name',
        
is_requiredtrue,
        
error_messages: [
            
Form_Field::ERROR_CODE_EMPTY => 'Please enter connection name'
        
]
    )]
    protected 
string $name 'default';

    
//... ... ...
    //... ... ...
    //... ... ...
}

As you can see, the configuration section definition is almost the same as the main configuration definition. There are only two minor but crucial differences:

  • The class does not inherit from Jet\Config, but from Jet\Config_Section
  • The class itself has no attribute values. Only the attribute definitions are needed.

Simply put, one configuration fits into the other. And yes, a subsection can have other subsections.

Now you know how a configuration definition works and how to work with it from an application perspective. Next, I recommend taking a look at the Jet\Config class, which is the focal point of the entire subsystem.

Previous chapter
Application configuration system
Next chapter
Jet\Config class