Configuration


1Présentation

La configuration d'un projet utilisant Temma se fait en modifiant le fichier temma.php qui est placé dans le répertoire etc/, et qui comporte plusieurs sections.

Par défaut, Temma recherche un fichier nommé etc/temma.php, au format PHP (expliqué ci-après). Mais vous pouvez utiliser d'autres formats pour le fichier de configuration :

  • JSON (fichier etc/temma.json) : Format très répandu dans les projets informatiques. C'est le format historique de Temma, qui est toujours recommandé si vous souhaitez générer vos fichiers de configuration par un script.
  • YAML (fichier etc/temma.yaml) : Format de plus en plus utilisé, notamment par d'autres frameworks modernes.
  • NEON (fichier etc/temma.neon) : Format utilisé par le framework Nette et l'outil d'analyse statique de code PHPStan. Il est très similaire au format YAML.

Il est recommandé d'utiliser le format PHP, car il présente l'intérêt d'être mis en cache par OPcache (le cache d'OPcode qui évite de relire les fichiers PHP à chaque accès), accélérant ainsi les traitements. Il permet aussi des générations dynamiques de configuration.

Dans le répertoire etc/, vous pourrez trouver deux fichiers servant à illustrer la configuration d'un projet Temma :

  • temma-mini.php : Fichier minimal contenant les directives de base.
  • temma-full.php : Fichier présentant toutes les options disponibles.

2Fichier temma.php simple

Voici un exemple typique de fichier etc/temma.php minimal :

<?php

return [
    // Configuration de l'application
    'application' => [
        'dataSources' => [
            'db' => 'mysql://user:passwd@localhost/mybase'
        ],
        'rootController' => 'HomepageController'
    ],
    // Définition des niveaux de log
    'loglevels' => 'ERROR',
    // Définition des pages d'erreur
    'errorPages' => 'error404.html',
    // Données importées automatiquement comme variables de template
    'autoimport' => [
        'googleId' => 'azeazeaez',
        'googleAnalyticsId' => 'azeazeazeaze'
    ]
];

On peut y voir :

  • Ligne 7 : La définition du DSN qui permet de se connecter à la base de données.
  • Ligne 9 : Le nom du contrôleur racine, qui sera appelé quand quelqu'un se connectera sans spécifier de contrôleur.
  • Ligne 12 : Définition du seuil d'écriture des messages de log dans le fichier log/temma.log. Ici, avec le seuil "ERROR", seuls les messages avec les seuils ERROR et CRIT seront effectivement écrits dans le fichier de log.
  • Ligne 14 : Définition de la page à afficher si une erreur survient. La page en question doivent être situées dans le répertoire www/ du projet.
  • Les variables de template automatiquement importées.
    • Ligne 17 : Identifiant Google, disponible dans les templates avec {$conf.googleId} et dans le contrôleur avec $this['conf']['googleId'].
    • Ligne 18 : Identifiant Google Analytics, disponible dans les templates avec {$conf.googleAnalyticsId} et dans le contrôleur avec $this['conf']['googleAnalyticsId'].

3Fichier temma.php complet

Voici un autre exemple, qui montre toutes les variables de configuration standard :

<?php

return [
    // variables de définition générale de l'application
    'application' => [
        // sources de données
        'dataSources' => [
            // DSN de connexion à la base de données MySQL
            //  (cf. objet \Temma\Datasources\Sql).
            // Optionnel
            'db' => 'mysqli://user:passwd@localhost/mybase',

            // DSN de connexion à la base de données Redis
            //  (cf. objet \Temma\Datasources\Redis).
            // Optionnel
            'ndb' => 'redis://localhost:6379/0',

            // DSN de connexion au serveur de cache Memcached
            //  (cf. objet \Temma\Datasources\Memcache).
            // Optionnel
            'cache' => 'memcache://localhost:11211',
        },

        // Indique qu'on souhaite ou non utiliser les sessions.
        // À utiliser pour désactiver les sessions au besoin.
        // Optionnel : "true" par défaut.
        'enableSessions' => true,

        // Nom du cookie de session.
        // Optionnel : "TemmaSession" par défaut.
        'sessionName' => 'TemmaSession',

        // Source de données pour stocker les sessions.
        // Optionnel : Utilise le mécanisme de gestion des
        // sessions de PHP par défaut
        'sessionSource' => 'ndb',

        // Durée de la session.
        // Optionnel : Un an par défaut.
        'sessionDuration' => 31536000,

        // Indique si le cookie de session ne doit être envoyé que
        // sur des connexions HTTPS.
        // Optionnel : "false" par défaut.
        'sessionSecure' => false,

        // Namespace par défaut des contrôleurs
        // Optionnel : par défaut les contrôleurs sont dans
        // le namespace global.
        'defaultNamespace' => '\MyApp\Controllers',

        // Nom du contrôleur utilisé pour la racine du site.
        // Optionnel : Utilise le contrôleur défini par la variable
        // 'defaultController' par défaut.
        'rootController' => 'Homepage',

        // Nom du contrôleur par défaut à utiliser si le contrôleur
        // demandé est inexistant.
        // Optionnel : Par défaut, génère une erreur 404 si on
        // demande un contrôleur qui n'existe pas.
        'defaultController' => 'NotFound',

        // Nom du contrôleur à appeler systématiquement,
        // même si celui demandé existe.
        // Optionnel.
        'proxyController' => 'Main',

        // Nom de la vue par défaut.
        // Optionnel : "\Temma\Views\Smarty" par défaut.
        'defaultView' => '\Temma\Views\Smarty',

        // Nom de l'objet qui sera utilisé comme
        // composant d'injection de dépendances
        // Optionnel : Utilise le composant d'injection de
        // dépendances de Temma par défaut.
        'loader' => 'MyLoader',

        // Chemin vers le fichier de log.
        // Optionnel : Par défaut, les logs sont écrits dans
        // le fichier "log/temma.log".
        // Mettre une valeur fausse (null, false, chaîne vide ou le nombre zéro)
        // pour désactiver l'écriture dans le fichier de log.
        'logFile' => 'log/temma.log',

        // Nom de l'objet de gestion de log, ou liste de noms d'objets.
        // Optionnel : Aucun gestionnaire de log n'est activé par défaut.
        'logManager' => [ 'ElasticLogManager', 'SentryLogManager' ],
    ],
    // Définition des seuils de log
    'loglevels': [
        'Temma/Base' => 'ERROR',
        'Temma/Web'  => 'WARN',
        'myapp'      => 'DEBUG',
        'default'    => 'NOTE',
    ],
    // Définition des seuils de log mis en attente
    'bufferingLoglevels' => [
        'Temma/Base' => 'DEBUG',
        'myapp'      => 'DEBUG',
    ],
    // Routage : On indique des noms de contrôleurs virtuels,
    // en y associant un contrôleur réel (ou virtuel, en cascade),
    // qui prendra en charge les requêtes.
    'routes' => [
        'sitemap.xml'          => 'SitemapController',
        'sitemap.extended.xml' => 'sitemap.xml',
        'robert'               => 'BobController',
    ],
    // Gestion des plugins
    'plugins' => [
        // Plugins exécutés pour tous les contrôleurs
        // - plugins exécutés avant le contrôleur
        '_pre' => [
            'CheckRequest',
            'UserGrant',
        ],
        // - plugins exécutés après le contrôleur
        '_post' => [ 'AddCrossLinks' ],
        // Définition de plugins spécifiques au contrôleur Article
        'Article' => [
            // plugins exécutés avant et après le contrôleur
            '_pre'  => [ 'Something' ],
            '_post' => [ 'SomethingElse' ],
            // plugins spécifiques à l'action index
            'index' => [
                '_pre'  => [ 'Aaa' ],
                '_post' => [ 'Bbb' ],
            },
            // plugin exécuté avant l'action setData
            'setData' => [
                '_pre' => [ 'CccPlugin' ],
            ]
        ],
        // Plugin pour le contrôleur BobController, mais
        // uniquement lorsqu'il est appelé par sa route "robert"
        'robert' => [
            '_pre' => [ 'AnotherPlugin' ],
        ]
    ],
    // Définition des pages d'erreurs
    'errorPages' => [
        '404'     => 'error404.html',
        '500'     => 'error500.html',
        'default' => 'error404.html',
    },
    // Liste de chemins d'inclusion, avec ou sans préfixes de namespaces
    'includePaths' => [
        '/opt/some_library/lib',
        '/opt/other_lib',
        '\Acme\Log'      => '/path/to/acme-log/lib',
        '\MyLib\Feature' => '/path/to/vendors/feature',
    ],
    // Données importées automatiquement comme variables de template
    'autoimport' => [
        'googleId'          => 'azeazeaez',
        'googleAnalyticsId' => 'azeazeazeaze',
    },
    // Configuration étendue
    'x-homepage' => [
        'title'       => 'Titre du site',
        'description' => 'Description du site',
    ],
    // Une autre configuration étendue
    'x-email' => [
        'senderAddress' => 'admin@localhost.localdomain',
        'senderName'    => 'Administrateur',
    ],
];

4Sections de configuration

4.1application

La principale section du fichier est celle nommé application. Elle sert à définir les variables de configuration les plus importantes du projet.
Voici les différentes variables qu'elle peut contenir :

  • dataSources : Sert à définir les DSN (Database Source Name) de connexion à des sources de données (MySQL, Redis, Memcached…).
  • enableSessions : Cette variable doit être mise à false dans le cas où on ne souhaite pas gérer les sessions. Cela peut être utile lorsqu'on ne veut pas suivre les visites des utilisateurs, comme par exemple pour une API.
  • sessionName : Nom du cookie qui contiendra l'identifiant de session.
  • sessionSource : Nom de la source de données (dans le tableau associatif dataSources) qui va contenir les données de session. Cela peut être une connexion Redis, Memcache ou SQL (voire File ou S3). Si ce paramètre n'est pas défini, Temma utilise le mécanisme de sessions natif de PHP.
  • sessionDuration : Durée de la session en secondes.
  • sessionSecure : Booléen indiquant si le cookie de session ne doit être envoyé que sur les connexions sécurisées par HTTPS.
  • defaultNamespace : Dans le cas où les fichiers PHP des contrôleurs ne sont pas enregistrés dans le répertoire controlers/, cette variable contient le namespace par défaut des contrôleurs.
    Par exemple, si cette variable contient la valeur \App\Ctrl, et qu'on se connecte à l'URL www.monsite.com/home, Temma va charger l'objet \App\Ctrl\Home.
  • rootController : Nom du contrôleur à exécuter quand une connexion a lieu sur la racine du site.
  • defaultController : Nom du contrôleur à exécuter quand est demandé un contrôleur qui n'existe pas.
  • proxyController : Nom du contrôleur à exécuter systématiquement, même si le contrôleur demandé existe.
  • defaultView : Nom de la vue par défaut, qui sera utilisée pour générer le flux de sortie (à moins qu'une autre vue ne soit explicitement demandée dans le contrôleur ou un plugin).
  • loader : Nom de l'objet qui sera utilisé comme le composant d'injection de dépendances.
  • logFile : Chemin vers le fichier de log. Mettre une valeur fausse (null, false, chaîne vide ou le nombre zéro) pour désactiver l'écriture dans le fichier de log (cela peut être utile si vous souhaitez passer uniquement par un log manager ; voir la directive suivante). Tout chemin ne commençant pas par un slash (/) sera relatif à la racine du projet.
  • logManager : Cette variable peut contenir le nom d'un objet de gestion du log, ou une liste de nom d'objets. Voir la documentation du log pour plus de détails.

4.2DSN (Data Source Name)

Pour définir une connexion à une source de données, il faut écrire une chaîne de caractère qui contient tous les paramètres.

Plusieurs formats sont possible :

  • Variable d'environnement
    Il est possible d'indiquer le nom d'une variable d'environnement.
    env://VAR_NAME
    Il faut que cette variable soit correctement définie, et que son contenu soit un DSN deconnexion supporté par Temma (voir les cas ci-dessous).

  • Base de données relationnelle
    protocol://user:password@host[:port][/db_name]
    • protocol : Type de base de données, tel que défini par PDO (mysql, pgsql, cubrid, sybase, mssql, dblib, firebird, ibm, informix, sqlsrv, oci, odbc, 4D)
    • user : Nom de l'utilisateur pour se connecter au serveur
    • password : Mot de passe utilisé pour se connecter au serveur
    • host : Nom de la machine hôte hébergeant le serveur de base de données
    • port : Numéro de port de la connexion réseau (optionnel, utilise le numéro de port habituel par défaut)
    • db_name : Nom d'instance de base auquel se connecter (optionnel)
    Exemples :
    • mysql://db_user:db_password@localhost/app_db
    • pgsql://db_user:db_password@db_server.mydomain.com:3307/app_db

    Il est possible de se connecter à un serveur MySQL en utilisant une socket Unix locale, plutôt qu'une socket réseau : mysql://user:password@localhost/db_name#/path/to/unix/socket
    Par rapport à une configuration MySQL classique, il faut ajouter un caractère dièse (#), suivi du chemin vers le fichier de la socket Unix. Le nom de machine hôte doit obligatoirement être localhost. Il ne peut pas y avoir de numéro de port.
    Exemple : mysql://db_user:db_password@localhost/app_db#/var/run/mysqld/mysqld.sock

  • Base de données relationnelle locale (SQLite)
    sqlite:/path/to/file.sq3

  • Base de données non relationnelle (Redis)
    redis://host[:port][/db_number]
    • host : Nom de la machine hôte hébergeant le serveur Redis
    • port : Numéro de port de la connexion réseau (optionnel, utilise le port 6379 par défaut)
    • db_number : Numéro de la base à laquelle se connecter (optionnel, utilise la base 0 par défaut)
    Exemples :
    • redis://localhost
    • redis://db_server.mydomain.com:6380/2

    Il est possible de se connecter à un serveur Redis en utilisant une socket Unix locale, plutôt qu'une socket réseau :
    redis-sock:///path/to/unix/socket[#base]
    Par rapport à une configuration Redis classique, il faut indiquer le chemin vers le fichier de la socket Unix, à la place du nom de serveur. Si un numéro de base est spécifié, il doit être indiqué à la fin, après un caractère dièse (#) ; sinon c'est la base 0 qui est utilisée par défaut.
    Exemples :
    • redis-sock:///var/run/redis/redis-server.sock
    • redis-sock:///var/run/redis/redis-server.sock#2

  • Serveur de cache (Memcached)
    memcache://host[:port]
    • host : Nom de la machine hôte hébergeant le serveur Memcached
    • port : Numéro de port de la connexion réseau (optionnel, utilise le port 11211 par défaut)
    Exemple : memcache://localhost
    Il est possible de configurer le client pour qu'il se connecte à plusieurs serveurs Memcached (la répartition des données sur plusieurs serveurs Memcached est gérée côté client). Pour cela, il faut indiquer les serveurs à la suite les uns des autres, séparés par un point-virgule (;) ; chaque serveur pouvant être accompagné d'un numéro de port spécifique.
    Exemple : memcache://localhost;serv1:11212;serv2

    Il est possible de se connecter à un serveur Memached en utilisant une socket Unix locale, plutôt qu'une socket réseau :
    memcache:///path/to/unix/socket
    Par rapport à une configuration Memcached classique, il faut indiquer le chemin vers le fichier de la socket Unix, à la place du nom de serveur.
    Exemple : memcache:///var/run/memcached/memcached.sock

4.3loglevels

La section loglevels sert à définir les différents niveaux de log. Vous trouverez des informations sur son utilisation dans la page de documentation consacrée au log.

Vous pouvez définir plusieurs "classes" de log, chacune avec un seuil d'affichage différent. Le seuil détermine les messages qui apparaîtront dans le fichier etc/temma.log. Lorsqu'un code tente d'écrire un message de log, le message en question n'apparaîtra que si son niveau de criticité est supérieur ou égale à celui du seuil correspondant.

Les différentes valeurs de seuil possibles sont :

  • DEBUG : message de débuggage (criticité la plus faible)
  • INFO : message d'information (niveau par défaut des messages dont le niveau n'est pas précisé)
  • NOTE : notification ; message normal mais significatif (seuil par défaut)
  • WARN : message d'alerte ; l'application ne fonctionne pas normalement mais elle peut continuer à fonctionner.
  • ERROR : message d'erreur ; l'application ne fonctionne pas normalement et elle doit s'arrêter.
  • CRIT : message d'erreur critique ; l'application risque d'endommager son environnement (système de fichiers ou base de données).

Vous pouvez définir vos propres "classes" de log, en fonction de vos besoins applicatifs. En plus de cela, vous pouvez utiliser les classes suivantes :

  • Temma/Base : Logs concernant les objets de base de Temma (base de données, autoloader, sessions, etc.).
  • Temma/Web : Logs concernant les objets du framework lui-même.
  • default : Sert à définir le seuil par défaut, pour toutes les classes qui ne sont pas définies.

Si la directive loglevels ne contient pas un tableau associatif, mais simplement une chaîne de caractères représentant un seuil de log, celui-ci sera utilisé pour tous les messages, quel que soit leur "classe" spécifique.


4.4bufferingLoglevels

La section bufferingLoglevels n'est pas nécessairement obligatoire. Elle sert à modifier le comportement du log.

Comme vu précédemment (voir section loglevels), un message apparaît dans le fichier de log si son niveau de criticité est supérieur ou égal au seuil défini. Si ce n'est pas le cas, il est simplement écarté.

Mais parfois, on veut un comportement différent : que les message qui ne sont pas écrits soient stockés, et si un message finit par être écrit, tous les messages en attente sont écrits d'abord dans le fichier de log.

Avec la directive bufferingLoglevels, il devient possible de définir − pour chaque "classe" de log − le seuil à partir duquel les messages non écrits sont stockés pour plus tard. Elle s'utilise de la même manière que pour loglevels, sauf que les messages liés à une classe non listée ne seront pas stockés.


4.5routes

Les routes permettent de définir des «contrôleurs virtuels», servant d'alias aux contrôleurs réels. C'est particulièrement utile pour permettre l'accès à des contrôleurs sous des noms de fichiers (tels que les classiques robots.txt et sitemap.xml). Cela permet aussi d'avoir un même contrôleur qui répond sur plusieurs URL différentes, en faisant éventuellement des traitements différents en fonction du nom de contrôleur demandé.


4.6plugins

Les plugins servent à exécuter du code avant et/ou après l'exécution du contrôleur. Vous trouverez des informations détaillées sur la page de documentation dédiée.

La variable _pre permet de lister les plugins qui seront exécutés avant les contrôleurs.
La variable _post permet de lister les plugins qui seront exécutés après les contrôleurs.

Il est possible de lister les contrôleurs par leurs noms, en spécifiant pour chacun des directives _pre et _post spécifiques. Il est aussi possible de spécifier des plugins pour une action particulière d'un contrôleur.


4.7errorPages

Dans certains cas, votre site devra envoyer un code d'erreur HTTP. Et si une erreur a lieu dans votre code lui-même (à cause d'une requête SQL incorrecte, par exemple), c'est le framework qui décidera d'envoyer une erreur 500.

Il est possible d'afficher une page HTML statique différente pour chaque type d'erreur HTTP, et de définir une page par défaut pour tous les types d'erreur qui ne sont pas explicitement configurés.

Pour ne pas définir toutes les pages d'erreur possible, vous pouvez utiliser la clé "default" pour définir la page par défaut.
Si vous voulez utiliser la même page, quelle que soit l'erreur, vous pouvez ne pas fournir un tableau associatif, mais juste une chaîne de caractères.


4.8includePaths

Cette section sert à ajouter des chemins d'inclusion dans lesquels PHP est susceptible d'aller chercher les objets à inclure.

À la base, Temma ajoute au chemin d'inclusion le répertoire lib/ du projet. Ainsi, si vous écrivez dans votre code : include('Toto.php');
C'est le fichier lib/Toto.php qui sera chargé.

Ou encore, grâce à l'autoloader, si vous écrivez : new \Aaa\Bbb();
PHP chargera le fichier lib/Aaa/Bbb.php automatiquement.

Mais si vous ajoutez la configuration suivante dans votre fichier etc/temma.php :

[
    'includePaths' => [
        '/path/to/lib1',
        '/path2',
    ]
]

Si vous écrivez dans votre code require_once('titi.php');
PHP essaiera de charger successivement plusieurs chemins, jusqu'à trouver le fichier. Dans l'ordre, il essaiera :

  • /path/to/lib1/titi.php
  • /path2/titi.php
  • lib/titi.php

En utilisant l'autoloader, si vous écrivez new \Aa\Bb(); PHP essaiera les chemins suivants :

  • /path/to/lib1/Aa/Bb.php
  • /path2/Aa/Bb.php
  • lib/Aa/Bb.php

Il peut arriver que vous ayez besoin de spécifier des chemins d'inclusion spécifiques pour certains préfixes de namespace. Imaginons que votre fichier etc/temma.php contient la configuration suivante :

[
    'includePaths' => [
        '\Acme\Log' => '/path/to/acme-log/lib',
        '\Aa\Bb'    => '/path/to/Bb',
    ]
]

Si vous écrivez le code new \Acme\Log\Writer();
PHP tentera de charger le fichier /path/to/acme-log/lib/Writer.php

Par contre, si vous écrivez new \Aa\Bb\Cc();
PHP chargera le fichier /path/to/Bb/Cc.php

Il est possible de spécifier en même temps des chemins d'inclusion avec et sans namespace :

[
    'includePaths' => [
        '/path/to/lib1',
        '/path2',
        '\Acme\Log' => '/path/to/acme-log/lib',
        '\Aa\Bb'    => '/path/to/Bb',
    ]
]

4.9autoimport

Toutes les directives placées dans la section autoimport sont chargées automatiquement à l'intérieur de la variable de template $conf.

Les variables de template sont accessibles dans les contrôleurs en écrivant $this['variable'].
Elles peuvent aussi être crées (ou écrasées) en écrivant $this['variable'] = $valeur;.


5Configuration étendue

Le fichier de configuration peut contenir des sections de configuration étendues. Leur but est de regrouper des variables de paramétrage supplémentaires, en les classant par fonctionnalité. Ainsi, un contrôleur ou un plugin peut facilement récupérer les valeurs qui l'intéressent, sans avoir besoin de connaître l'ensemble des variables de configuration existantes.

Pour récupérer une section entière de configuration étendue, il faut passer par l'objet de configuration qui est disponible directement dans les contrôleurs :

$this->_config

Et via le composant d'injection de dépendances :

$this->_loader->config

Par exemple, pour récupérer l'ensemble de la configuration étendue x-homepage, il faut écrire :

$data = $this->_config->xtra('homepage');

Pour récupérer la variable senderName de la section x-email :

$data = $this->_config->xtra('email', 'senderName');

Il est aussi possible de spécifier une valeur par défaut qui sera utilisée si la variable demandée n'existe pas :

$data = $this->_config->xtra('email', 'recipient', 'contact@host.domain');

6Configuration par plateforme et surcharge de configuration

Plutôt que d'avoir un unique fichier etc/temma.php (ou etc/temma.json, etc.) vous pouvez utiliser des fichiers différents en fonction de la plateforme sur laquelle le site est déployé : local, test, staging, production…

Pour cela, vous devez définir une variable d'environnement ENVIRONMENT, dont la valeur est le nom de la plateforme courante.

Par exemple, vous pourriez avoir un fichier etc/temma.test.php et un fichier etc/temma.prod.php. Le premier sera utilisé sur les serveurs dont la variable d'environnement ENVIRONMENT vaut test, et le second sur les serveur dont la variable vaut prod.

Bien souvent, seules certaines parties de la configuration est différente d'une plateforme à l'autre. Dans ce cas, vous pouvez mettre toute la configuration commune dans le fichier etc/temma.php habituel, et mettre dans les fichiers spécifiques les configurations propres à chaque plateforme.
Le fichier général est lu en premier, et le fichier spécifique à la plateforme vient le surcharger avec les valeurs qu'il contient.

De plus, les fichiers ne doivent pas obligatoirement être tous au même format. Vous pouvez avoir un fichier général etc/temma.php, et le surcharger en staging avec un fichier etc/temma.staging.json et en production avec un fichier etc/temma.prod.yaml.