Heim > Artikel > Backend-Entwicklung > Vom Benutzer konfigurierbare Einstellungen in Symfony-Anwendungen mit jbtronics/settings-bundle (Teilmigrationen und Umgebungsvariablen).
In den ersten beiden Teilen dieser Serie wurden die Grundkonzepte des Einstellungspakets vorgestellt und wie man damit schöne, vom Benutzer konfigurierbare Einstellungen in Symfony-Anwendungen erstellt.
In diesem Teil erfahren Sie, wie Sie Ihre Einstellungen versionieren und zwischen ihnen migrieren. Darüber hinaus erfahren Sie, wie Sie Umgebungsvariablen mit Einstellungen kombinieren.
Im Laufe der Zeit wird sich Ihre Anwendung weiterentwickeln, ebenso wie Ihre Einstellungen. Das bedeutet, dass im Laufe der Zeit neue Parameter zu den Einstellungen hinzugefügt, alte entfernt und bestehende geändert werden. Um dies zu bewältigen, bietet das Einstellungspaket einen Versionierungs- und Migrationsmechanismus, der Ihnen den größten Teil der Arbeit abnimmt.
Nehmen wir an, Sie haben eine einfache Einstellungsklasse wie diese:
namespace App\Settings; use Jbtronics\SettingsBundle\Settings\Settings; use Jbtronics\SettingsBundle\Settings\SettingsParameter; #[Settings] class TestSettings { #[SettingsParameter] public string $email = 'test@invalid'; #[SettingsParameter] public int $baz = 42; }
Diese Einstellungen wurden bereits seit einiger Zeit in Ihrer Anwendung verwendet und Benutzer haben ihre Anpassungen bereits darin gespeichert. Wenn Sie den Einstellungen lediglich einen neuen Parameter hinzufügen möchten, können Sie dies tun, indem Sie einfach eine neue Eigenschaft zur Klasse hinzufügen. Dies funktioniert problemlos. Der neue Parameter wird mit dem Standardwert initialisiert und Benutzer können ihn nach Belieben ändern:
#[Settings] class TestSettings { #[SettingsParameter] public string $email = 'test@invalid'; #[SettingsParameter] public int $baz = 42; #[SettingsParameter] public bool $qux = true; }
Das Entfernen eines Parameters funktioniert ähnlich. Wenn Sie eine Eigenschaft aus der Klasse entfernen, ignoriert das Einstellungspaket die dafür vorhandenen Werte und löscht sie, wenn die Einstellungen das nächste Mal gespeichert werden.
Schwieriger wird es jedoch, wenn Sie ein Feld umbenennen oder, noch komplexer, seinen Typ oder die genaue Art der Datenspeicherung ändern möchten. Um bestehende Benutzeranpassungen nicht zu verlieren, müssen Sie angeben, wie zwischen den verschiedenen Darstellungen der Einstellungen konvertiert werden soll. Das Settings-Bundle kann Sie dabei unterstützen, indem es einen Rahmen für Migrationen bereitstellt.
Angenommen, Sie möchten Ihre Einstellungsklasse so ändern, dass Sie jetzt mehrere E-Mail-Adressen haben können. Außerdem möchten Sie die Indizierung des baz-Parameters ändern, sodass er nicht bei 0, sondern bei 1 beginnt, was bedeutet, dass alle vorhandenen Werte um 1 erhöht werden sollen. Am Ende sollte Ihre Einstellungsklasse so aussehen:
namespace App\Settings; use Jbtronics\SettingsBundle\Settings\Settings; use Jbtronics\SettingsBundle\Settings\SettingsParameter; #[Settings(version: self::VERSION, migrationService: TestSettingsMigration::class)] class TestSettings { public const VERSION = 1; #[SettingsParameter(type: ArrayType::class, options: ['type' => StringType::class])] public array $email = ['test@invalid']; #[SettingsParameter] //Now with different indexing public int $baz = 43; }
Die Testeinstellungsklasse hat jetzt die neue vorgesehene Struktur und kann in der Anwendung verwendet werden. Das Einstellungspaket weiß jedoch nicht, wie es die vorhandenen Daten in die neue Struktur konvertieren soll. Hier kommt es zu Migrationen
ins Spiel kommen.
Sie können sehen, dass für das Einstellungsattribut jetzt die Versionsoption und die Option migrationService angegeben sind:
Die Versionsoption gibt die neueste Schemaversion der Einstellungen an und ist nur eine Ganzzahl (größer Null), die jedes Mal erhöht wird, wenn Sie die Struktur der Einstellungsklasse ändern. Sie können mit 1 beginnen und ihn jedes Mal erhöhen, wenn Sie die Struktur der Einstellungsklasse ändern. Sie können die Versionsnummer direkt in das Attribut einfügen oder wie im Beispiel gezeigt eine Konstante dafür definieren, was den Vorteil hat, dass Sie die aktuelle Version einfach von außerhalb der Klasse abrufen können.
Die zweite Neuerung ist die Option migrationService. Dies gibt die Serviceklasse an, die die Datenmigration tatsächlich durchführt. Der migrationService muss das SettingsMigrationInterface implementieren, das eine Migrationsfunktion angibt, die für die Migration zwischen zwei bestimmten Versionen der Daten verantwortlich ist.
In den meisten Fällen möchten Sie schrittweise Migrationen zwischen den Versionen durchführen (das heißt, Sie migrieren 1 -> 2, dann 2 -> 3 usw., statt 1 -> 3 direkt, um Codeduplizierung zu vermeiden). In dieser Situation ist es einfacher, die SettingsMigration-Klasse zu erweitern. Wenn Sie diese abstrakte Klasse verwenden, könnte Ihr Migrationsdienst so aussehen:
namespace App\Settings\Migrations; use Jbtronics\SettingsBundle\Migrations\SettingsMigration; class TestSettingsMigration extends SettingsMigration { /** * This method is called automatically by the migration class and handles * migration of version 0 (non versioned settings) to version 1. */ public function migrateToVersion1(array $data, SettingsMetadata $metadata): array { /* * $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. */ //If the email parameter was set, convert it to an array if (isset($data['email'])) { $data['email'] = [$data['email']]; } //Increment the baz parameter, if it was set if (isset($data['baz'])) { $data['baz']++; } //Return the new data return $data; } /** * This method is called, to handle migration from version 1 to version 2. */ public function migrateToVersion2(array $data, SettingsMetadata $metadata): array { //Perform some more migrations... return $data; } }
Der Migrationsdienst enthält verschiedene Methoden in der Form migrateToVersionXX(), die von der Klasse automatisch aufgerufen werden, wenn die Einstellungen von Version XX-1 auf Version XX migriert werden. Die Methode empfängt die Daten in normalisierter Form und die Metadaten der Einstellungsklasse und muss die Daten in normalisierter Form zurückgeben, die dann an die Parametertypkonverter übergeben werden. Wenn Sie explizit angeben möchten, welche Funktionen für welche Version aufgerufen werden, können Sie die Methode „resolveStepHandler“ überschreiben, die den für eine bestimmte Version zu verwendenden Abschluss zurückgibt.
Da die vorhandenen Daten noch keine Version hatten, wird davon ausgegangen, dass es sich um Version 0 handelte. Wenn diese Dateneinstellungen auftreten, ruft das Bundle daher den Handler migrateToVersion1 auf, um von 0 auf die neueste Version 1 zu migrieren.
The old data from the storage is passed to the migration method (as $data) 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 $data array with the parameter name as key. You can then modify the data as you like and return it in the end.
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 getAsPHPValue() and setAsPHPValue() methods available in the SettingsClass (or in the PHPValueConverterTrait) useful. Or you call the ParameterTypes you need directly.
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.
Environment variables are one of the classic possibilities to configure a Symfony application. 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.
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.
This is done via the envVar option on the SettingsParameter attribute:
#[Settings] class TestSettings { #[SettingsParameter(envVar: 'APP_EMAIL')] public string $email = 'test@invalid'; #[SettingsParameter(envVar: 'int:APP_BAZ', envVarMode: EnvVarMode::OVERWRITE)] public int $baz = 42; #[SettingsParameter(envVar: 'bool:APP_TEST_SETTINGS_QUX', envVarMode: EnvVarMode::OVERWRITE_PERSIST)] public bool $qux = true; }
The envVar 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 env var processors to convert the string value of the env variable to the desired type (e.g. int:APP_BAZ, which converts the content of APP_BAZ to an int).
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.
The envVarMode option specifies how the environment variable should be handled. If no mode is specified, the mode EnvVarMode::INITIAL 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.
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 EnvVarMode::OVERWRITE and EnvVarMode::OVERWRITE_PERSIST 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 OVERWRITE_PERSIST 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).
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.
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 isEnvVarOverwritten method of the SettingsManager), and if so, you might want to disable the possibility to change the parameter in your code.
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 envVarMapper option of the SettingsParameter 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:
class TestSettings { #[SettingsParameter(envVar: 'string:ENV_VAR3', envVarMapper: [self::class, 'mapDateTimeEnv']) private ?\DateTime $dateTimeParam = null; public static function mapDateTimeEnv(?string $value): ?\DateTime { return $value ? new \DateTime($value) : null; } }
The $value 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.
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.
As always you can find more information in the bundle documentation.
Das obige ist der detaillierte Inhalt vonVom Benutzer konfigurierbare Einstellungen in Symfony-Anwendungen mit jbtronics/settings-bundle (Teilmigrationen und Umgebungsvariablen).. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!