<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jan Böhmer</title>
    <description>The latest articles on DEV Community by Jan Böhmer (@jbtronics).</description>
    <link>https://dev.to/jbtronics</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F390045%2F08e74f42-52a7-43c9-9c4e-e6342d6fe197.png</url>
      <title>DEV Community: Jan Böhmer</title>
      <link>https://dev.to/jbtronics</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jbtronics"/>
    <language>en</language>
    <item>
      <title>User-configurable settings in Symfony applications with jbtronics/settings-bundle (Part 3): Migrations and environment variables</title>
      <dc:creator>Jan Böhmer</dc:creator>
      <pubDate>Wed, 17 Jul 2024 07:55:10 +0000</pubDate>
      <link>https://dev.to/jbtronics/user-configurable-settings-in-symfony-applications-with-jbtronicssettings-bundle-part-3-migrations-and-environment-variables-4jpl</link>
      <guid>https://dev.to/jbtronics/user-configurable-settings-in-symfony-applications-with-jbtronicssettings-bundle-part-3-migrations-and-environment-variables-4jpl</guid>
      <description>&lt;p&gt;In the first two parts of this series, the basic concepts of the settings-bundle were introduced and how to use it to create nice user-configurable settings in Symfony applications.&lt;br&gt;
In this part, you will learn how to version your settings and migrate between them. Additionally, you will learn how to combine environment variables with settings.&lt;/p&gt;
&lt;h2&gt;
  
  
  Versioning and migration
&lt;/h2&gt;

&lt;p&gt;Over time you application will evolve and so will your settings. This means that over time new parameters will be added to settings, old ones will be removed and existing ones will be changed. To handle this, the settings-bundle provides a versioning and migration mechanism, which takes care of most of the work for you.&lt;/p&gt;

&lt;p&gt;Let's assume you have a simple settings class like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;
&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Settings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Jbtronics\SettingsBundle\Settings\Settings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Jbtronics\SettingsBundle\Settings\SettingsParameter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="na"&gt;#[Settings]&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestSettings&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="na"&gt;#[SettingsParameter]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'test@invalid'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="na"&gt;#[SettingsParameter]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$baz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These settings were already used for some time in your application and users already saved their customizations to it. If you just want to add a new parameter to the settings, you can do this by simply adding a new property to the class, and it will work fine. The new parameter will be initialized with the default value and users can change it as they like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;
&lt;span class="na"&gt;#[Settings]&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestSettings&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="na"&gt;#[SettingsParameter]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'test@invalid'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="na"&gt;#[SettingsParameter]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$baz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="na"&gt;#[SettingsParameter]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nv"&gt;$qux&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Removing a parameter works similarly. If you remove a property from the class, the settings-bundle will ignore existing values for it, and delete it the next time the settings are saved.&lt;/p&gt;

&lt;p&gt;However, what is more tricky, is if you want to rename a field or, even more complex, change its type or how data is exactly saved. To not lose existing customizations of users, you have to specify how to convert between the different representations of the settings. The settings-bundle can support you with this by providing a framework for migrations.&lt;/p&gt;

&lt;p&gt;Let's assume you want to change your settings class in a way, that you now can have multiple email addresses. Also, you want to change the indexing of the baz parameter, so that it not start at 0, but at 1, meaning that all existing values should be incremented by 1. In the end your settings class should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;
&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Settings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Jbtronics\SettingsBundle\Settings\Settings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Jbtronics\SettingsBundle\Settings\SettingsParameter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="na"&gt;#[Settings(version: self::VERSION, migrationService: TestSettingsMigration::class)]&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestSettings&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="no"&gt;VERSION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="na"&gt;#[SettingsParameter(type: ArrayType::class, options: ['type' =&amp;gt; StringType::class])]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'test@invalid'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="na"&gt;#[SettingsParameter]&lt;/span&gt;
    &lt;span class="c1"&gt;//Now with different indexing&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$baz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;43&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The test settings class now have the new intended structure and can be used in the application. However, the settings-bundle will not know how to convert the existing data to the new structure. This is where migrations&lt;br&gt;
come into play.&lt;/p&gt;

&lt;p&gt;You can see that the settings attribute now have the &lt;code&gt;version&lt;/code&gt; option and the &lt;code&gt;migrationService&lt;/code&gt; option specified:&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;version&lt;/code&gt; option specifies the most recent schema version of the settings and is just a integer (greater zero), which is incremented every time you change the structure of the settings class. You can start with 1 and increment it every time you change the structure of the settings class. You can put the version number directly into the attribute, or you can define a constant for it, as shown in the example, which has the advantage that you can retrieve the current version easily from outside the class.&lt;/p&gt;

&lt;p&gt;The second new thing is the &lt;code&gt;migrationService&lt;/code&gt; option. This specifies the service class, which actually performs the data migration. The &lt;code&gt;migrationService&lt;/code&gt; must implement the &lt;code&gt;SettingsMigrationInterface&lt;/code&gt;, which specifies a &lt;code&gt;migrate&lt;/code&gt; function that is responsible for performing migration between two given versions of the data.&lt;/p&gt;

&lt;p&gt;In most cases you want to step-wise migrations between the versions (meaning you migrate 1 -&amp;gt; 2, then 2 -&amp;gt; 3 and so on, instead of 1 -&amp;gt; 3 directly to avoid code duplication). In this situation, it is easier to extend the &lt;code&gt;SettingsMigration&lt;/code&gt; class. Using this abstract class, your migration service might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;
&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Settings\Migrations&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Jbtronics\SettingsBundle\Migrations\SettingsMigration&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestSettingsMigration&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;SettingsMigration&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * This method is called automatically by the migration class and handles 
     * migration of version 0 (non versioned settings) to version 1.
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;migrateToVersion1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;SettingsMetadata&lt;/span&gt; &lt;span class="nv"&gt;$metadata&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="cm"&gt;/*
         * $data contains the old settings data, in the normalized form (in the way it was saved in the database)
         * Each key is the parameter name (not necessarily the property name) 
         * 
         * In the end we must return the new data in the normalized form, which is later then passed to 
         * the parameter type converters.
         */&lt;/span&gt;

        &lt;span class="c1"&gt;//If the email parameter was set, convert it to an array&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;isset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'email'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'email'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'email'&lt;/span&gt;&lt;span class="p"&gt;]];&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;//Increment the baz parameter, if it was set&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;isset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'baz'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'baz'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;//Return the new data&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * This method is called, to handle migration from version 1 to version 2.
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;migrateToVersion2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;SettingsMetadata&lt;/span&gt; &lt;span class="nv"&gt;$metadata&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;//Perform some more migrations...&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The migration service contains various methods in the form &lt;code&gt;migrateToVersionXX()&lt;/code&gt;, which are called automatically by the class if the settings are migrated from version XX-1 to version XX. The method receives the data in the normalized form and the metadata of the settings class and must return the data in the normalized form, which is then passed to the parameter type converters. If you want to specify explicitly which functions are called for which version, you can override the &lt;code&gt;resolveStepHandler&lt;/code&gt; method, which returns the closure to use for a given version.&lt;/p&gt;

&lt;p&gt;As the existing data had no version yet, it is assumed it was version &lt;code&gt;0&lt;/code&gt;. Therefore, when encountering these data settings-bundle will call the &lt;code&gt;migrateToVersion1&lt;/code&gt; handler to migrate from &lt;code&gt;0&lt;/code&gt; to the most recent version &lt;code&gt;1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The old data from the storage is passed to the migration method (as &lt;code&gt;$data&lt;/code&gt;) and you have to convert it to the new form how it can be saved to storage and how the parameter type conversions can understand it. Each parameter is stored in the &lt;code&gt;$data&lt;/code&gt; array with the parameter name as key. You can then modify the data as you like and return it in the end. &lt;/p&gt;

&lt;p&gt;Please note that the $data array is in the normalized form, meaning that you only have simple datatypes like strings, integers, arrays and so on. If you want to like to work with the denormalized form (like objects, etc.) you might find the &lt;code&gt;getAsPHPValue()&lt;/code&gt; and &lt;code&gt;setAsPHPValue()&lt;/code&gt; methods available in the &lt;code&gt;SettingsClass&lt;/code&gt; (or in the &lt;code&gt;PHPValueConverterTrait&lt;/code&gt;) useful. Or you call the ParameterTypes you need directly.&lt;/p&gt;

&lt;p&gt;The settings-bundle stores the version of the data in the storage provider, so that it is automatically known what version the data has and what migrations to perform. The migrations are automatically performed when trying to retrieve settings data (by getting the settings from the SettingsManager or calling a property of a lazy settings class). By default, the migrated data is written back to the storage after the migration, so that the migration only has to be performed once for each setting, even if the settings are not explicitly written back to the storage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Environment variables
&lt;/h2&gt;

&lt;p&gt;Environment variables are one of the classic possibilities to &lt;a href="https://symfony.com/doc/current/configuration.html" rel="noopener noreferrer"&gt;configure a Symfony application&lt;/a&gt;. They allow you for an easy configuration approach in automatic deployed applications, containers, etc. via a more or less unified interface. So they are pretty ideal for server administrators, who want to configure an application without touching the code. However, the big disadvantage of environment variables is, that they are not user-configurable, as users (even those intended as admin users) can not change them without direct access to the server.&lt;/p&gt;

&lt;p&gt;To retain the advantages of environment variables, while also allowing users to configure the applications via the settings-bundle, the bundle can map environment variables to settings class parameters.&lt;/p&gt;

&lt;p&gt;This is done via the &lt;code&gt;envVar&lt;/code&gt; option on the &lt;code&gt;SettingsParameter&lt;/code&gt; attribute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;
&lt;span class="na"&gt;#[Settings]&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestSettings&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="na"&gt;#[SettingsParameter(envVar: 'APP_EMAIL')]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'test@invalid'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="na"&gt;#[SettingsParameter(envVar: 'int:APP_BAZ', envVarMode: EnvVarMode::OVERWRITE)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$baz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="na"&gt;#[SettingsParameter(envVar: 'bool:APP_TEST_SETTINGS_QUX', envVarMode: EnvVarMode::OVERWRITE_PERSIST)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nv"&gt;$qux&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;envVar&lt;/code&gt; option specifies the environment variable to map to the parameter. If it does not exist, nothing happens. However, if it exists, the bundle will retrieve the value of the environment variable and set it as the value of the parameter. By default, the "raw" environment variable contains just a string. If you have another simple data type (like an integer or a boolean), you can use one of Symfony's &lt;a href="https://symfony.com/doc/current/configuration/env_var_processors.html" rel="noopener noreferrer"&gt;env var processors&lt;/a&gt; to convert the string value of the env variable to the desired type (e.g. &lt;code&gt;int:APP_BAZ&lt;/code&gt;, which converts the content of &lt;code&gt;APP_BAZ&lt;/code&gt; to an int).&lt;/p&gt;

&lt;p&gt;The environment variable handling happens transparently in the background, meaning that you can use the settings class as usual, and you (almost) do not have to care about the environment variables when using the settings.&lt;/p&gt;

&lt;h3&gt;
  
  
  Environment variable handling modes
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;envVarMode&lt;/code&gt; option specifies how the environment variable should be handled. If no mode is specified, the mode &lt;code&gt;EnvVarMode::INITIAL&lt;/code&gt; is used. In this mode the environment variable is only used to initialize the parameter. That means if the parameter is used the first time, instead of the default value in the code, the value of the environment variable is used. Users can change this value as they like, and the environment variable will not affect the parameter anymore. This mode allows a server administrator to set useful initial defaults via environment variables (e.g. while deploying the container), but users can change them completely later.&lt;/p&gt;

&lt;p&gt;However, in some cases, you might want the server admin to enforce a certain value via environment variables and forbid users to change them via WebUI. For these cases, you can use the  &lt;code&gt;EnvVarMode::OVERWRITE&lt;/code&gt; and &lt;code&gt;EnvVarMode::OVERWRITE_PERSIST&lt;/code&gt; mode. In this mode, the environment variable will always overwrite a parameter value, no matter what was set as a value before by users. This means that freshly retrieved settings will always have the value of the environment variable, even if the user changed it before. The &lt;code&gt;OVERWRITE_PERSIST&lt;/code&gt; mode additionally writes the value back to the storage, so that the value is still set even after the env variable is removed (however users can then change the value again).&lt;/p&gt;

&lt;p&gt;If a parameter is overwritten by an environment variable, its form field will be disabled in the default generated WebUI, so that users can see that the value is enforced by the environment variable and can not be changed via the WebUI.&lt;/p&gt;

&lt;p&gt;A limitation of this system is that you can still change the value of a settings parameter in your code, even if it is overwritten by an environment variable. The changes will also be used in other parts of the application during the request. It is just that these changes do not get persisted, meaning that if you reload the settings from the storage, the value of the environment variable will be used again. If you try to change settings parameters via direct access in you code, you might want to check if the parameter is overwritten by an environment variable (by using the &lt;code&gt;isEnvVarOverwritten&lt;/code&gt; method of the &lt;code&gt;SettingsManager&lt;/code&gt;), and if so, you might want to disable the possibility to change the parameter in your code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Environment variables mapper
&lt;/h3&gt;

&lt;p&gt;For many constellations, the type conversion via the env var processor works fine. However, in some cases where you have more complex parameter types, you need a more complex conversion logic. For these cases, you can use the &lt;code&gt;envVarMapper&lt;/code&gt; option of the &lt;code&gt;SettingsParameter&lt;/code&gt; attribute. This option specifies a callable, which is called with the value of the environment variable and must return the value to set as the parameter value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestSettings&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;SettingsParameter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;envVar&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'string:ENV_VAR3'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;envVarMapper&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'mapDateTimeEnv'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="nc"&gt;\DateTime&lt;/span&gt; &lt;span class="nv"&gt;$dateTimeParam&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;mapDateTimeEnv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;?string&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="nc"&gt;\DateTime&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;\DateTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;$value&lt;/code&gt; parameter passed, is the value retrieved from the environment variable, with env var processors applied, meaning that it not necessarily has to be a string. &lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;You can see that jbtronics/settings-bundle can support you with handling changes in the schema of settings, and how to map environment variables to settings parameters. This allows you to have a flexible configuration system, which can be used by users and server administrators alike.&lt;/p&gt;

&lt;p&gt;As always you can find more information in the &lt;a href="https://jbtronics.github.io/settings-bundle/" rel="noopener noreferrer"&gt;bundle documentation&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>symfony</category>
      <category>php</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>User-configurable settings in Symfony applications with jbtronics/settings-bundle (Part 2): Forms</title>
      <dc:creator>Jan Böhmer</dc:creator>
      <pubDate>Thu, 02 May 2024 12:09:45 +0000</pubDate>
      <link>https://dev.to/jbtronics/user-configurable-settings-in-symfony-applications-with-jbtronicssettings-bundle-part-2-1704</link>
      <guid>https://dev.to/jbtronics/user-configurable-settings-in-symfony-applications-with-jbtronicssettings-bundle-part-2-1704</guid>
      <description>&lt;p&gt;After the &lt;a href="https://dev.to/jbtronics/user-changeable-settings-management-in-symfony-applications-with-jbtronicssettings-bundle-part-1-448d"&gt;first blog post of this series&lt;/a&gt; explained the basic concepts of the settings-bundle, this blog post will show how to use jbtronics/settings-bundle to create WebUI forms&lt;br&gt;
to change the settings.&lt;/p&gt;
&lt;h2&gt;
  
  
  Creating forms for settings
&lt;/h2&gt;

&lt;p&gt;Remember the appearance settings from the last blog post? We will now create a form to change these settings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="c1"&gt;// src/Settings/AppearanceSettings.php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Settings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Jbtronics\SettingsBundle\Settings\Settings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Jbtronics\SettingsBundle\Settings\SettingsParameter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Jbtronics\SettingsBundle\Storage\JSONFileStorageAdapter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Jbtronics\SettingsBundle\Settings\SettingsTrait&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\Validator\Constraints&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nc"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="na"&gt;#[Settings(storageAdapter: JSONFileStorageAdapter::class)]&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppearanceSettings&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="na"&gt;#[SettingsParameter]&lt;/span&gt;
    &lt;span class="na"&gt;#[Assert\Language]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$language&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'en'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="na"&gt;#[SettingsParameter]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nv"&gt;$darkMode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="na"&gt;#[SettingsParameter]&lt;/span&gt;
    &lt;span class="na"&gt;#[Assert\Range(min: 12, max: 24)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$fontSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="na"&gt;#[SettingsParameter()]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;MyTheme&lt;/span&gt; &lt;span class="nv"&gt;$theme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MyTheme&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;MODERN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To create forms for settings, you can use the &lt;code&gt;SettingsFormFactoryInterface&lt;/code&gt; service. Its &lt;code&gt;createSettingsFormBuilder()&lt;/code&gt; method takes a settings instance and returns a form builder, containing a form field for each settings parameter. As it is still a form builder you can add your own additional fields or modify the form as you like. For example, you probably want to add a submit button, so that the user can save the settings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="c1"&gt;// src/Controller/SettingsController.php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Controller&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Settings\AppearanceSettings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Jbtronics\SettingsBundle\Form\SettingsFormFactoryInterface&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Bundle\FrameworkBundle\Controller\AbstractController&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SettingsController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;AbstractController&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;SettingsFormFactoryInterface&lt;/span&gt; &lt;span class="nv"&gt;$settingsFormFactory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;SettingsManager&lt;/span&gt; &lt;span class="nv"&gt;$settingsManager&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;appearance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;AppearanceSettings&lt;/span&gt; &lt;span class="nv"&gt;$settings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;//Create a builder for the settings form&lt;/span&gt;
        &lt;span class="nv"&gt;$builder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$settingsFormFactory&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;createSettingsFormBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$settings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getForm&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;//Modify the form: Add a submit button, so we can save the form&lt;/span&gt;
        &lt;span class="nv"&gt;$builder&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'submit'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;SubmitType&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$builder&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getForm&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nv"&gt;$form&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;handleRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;//If the form is submitted and valid, save the settings&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$form&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;isSubmitted&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;$form&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;isValid&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$settingsManager&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$settings&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'settings/appearance.html.twig'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'form'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$form&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;createView&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The settings instance is bound to the form so that the form fields are prefilled with the current settings and changes to the form are reflected in the settings instance. If the form was submitted you then just need to call the &lt;code&gt;save()&lt;/code&gt; method of the &lt;code&gt;SettingsManager&lt;/code&gt; to save the settings to the storage. However this approach of directly using the global settings instance in the controller has it disadvantages, which will be discussed (and solved) later. For now we will just use this approach to keep things simple.&lt;/p&gt;

&lt;h2&gt;
  
  
  Customizing the form
&lt;/h2&gt;

&lt;p&gt;If you write a twig template, which renders the form and view the result, you will notice that for the &lt;code&gt;$darkMode&lt;/code&gt; parameter a Checkbox was chosen, and for the &lt;code&gt;$fontSize&lt;/code&gt; parameter a NumberType input field was chosen. This is because settings-bundle detected the type of the parameter based on the property type hint and assigned the &lt;code&gt;BoolType&lt;/code&gt; and &lt;code&gt;IntType&lt;/code&gt; parameter types to these parameters. These parameter types are not only used to convert the property values to a storable format and vice versa, but they can also make presets for the rendering of the form fields. Not every parameter type needs to do that, but at least the built-in parameters try to make a good assumption of what form type and what form options to use, to get a nice-looking form, without much customizing.&lt;/p&gt;

&lt;p&gt;However, in some cases the presets are not what you want and you might want to change the form type and/or form options. You can do this by utilizing the &lt;code&gt;formType&lt;/code&gt; and &lt;code&gt;formOptions&lt;/code&gt; options on the &lt;code&gt;SettingsParameter&lt;/code&gt; attribute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="na"&gt;#[SettingsParameter(formType: LanguageType::class, formOptions: ['choice_self_translation' =&amp;gt; true])]&lt;/span&gt;
&lt;span class="na"&gt;#[Assert\Language]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$language&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'en'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to specify the form label and a description of the form field (which will be shown as help text), you can use the &lt;code&gt;label&lt;/code&gt; and &lt;code&gt;description&lt;/code&gt; options of the attribute.&lt;br&gt;
This also has the advantage, that this allows to utilize this information in other contexts, too.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="na"&gt;#[SettingsParameter(label: "Font size", description: "The font size in pixels to use (between 12 and 24)")]&lt;/span&gt;
&lt;span class="na"&gt;#[Assert\Range(min: 12, max: 24)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$fontSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The strings given there are passed to Symfony form component like normal, meaning that these keys also get translated if you use the translator service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Forms for multiple settings and embedded settings
&lt;/h2&gt;

&lt;p&gt;If you have multiple settings classes, you wanna create a common form for them, you can use the &lt;code&gt;createMultiSettingsFormBuilder()&lt;/code&gt; method of the &lt;code&gt;SettingsFormFactoryInterface&lt;/code&gt; service, &lt;br&gt;
which takes an array of settings instances and returns a form builder subforms for each settings instance. This sub-form builder then contains a form field for each settings parameter of each settings instance.&lt;/p&gt;

&lt;p&gt;However, for more complex setting structures you might want to use a hierarchical organization of settings. This can be achieved by using so-called settings embeds. If you have a settings class you can add a property to it and mark it with the &lt;code&gt;#[EmbeddedSettings]&lt;/code&gt; attribute. The settings-bundle will then fill this property with the instance of the other settings class so that you can access the settings of the embedded settings class through the parent settings class. This is useful if you have settings that are only relevant in a certain context or if you want to group settings. If you worry about performance, do not worry. The injecting embedded settings are lazy-loaded, so that they only get initialized when you access them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Settings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Jbtronics\SettingsBundle\Settings\Settings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Jbtronics\SettingsBundle\Settings\SettingsParameter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Jbtronics\SettingsBundle\Settings\EbeddedSettings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="na"&gt;#[Settings]&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppSettings&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="na"&gt;#[SettingsParameter]&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$appName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'My App'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="na"&gt;#[SettingsParameter]&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$appVersion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'1.0.0'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;//The instance of the AppearanceSettings class will be injected here by the settings-bundle&lt;/span&gt;
        &lt;span class="na"&gt;#[EmbeddedSettings]&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;AppearanceSettings&lt;/span&gt; &lt;span class="nv"&gt;$appearanceSettings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Settings-bundle will automatically detect the class to inject based on the property type hint. If you want to specify the class to inject, you can use the &lt;code&gt;target&lt;/code&gt; option of the &lt;code&gt;EmbeddedSettings&lt;/code&gt; attribute, but normally everything should be automatically detected. The embedded settings are the same instance as the global settings object, so you can read and modify the settings of the embedded settings class in the same way as the global settings object. &lt;/p&gt;

&lt;p&gt;There is no limit on how deep you can nest embedded settings, so you can create complex settings structures with multiple levels of embedded settings. However, you should be careful with this, as it can make the settings structure hard to understand and maintain. But this also allows you to split up your settings for various parts of your application in a way, that each service only gets the settings it requires, while you can still have a common settings object which contains all settings, for easy access and modification by users.&lt;/p&gt;

&lt;p&gt;In principle, settings-bundle can even handle circular embedded settings (A embeds B, B embeds A), but you should avoid this, as you can not easily convert such structures to forms.&lt;/p&gt;

&lt;p&gt;The methods of the SettingsManager, normally affect the whole cascade of settings, so if you call save(), reload(), etc. on the top level settings object, it will also affect all embedded settings. You can control this behavior via the &lt;code&gt;cascade&lt;/code&gt; parameter of the methods of the SettingsManager. If you set it to false, the method will only affect the settings object you called the method on.&lt;/p&gt;

&lt;p&gt;If you call the &lt;code&gt;createSettingsFormBuilder&lt;/code&gt; method on a settings object with embedded settings, the return form builder will contain the nested structure of the settings. For each embedded settings object, a subform will be created, allowing you to easily create forms for complex settings structures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Only showing a subset of settings in a form
&lt;/h2&gt;

&lt;p&gt;Maybe you sometimes want to only show some settings parameters in a form, not all. For example, if you wanna offer a "simple" settings form, where more advanced settings are hidden.&lt;br&gt;
You can achieve this by using the &lt;code&gt;groups&lt;/code&gt; option on the &lt;code&gt;#[SettingsParameter]&lt;/code&gt; (and &lt;code&gt;#[EmbeddedSettings]&lt;/code&gt;) attributes. These groups work very similar to the groups of the symfony/serializer group. You can specify a list of groups a parameter belongs to and then you can specify the groups you want to include in the form builder. On &lt;code&gt;#[Settings]&lt;/code&gt; attribute you can specify the default groups for a settings class when it has no explicitly specified groups.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Settings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Jbtronics\SettingsBundle\Settings\Settings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Jbtronics\SettingsBundle\Settings\SettingsParameter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="na"&gt;#[Settings(groups: ['simple', 'advanced'])]&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SecuritySettings&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;SettingsParameter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;groups&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"advanced"&lt;/span&gt;&lt;span class="p"&gt;])]&lt;/span&gt; &lt;span class="c1"&gt;//Override the default groups of the settings class&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'mysecret'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="na"&gt;#[SettingsParameter(groups: ["simple", "advanced"])]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nv"&gt;$enforce2FA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;SettingsParameter&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;//This parameter will be in the "simple" and "advanced" group, as inherited from default&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nv"&gt;$limitLoginAttempts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you now call the &lt;code&gt;createSettingsFormBuilder&lt;/code&gt; with the &lt;code&gt;groups&lt;/code&gt; option, to specify which groups should be included. By default (if the value is null), then all groups are included.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;
&lt;span class="nv"&gt;$settings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;settingsManager&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SecuritySettings&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;//This form builder includes all parameters of the SecuritySettings class&lt;/span&gt;
&lt;span class="nv"&gt;$builder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$settingsFormFactory&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;createSettingsFormBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$settings&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;//This form builder only includes the parameters of the "simple" group&lt;/span&gt;
&lt;span class="c1"&gt;//Therefore the "secret" parameter is not included&lt;/span&gt;
&lt;span class="nv"&gt;$builder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$settingsFormFactory&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;createSettingsFormBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$settings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'simple'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;groups&lt;/code&gt; option is an OR condition, so a parameter is shown if it is in at least one of the specified groups. Embedded settings are also only shown if they are in at least one of the specified groups (you can specify the groups of embedded settings in the same way as for normal settings).&lt;/p&gt;

&lt;h2&gt;
  
  
  Working with temporary copies of settings
&lt;/h2&gt;

&lt;p&gt;One big problem here is how Symfony Forms work: They apply the changes directly to the object you pass to them and do the validation afterward. And even if the validation fails, the changes are still there in the object for the remainder of the request. This is a problem if other parts of your application depend on the settings object, as they will see the changes,&lt;br&gt;
even if the validation failed. Depending on your application, invalid values can cause undesired behavior or exceptions.&lt;/p&gt;

&lt;p&gt;To fix this problem, you can retrieve a temporary copy of a settings object, which is completely independent of the global settings object. Therefore the form can modify it as it wants&lt;br&gt;
without affecting the global settings object with invalid values. You can create it by calling the &lt;code&gt;createTemporaryCopy()&lt;/code&gt; method on the &lt;code&gt;SettingsManager&lt;/code&gt; service. The settings manager creates a new instance and copies all parameter values over to it. If you have embedded settings, the embedded settings are also copied, so that you get a complete deep copy of the settings object, containing the old values.&lt;/p&gt;

&lt;p&gt;To save the changes to storage, you first need to merge the temporary copy back to the global settings object. This can be done by calling the &lt;code&gt;mergeTemporaryCopy()&lt;/code&gt; method on the &lt;code&gt;SettingsManager&lt;/code&gt; service. It will be verified that the temporary copy is valid and otherwise an exception is thrown so that no invalid data can be merged back. All parameter values of the temporary copy are then copied back to the global settings object so that the changes can be saved to storage. If you have embedded settings, the embedded settings are also merged back by default, you can control this behavior with the &lt;code&gt;cascade&lt;/code&gt; option, however.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="c1"&gt;// src/Controller/SettingsController.php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Controller&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Settings\AppearanceSettings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Jbtronics\SettingsBundle\Form\SettingsFormFactoryInterface&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Bundle\FrameworkBundle\Controller\AbstractController&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SettingsController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;AbstractController&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;SettingsFormFactoryInterface&lt;/span&gt; &lt;span class="nv"&gt;$settingsFormFactory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;SettingsManager&lt;/span&gt; &lt;span class="nv"&gt;$settingsManager&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;appearance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;//Create a temporary copy of the settings&lt;/span&gt;
        &lt;span class="nv"&gt;$clone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$settingsManager&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;createTemporaryCopy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AppearanceSettings&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;//Create a builder for the settings form&lt;/span&gt;
        &lt;span class="nv"&gt;$builder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$settingsFormFactory&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;createSettingsFormBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$clone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getForm&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;//Modify the form: Add a submit button, so we can save the form&lt;/span&gt;
        &lt;span class="nv"&gt;$builder&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'submit'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;SubmitType&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$builder&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getForm&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nv"&gt;$form&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;handleRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;//If the form is submitted and valid, save the settings&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$form&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;isSubmitted&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;$form&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;isValid&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;//Merge the temporary copy back to the global settings object, so that we can save the changes&lt;/span&gt;
            &lt;span class="nv"&gt;$settingsManager&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;mergeTemporaryCopy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$clone&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nv"&gt;$settingsManager&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'settings/appearance.html.twig'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'form'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$form&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;createView&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you do not use "simple" data types like strings, bools, enums, etc. as settings parameters, but more complex objects, which are not easily cloneable, you maybe need to implement&lt;br&gt;
some custom copy and merge behavior for the settings object. You can do this by implementing the &lt;code&gt;CloneAndMergeAwareSettingsInterface&lt;/code&gt; interface on your settings class. See the &lt;a href="https://jbtronics.github.io/settings-bundle/usage/using_setting.html#customizing-the-clone-and-merge-behavior" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; for more information.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;You can see that jbtronics/settings-bundle allows you to easily build settings forms, even for complex use cases. You can find more information about form generation and some more&lt;br&gt;
advanced things, like how to implement your own settings parameter types with form presets, in the &lt;a href="https://jbtronics.github.io/settings-bundle/usage/forms.html" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There you can also already find the documentation about more advanced features like settings versioning/migrations and how to combine settings with environment variables for easy automatic deployment of your application. That will be the topic of the next blog post of this series.&lt;/p&gt;

</description>
      <category>symfony</category>
      <category>php</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>User-configurable settings in Symfony applications with jbtronics/settings-bundle (Part 1)</title>
      <dc:creator>Jan Böhmer</dc:creator>
      <pubDate>Mon, 08 Apr 2024 09:57:31 +0000</pubDate>
      <link>https://dev.to/jbtronics/user-changeable-settings-management-in-symfony-applications-with-jbtronicssettings-bundle-part-1-448d</link>
      <guid>https://dev.to/jbtronics/user-changeable-settings-management-in-symfony-applications-with-jbtronicssettings-bundle-part-1-448d</guid>
      <description>&lt;p&gt;Symfony offers vast &lt;a href="https://symfony.com/doc/current/configuration.html" rel="noopener noreferrer"&gt;configuration possibilities&lt;/a&gt; using container parameters and environment variables. However, these are only useful for configuration intended to be configured by developers, as they require changing various text files on the server and might even some recompilation of the container (by running a &lt;code&gt;cache:clear&lt;/code&gt; command).&lt;br&gt;
If you want to allow end-users to configure various aspects of an application, they will need some kind of WebUI to do so (like, for example, WordPress does). However, there is no good way to change Symfony's container parameters or env variables (from various sources) from inside a Symfony application itself.&lt;/p&gt;

&lt;p&gt;For this reason, if you want to have user-configurable settings you have to store and work with them independently. For this there are already several Symfony bundles, with the most popular being probably &lt;a href="https://github.com/sherlockode/configuration-bundle" rel="noopener noreferrer"&gt;sherlockode/configuration-bundle&lt;/a&gt;. However all of these bundles are more or less a simple key-value store, where a configuration can be retrieved via something like &lt;code&gt;$parameterManager-&amp;gt;get('contact_email')&lt;/code&gt;. However this is not really type-safe and DX-friendly, as the return type of the &lt;code&gt;get&lt;/code&gt; method is not obvious and you have to check yourself which keys are available and auto-complete in common IDEs is not possible.&lt;/p&gt;
&lt;h2&gt;
  
  
  Class-based settings
&lt;/h2&gt;

&lt;p&gt;To solve these problems, the MIT-licensed &lt;a href="https://github.com/jbtronics/settings-bundle" rel="noopener noreferrer"&gt;jbtronics/settings-bundle&lt;/a&gt; was created. The basic idea is that settings parameters are not organized in a simple key-value storage, but are based around classes. Each (settings) class represents a set of parameters, which are somehow logically connected.&lt;/p&gt;

&lt;p&gt;After you have installed the bundle with composer (&lt;code&gt;composer require jbtronics/settings-bundle&lt;/code&gt;), you can create a new settings class in the &lt;code&gt;src/Settings&lt;/code&gt; directory of your symfony application.&lt;/p&gt;

&lt;p&gt;For example, if you want to allow users to configure the appearance settings of your application, you might create a &lt;code&gt;AppearanceSettings&lt;/code&gt; class like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="c1"&gt;// src/Settings/AppearanceSettings.php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Settings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Jbtronics\SettingsBundle\Settings\Settings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Jbtronics\SettingsBundle\Settings\SettingsParameter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Jbtronics\SettingsBundle\Storage\JSONFileStorageAdapter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Jbtronics\SettingsBundle\Settings\SettingsTrait&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\Validator\Constraints&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nc"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="na"&gt;#[Settings(storageAdapter: JSONFileStorageAdapter::class)]&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppearanceSettings&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="na"&gt;#[SettingsParameter]&lt;/span&gt;
    &lt;span class="na"&gt;#[Assert\Language]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$language&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'en'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="na"&gt;#[SettingsParameter]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nv"&gt;$darkMode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="na"&gt;#[SettingsParameter]&lt;/span&gt;
    &lt;span class="na"&gt;#[Assert\Range(min: 12, max: 24)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$fontSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="na"&gt;#[SettingsParameter()]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;MyTheme&lt;/span&gt; &lt;span class="nv"&gt;$theme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MyTheme&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;MODERN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;where &lt;code&gt;MyTheme&lt;/code&gt; is an enum class like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Settings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Jbtronics\SettingsBundle\Settings\Enum&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;enum&lt;/span&gt; &lt;span class="nc"&gt;MyTheme&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;MODERN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'modern'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;TRADITIONAL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'traditional'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;FANCY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'fancy'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, the &lt;code&gt;AppearanceSettings&lt;/code&gt; class is a normal PHP class with some properties. They do not even need to be public, you could also define some getter/setter methods to access them. But we omit that here to make the example more concise.&lt;/p&gt;

&lt;p&gt;The only thing special about this class, are the attributes &lt;code&gt;#[Settings]&lt;/code&gt; and &lt;code&gt;#[SettingsParameter]&lt;/code&gt;. The &lt;code&gt;#[Settings]&lt;/code&gt; attribute marks this class as a settings class, which means that it will be automatically registered with the bundle. The &lt;code&gt;storageAdapter&lt;/code&gt; attribute specified, which storage backend will be saved to actually store the settings. There are multiple storage adapters available, for saving settings in files in various formats or a database. For simplicity, we will use the &lt;code&gt;JSONFileStorageAdapter&lt;/code&gt; here, which will save the settings in a JSON file in the &lt;code&gt;var/settings&lt;/code&gt; directory of your Symfony application.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;#[SettingsParameter]&lt;/code&gt; attribute marks a property as a settings parameter, which means that it will be saved and loaded by the bundle. If you retrieve an instance of the &lt;code&gt;AppearanceSettings&lt;/code&gt; via the bundle, then these properties are automatically filled with the values from the storage backend.&lt;/p&gt;

&lt;p&gt;As many more complex datatypes (objects, etc.) can not be directly saved to the storage backend, the PHP data is normalized/denormalized by so-called &lt;code&gt;ParameterTypes&lt;/code&gt;, which you can set as an option in the &lt;code&gt;SettingsParameter&lt;/code&gt; attribute. However, for elementary types like &lt;code&gt;string&lt;/code&gt;, &lt;code&gt;int&lt;/code&gt;, &lt;code&gt;bool&lt;/code&gt;, etc., and even for enums, the correct parameter type is automatically detected by the bundle, based on the type declaration of the property and you don't need to specify it manually.&lt;/p&gt;

&lt;p&gt;If you have a more complex datatype, like a custom object, you can easily create your own &lt;code&gt;ParameterType&lt;/code&gt; class, which implements the &lt;code&gt;ParameterTypeInterface&lt;/code&gt; and then specify it in the &lt;code&gt;SettingsParameter&lt;/code&gt; attribute, so that the bundle knows how to normalize/denormalize this datatype.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting current settings
&lt;/h2&gt;

&lt;p&gt;If you wanna read the configured value of a parameter, you can do it by directly accessing the property (or the getter) of the settings object. However, you can not just do a &lt;code&gt;new AppearanceSettings()&lt;/code&gt;, as this would create a new instance of the class with the default values. Instead, you need to retrieve a settings object from the bundle.&lt;/p&gt;

&lt;p&gt;However as the bundle registers all settings classes as services by default, you can simply inject the settings class as a dependency in your controller or service. The bundle will automatically load the settings from the storage and fill the properties of the object with the data. If no settings are found in the storage, then the default values of the properties are used.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; 
&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Controller&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Settings\AppearanceSettings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;AbstractController&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;AppearanceSettings&lt;/span&gt; &lt;span class="nv"&gt;$appearanceSettings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// $appearanceSettings is already filled with the data from the storage&lt;/span&gt;
        &lt;span class="c1"&gt;// To read the values, you can simply access the properties/fields of the object&lt;/span&gt;
        &lt;span class="nv"&gt;$language&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$appearanceSettings&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$darkMode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$appearanceSettings&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;darkMode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$fontSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$appearanceSettings&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;fontSize&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$theme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$appearanceSettings&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cool thing is, that the properties are type-hinted, so you get auto-completion in your IDE and you can be sure that the value is of the correct type. If you try to set a value of the wrong type, you will get an exception. Also if you retrieve the settings object like this then you have no direct dependency on the settings bundle and you can easily replace the settings instance with a mock in your tests.&lt;/p&gt;

&lt;p&gt;If you worry about the performance of loading the settings from the storage every time your services get initialized, you do not have to. The bundle actually replaces the settings object with a lazy proxy, which only really loads the settings from the storage when data is actually accessed. So if your code never actually requires some settings data, then the settings are never loaded from the storage too. &lt;/p&gt;

&lt;p&gt;If you want to use settings in a twig template, you can either pass the settings object to the template or you can use the &lt;code&gt;settings_instance&lt;/code&gt; twig function defined by the bundle. This function returns the settings object of a given class, which you can then use in your template. The function expects either the fully qualified class name of the settings class, which is not so easy to type in twig, or the so-called short Name of the class. This is by default the class name without the namespace, but you can also specify a custom short name for a settings class by using the &lt;code&gt;shortName&lt;/code&gt; attribute of the &lt;code&gt;Settings&lt;/code&gt; attribute.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight twig"&gt;&lt;code&gt;&lt;span class="c"&gt;{# @var settings \App\Settings\AppearanceSettings #}&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;settings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;settings_instance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'\\App\\Settings\\AppearanceSettings'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;

&lt;span class="c"&gt;{# or directly #}&lt;/span&gt;
&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;settings_instance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'appearance'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;language&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Changing settings
&lt;/h2&gt;

&lt;p&gt;To change the value of a settings parameter, you can simply set the property of the settings or use any setter method you have defined:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Controller&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Settings\AppearanceSettings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;AbstractController&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;changeSettings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;AppearanceSettings&lt;/span&gt; &lt;span class="nv"&gt;$appearanceSettings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$appearanceSettings&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;language&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'de'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$appearanceSettings&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;darkMode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$appearanceSettings&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;fontSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$appearanceSettings&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;theme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MyTheme&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;FANCY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All services share the same instance of the settings object, so if you change a value in one service or controller, then it will directly be reflected in all other services and controllers.&lt;br&gt;
So as long as your code always accesses the property or getter of the settings object directly and does not cache the value in a local variable, you can be sure that always the current value is used.&lt;/p&gt;

&lt;p&gt;However, this only affects the instance of the settings object in the current request. If you want to persist the changes to the storage, you have to explicitly save the settings object.&lt;/p&gt;
&lt;h2&gt;
  
  
  Persisting changes
&lt;/h2&gt;

&lt;p&gt;If you want to interact explicitly with the bundle, then you can use the &lt;code&gt;SettingsManagerInterface&lt;/code&gt; service. This service allows you to retrieve, save, and reset settings objects.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Controller&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Settings\AppearanceSettings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Jbtronics\SettingsBundle\Manager\SettingsManagerInterface&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;AbstractController&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;changeSettings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;SettingsManagerInterface&lt;/span&gt; &lt;span class="nv"&gt;$settingsManager&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;//Instead of dependency injection, you can also use the get method of the settings manager to retrieve the settings instance&lt;/span&gt;
        &lt;span class="nv"&gt;$appearanceSettings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$settingsManager&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AppearanceSettings&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Change the settings&lt;/span&gt;
        &lt;span class="nv"&gt;$appearanceSettings&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;language&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'de'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$appearanceSettings&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;darkMode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$appearanceSettings&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;fontSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$appearanceSettings&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;theme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MyTheme&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;FANCY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// Persist the settings to storage&lt;/span&gt;
        &lt;span class="nv"&gt;$settingsManager&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$appearanceSettings&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before writing the settings to the storage, the bundle will also validate the settings object, using the Symfony validator component. If the settings object is not valid, according to the defined constraints, then an exception is thrown and the settings are not saved.&lt;/p&gt;

&lt;p&gt;If you somehow end up in a situation where you want to discard the changes you made to the settings object, you can simply call the &lt;code&gt;reload&lt;/code&gt; method of the &lt;code&gt;SettingsManagerInterface&lt;/code&gt; service, which will reload the settings from the storage and discard all changes you made to the object. Only the parameter properties of the object are reset, the instance itself is not replaced, so that all references to the object in some other places of your code are still valid.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Controller&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Settings\AppearanceSettings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Jbtronics\SettingsBundle\Manager\SettingsManagerInterface&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;AbstractController&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;changeSettings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;SettingsManagerInterface&lt;/span&gt; &lt;span class="nv"&gt;$settingsManager&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$appearanceSettings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$settingsManager&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AppearanceSettings&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Change the settings&lt;/span&gt;
        &lt;span class="nv"&gt;$appearanceSettings&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;language&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'de'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$appearanceSettings&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;darkMode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$appearanceSettings&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;fontSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$appearanceSettings&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;theme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MyTheme&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;FANCY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// Discard the changes&lt;/span&gt;
        &lt;span class="nv"&gt;$settingsManager&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;reload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$appearanceSettings&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to reset the settings to their default values, defined in the code, you can use the &lt;code&gt;resetToDefaultValues&lt;/code&gt; method of the &lt;code&gt;SettingsManagerInterface&lt;/code&gt; service. Like the &lt;code&gt;reload&lt;/code&gt; method, this method will also maintain the instance of the settings object.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Controller&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Settings\AppearanceSettings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Jbtronics\SettingsBundle\Manager\SettingsManagerInterface&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;AbstractController&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;resetSettings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;SettingsManagerInterface&lt;/span&gt; &lt;span class="nv"&gt;$settingsManager&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$appearanceSettings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$settingsManager&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AppearanceSettings&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Reset the settings to their default values&lt;/span&gt;
        &lt;span class="nv"&gt;$settingsManager&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;resetToDefaultValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$appearanceSettings&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Profiler
&lt;/h2&gt;

&lt;p&gt;If you are using Symfony profiler, you should be able to see a new "Settings" panel in the profiler. It shows all settings classes with their parameters defined in your application and their resolved metadata (like which storage adapter to use, which parameter types should be used for mapping, etc.). You can also see the current values of the settings objects and their parameters to the time of the request, which is useful for debugging.&lt;/p&gt;

&lt;h2&gt;
  
  
  And many more
&lt;/h2&gt;

&lt;p&gt;The bundle has many more features, like automatic form generation for easy changeable settings in the WebUI, easy versioning of settings with migrations, and much more.&lt;/p&gt;

&lt;p&gt;These features will be explained more in the next parts of this tutorial. If you wanna check them out now, you can take a look at the &lt;a href="https://jbtronics.github.io/settings-bundle/" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; of the bundle, where every feature and option of the bundle is explained in detail.&lt;/p&gt;

</description>
      <category>symfony</category>
      <category>php</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
