Astuce Couleurs de lignes alternées avec Excel

Dans un fichier Excel, si la couleur des lignes alterne mais que vous n’êtes pas dans un tableau, c’est que la couleur a été spécifiée pour chaque ligne manuellement.
Si vous ajoutez de nouvelles lignes, elles ne porteront donc pas l’alternance de couleur.

Pour corriger toutes vos nouvelles lignes d’un coup :

  • Sélectionnez juste deux lignes qui se suivent, avec la bonne alternance de couleur
  • Cliquer sur le bouton Reproduire la mise en forme
  • Sélectionnez toutes les lignes pour lesquelles corriger la coloration

L’alternance devrait maintenant être correcte.

Astuce Ouvrir une application en tant qu’administrateur sous Windows

Sous Windows, la solution classique pour ouvrir une application en tant qu’administrateur, c’est d’utiliser sa souris :

  • Dans l’explorateur Windows, faites un clic-droit sur l’icône de l’application
  • Dans le menu contextuel qui apparaît, cliquer sur l’option « Exécuter en tant qu’administrateur ».

Note : parfois, cette option n’apparaît que si on maintient la touche Ctrl enfoncée lors du clic-droit.

Un autre raccourci clavier

Il existe une autre méthode, plus rapide, pour ouvrir les applications de la barre des tâches ou du menu Démarrer :

  • Maintenez les touches Windows + Ctrl + Shift
  • Cliquer sur l’icône de l’application à exécuter en tant qu’administrateur

Astuce Dupliquer une ligne n fois avec PostgreSQL

Par exemple, vous avez une donnée en base, et pour vos tests, vous en voudriez 10 000.

Il est possible d’utiliser cette boucle SQL pour faire 10 000 insertions :

-- Copie d’une ligne n fois
--
-- Paramètres modifiables :
--   - nb de fois (ici 10000)
--   - table concernée (ici my_table)
--   - liste des colonnes à copier (ici column_1, column_2, column_x)
--   - id de la ligne à copier (ici 1)

DO
$$
    DECLARE
        i integer;
    BEGIN
        FOR i IN 1..10000
            LOOP
                INSERT INTO public.my_table (column_1, column_2, column_x)
                SELECT column_1, column_2, column_x
                FROM public.my_table
                WHERE id = 1;
            END LOOP;
    END
$$ LANGUAGE plpgsql;

Pour générer cette requête avec toutes les colonnes de votre table, vous pouvez faire générer l’INSERT directement à PostgreSQL, avec la requête suivante :

-- Génération de la requête d’insertion avec toutes les colonnes sauf la colonne id (car auto-incrémentale)
--
-- Paramètres modifiables :
--   - table concernée (ici my_table)
--   - id de la ligne à copier (ici 1)
WITH cols AS (SELECT column_name, ordinal_position
              FROM information_schema.columns
              WHERE table_schema = 'public'
                AND table_name = 'my_table'
                AND column_name NOT IN ('id')
              ORDER BY ordinal_position)
SELECT format(
               'INSERT INTO %I.%I (%s) SELECT %s FROM %I.%I WHERE id = 1;',
               'public',
               'my_table', string_agg(quote_ident(column_name), ', '), string_agg(quote_ident(column_name), ', '),
               'public', 'my_table') AS sql

Un dernier problème se pose si certaines colonnes doivent contenir des données uniques.
Dans les INSERT précédents, on ne gère que le cas de la colonne id, supposée auto-incrémentale. Pour générer des données pour d’autres colonnes, remplacez la requête précédente par celle-ci :

-- Génération de la requête de copie avec toutes les colonnes sauf celle contenant des ID uniques
--
-- Paramètres modifiables :
--   - table concernée (ici my_table)
--   - liste des colonnes pour lesquelles générer des uuid uniques (ici 'uuid1', 'uuid2')
--   - id de la ligne à copier (ici 1)
WITH cols AS (SELECT column_name, ordinal_position
              FROM information_schema.columns
              WHERE table_schema = 'public'
                AND table_name = 'my_table'
                AND column_name NOT IN ('id', 'uuid1', 'uuid2')
              ORDER BY ordinal_position)
SELECT format(
               'INSERT INTO %I.%I (uuid1, uuid2, %s) SELECT gen_random_uuid(), gen_random_uuid(), %s FROM %I.%I WHERE id = 1;',
               'public',
               'my_table', string_agg(quote_ident(column_name), ', '), string_agg(quote_ident(column_name), ', '),
               'public', 'my_table') AS sql

Note : si les valeurs uniques ne sont pas des UUID, il faut rechercher si PostgreSQL propose d’autres fonctions de génération aléatoires que gen_random_uuid().

Lister les clés étrangères qui référencent une table MySQL

On a parfois besoin de supprimer une entrée en base de donnée, mais qui est potentiellement référencées dans plusieurs autres tables. Plutôt que d’exécuter la requête de suppression et de corriger/supprimer une par une les données qui référencent et interdisent la suppression, on peut rechercher toutes les références possibles dans les autres tables.

-- Liste de toutes les clés étrangères vers la colonne `id` de `table1`
SELECT *
FROM
    INFORMATION_SCHEMA.KEY_COLUMN_USAGE
WHERE
    REFERENCED_TABLE_NAME = 'table1'
  AND REFERENCED_COLUMN_NAME = 'id';

Astuce Utiliser le débogage pas à pas avec Docker et PHPStorm

Docker simplifie beaucoup de problématiques d’environnement pour les développeurs.
Quand la configuration de Docker est correcte, un développeur peut lancer un projet rapidement sans avoir à installer la bonne version de PHP, de la base de données, etc.

Malheureusement, cela complexifie aussi la communication entre ce qui tourne sur votre système hôte (ex : Windows) et les services nécessaires au projet (typiquement : PHP).

Voici comment configurer PHPStorm pour utiliser le débogage pas à pas d’XDebug, quand PHP est dockerisé.

Prérequis

PHP avec xDebug

Avant de parler de débogage pas à pas, il faut déjà qu’XDebug soit installé dans votre image docker. Voici un exemple d’image Docker avec PHP 8.4 et XDebug :

FROM php:8.4-fpm-alpine3.20

# Paramétrage du chemin vers les sources dans le conteneur
ENV MOUNT_DIR /srv/php
RUN mkdir -p ${MOUNT_DIR}
WORKDIR ${MOUNT_DIR}

# Installation d’XDebug
RUN install-php-extensions xdebug
COPY config/xdebug.ini $PHP_INI_DIR/conf.d/

Elle utilise ce fichier de config xdebug.ini (dans le répertoire config/) qui sera injecté dans l’image :

;config/xdebug.ini
xdebug.mode = debug,develop,profile
xdebug.start_with_request = trigger
xdebug.client_host = host.docker.internal

On peut builder l’image en local avec une commande du type :

docker build . -t my-group/my-image

Débogage pas à pas

Objectif

Dans l’exemple qui suit, on supposera qu’on souhaite déboguer le fichier src/Controller/TestController.php, lorsqu’on accède à la page http://localhost:8000/test.

Fichier PHP à déboguer

L’exemple utilise Symfony, mais c’est la même chose pour un autre contexte PHP. C’est bien un fichier index.php qui est exécuté ici, et il se charge ensuite d’appeler la méthode number() du fichier TestController.php.

Notez le point rouge dans la marge à gauche (l. 18). Il représente un point d’arrêt : là où vous souhaitez arrêter l’exécution pour déboguer. Pour ajouter un point d’arrêt, cliquez dans la marge à gauche de la ligne de code juste avant laquelle vous souhaitez arrêter l’exécution pour déboguer.
Ici, l’exécution s’arrêtera donc juste avant d’exécuter la ligne 18.

Configuration

Dans PHPStorm, on peut activer le débogage pas à pas en cliquant sur le bouton « téléphone rouge », qui devient vert quand il est activé (aussi disponible en bas du menu Run : Start Listening for PHP Debug Connections).

Boutons de débogage

Mais avant, il faut cliquer sur la liste déroulante Current file à côté, puis sur Edit Configurations....

Nouvelle configuration de Run

Dans la fenêtre de configuration :

  • Cliquez sur le bouton Add
  • Sélectionnez PHP Remote debug
  • Nommez la configuration (ex : Docker)

Boutons de débogage

  • Cliquez sur le lien Validate
  • Sélectionnez le bouton radio Output phpinfo()
  • Créez un fichier php contenant uniquement phpinfo();
  • Affichez-le dans votre navigateur et copiez la source de la page (via Ctrl+U)
  • Collez le code HTML dans le champ texte de la fenêtre de configuration
  • Cliquez sur le bouton Validate.
    PHPStorm va valider une série d’éléments et indiquer si XDebug est bien opérationnel :

Validation de la présence d’XDebug

  • Cliquez sur le bouton Cancel.

Extension de navigateur

Pour simplifier le débogage, vous pouvez installer une extension de navigateur, qui ajoutera une variable dans le header des requêtes, pour indiquer au serveur qu’on souhaite utiliser le débogage pas à pas.

Il en existe plusieurs, comme Xdebug Helper.

Elles fonctionnent toutes plus ou moins de la même manière, en ajoutant un bouton pour activer/désactiver le débogage :

Bouton de l’extension de navigateur

Débogage

  • Cliquez sur le bouton « téléphone rouge » dans PHPStorm pour activer le débogage
  • Cliquez sur le bouton « insecte » (ajouté par l’extension) dans votre navigateur pour activer le débogage
  • Appelez votre page à déboguer.
    La page devrait rester bloquée en cours de chargement, et PHPStorm devrait afficher une fenêtre indiquant qu’il a capté un script à déboguer :

Débogage entrant

  • Cliquez sur le bouton Accept

Normalement, rien ne se passe et la page s’affiche normalement. PHPStorm affiche un message d’alerte, car il n’a pas réussi à faire le lien entre la requête et le fichier PHP concret à déboguer :

Message d’alerte

  • Cliquez sur le lien PHP|Servers dans le message (ou recherchez cette configuration depuis la fenêtre de Settings globale de PHPStorm)
  • Créez un nouveau server (ex : nommé localhost).
  • Par défaut, seulement le répertoire contenant le fichier index.php est mappé. Il faut ajouter le répertoire racine de votre application PHP.
    Dans le tableau, la colonne de gauche indique le chemin physique de votre application sur votre machine ( probablement un répertoire WSL si vous êtes sous Windows). Celle de droite indique le chemin de l’application à l’intérieur de votre conteneur docker (/srv/php correspond au chemin indiqué dans le Dockerfile précédent).

Paramétrage du mapping des fichiers

Normalement, tout est opérationnel. Vous pouvez recharger votre page de test et l’exécution devrait s’arrêter au niveau de votre point d’arrêt.

Dans la section basse de votre IDE, apparait la variable locale $randomNumbers, avec sa valeur courante :

Débogage de la variable $randomNumbers

Marque-page Utiliser les cookies en javascript

L’utilisation des cookies en javascript est à la fois simple et compliquée.

On aimerait une interface d’API un peu plus directe, pour pouvoir juste ajouter/modifier/supprimer un cookie.
Voici un exemple d’implémentation d’une telle API, en javascript puis en typescript.

Version javascript :

//cookie.js
export const ONE_DAY_IN_SECONDS = 24 * 60 * 60 * 1000;
export const SEVEN_DAYS_IN_SECONDS = 7 * ONE_DAY_IN_SECONDS;

/**
 * @param {string} name
 * @param {string} value
 * @param {number} expirationDuration Durée en secondes, ou -1 pour que le cookie soit supprimé à la fermeture de la page.
 */
export function setCookie(name, value, expirationDuration = SEVEN_DAYS_IN_SECONDS) {
    const date = new Date();
    date.setTime(date.getTime() + expirationDuration);

    const expirationPart = expirationDuration > 0
        ? ` expires=${date.toUTCString()};`
        : '';

    document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)};${expirationPart}path=/`;
}

/**
 * @param {string} name
 * @param {string|null} defaultValue
 *
 * @return {string|null}
 */
export function getCookie(name, defaultValue = null) {
    const encodedCookie = document.cookie
        .split('; ')
        .find((cookie) => cookie.startsWith(encodeURIComponent(name) + '='))
        ?.split('=')?.[1];

    return encodedCookie !== undefined ? decodeURIComponent(encodedCookie) : defaultValue;
}

/**
 * @param {string} name
 */
export function deleteCookie(name) {
    const expirationDate = new Date('Thu, 01 Jan 1970 00:00:01 GMT');

    document.cookie = `${encodeURIComponent(name)}=; expires=${expirationDate.toUTCString()}; path=/`;
}

Version typescript :

//cookie.ts
export const ONE_DAY_IN_SECONDS = 24 * 60 * 60 * 1000;
export const SEVEN_DAYS_IN_SECONDS = 7 * ONE_DAY_IN_SECONDS;

/**
 * @param {number} expirationDuration Durée en secondes, ou -1 pour que le cookie soit supprimé à la fermeture de la page.
 */
export const setCookie = (name: string, value: string, expirationDuration: number = SEVEN_DAYS_IN_SECONDS) => {
    const date = new Date();
    date.setTime(date.getTime() + expirationDuration);

    const expirationPart = expirationDuration > 0
        ? ` expires=${date.toUTCString()};`
        : '';

    document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)};${expirationPart}path=/`;
};

export const getCookie = (name: string, defaultValue: string | null = null) => {
    const encodedCookie = document.cookie
        .split('; ')
        .find((cookie) => cookie.startsWith(encodeURIComponent(name) + '='))
        ?.split('=')?.[1];

    return encodedCookie !== undefined ? decodeURIComponent(encodedCookie) : defaultValue;
};

export const deleteCookie = (name: string) => {
    const expirationDate = new Date('Thu, 01 Jan 1970 00:00:01 GMT');

    document.cookie = `${encodeURIComponent(name)}=; expires=${expirationDate.toUTCString()}; path=/`;
};

Erreur Routing et export statique de Next.js vers Azure Static Web Apps

Lorsqu’on fait un export statique d’une application Next.js pour l’héberger sur Azure Static Web Apps, l’application ne fonctionne que lorsqu’on accède à la racine de l’application (/). Les autres chemins (ex : /ma-page) retourne une erreur 404.

Pour que le chemin /ma-page fonctionne, il faut en fait rediriger vers /ma-page/index.html.

On peut faire ça pour toutes les pages automatiquement, via un peu de configuration.

  1. Modifiez le fichier next.config.mjs pour y ajouter trailingSlash: true. Exemple :

    /** @type {import('next').NextConfig} */
    const nextConfig = {
      output: 'export',
      trailingSlash: true,
    };
    
    export default nextConfig;
  2. Créez un fichier staticwebapp.config.json à la racine, destiné à Azure Static Web Apps :

    {
      "navigationFallback": {
        "rewrite": "index.html",
        "exclude": ["*.{png,jpg,gif,svg}", "*.css"]
      }
    }

Astuce Déployer un site statique vers Microsoft Azure

Le service Microsoft Azure permet d’héberger un site statique dans un espace de stockage. Voici comment réaliser le déploiement en lignes de commande.

Prérequis

  1. Installez la CLI Azure

    # pour Debian
    curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash

    Documentation d’installation : https://learn.microsoft.com/fr-fr/cli/azure/install-azure-cli

  2. Activez les sites web statiques dans Azure Storage :

Paramétrage Azure storage

Déploiement

Variables d’environnement à configurer

  • AZURE_APP_ID (ex : 8514903f-ed49-48b6-b5f8-bc4ff676fce3) :
  • AZURE_CLIENT_SECRET (ex : TYY5A~I2d19dffcf938ImHBeS~-.S4db2318T.) :
  • AZURE_TENANT_ID (ex : 44db2318-9218-41b4-b935-2d19dffcf938) :
  • AZURE_STORAGE_ACCOUNT (ex : stmystaticwebsite) :

    AZURE_STORAGE_ACCOUNT

export AZURE_APP_ID='8514903f-ed49-48b6-b5f8-bc4ff676fce3'
export AZURE_CLIENT_SECRET='TYY5A~I2d19dffcf938ImHBeS~-.S4db2318T.'
export AZURE_TENANT_ID='44db2318-9218-41b4-b935-2d19dffcf938'
export AZURE_STORAGE_ACCOUNT='stmystaticwebsite'

Déploiement

# Authentification
az login --service-principal -u $AZURE_APP_ID -p $AZURE_CLIENT_SECRET --tenant $AZURE_TENANT_ID
# Suppression des fichiers statiques existants
az storage blob delete-batch \
  --account-name $AZURE_STORAGE_ACCOUNT --auth-mode login \
  --source '$web' --pattern '*'
# Envoi des nouveaux fichiers statiques
az storage blob upload-batch \
  --account-name $AZURE_STORAGE_ACCOUNT --auth-mode login \
  --destination '$web' -s my_static_app_dir

CDN

Au cas où Microsoft Azure CDN est activé pour servir le site statique, il faut penser à le purger.

Variables d’environnement à configurer

  • AZURE_RESOURCE_GROUP (ex : RG_MY_STATIC_WEBSITE) :

    AZURE_RESOURCE_GROUP

  • AZURE_CDN_PROFILE_NAME (ex : cdnpmystaticwebsite) :

    AZURE_CDN_PROFILE_NAME

  • AZURE_CDN_ENDPOINT_NAME (ex : cdnemystaticwebsite) : c’est le même qu’AZURE_CDN_PROFILE_NAME, mais avec cdne plutôt que cdnp en préfixe.

    export AZURE_RESOURCE_GROUP='RG_MY_STATIC_WEBSITE'
    export AZURE_CDN_PROFILE_NAME='cdnpmystaticwebsite'
    export AZURE_CDN_ENDPOINT_NAME='cdnemystaticwebsite'

Purge du CDN

az cdn endpoint purge \
  --resource-group $AZURE_RESOURCE_GROUP \
  --profile-name $AZURE_CDN_PROFILE_NAME \
  --endpoint-name $AZURE_CDN_ENDPOINT_NAME \
  --content-paths '/*'

Erreur SAVEPOINT DOCTRINE_2 does not exist

Lors de l’exécution d’une migration Doctrine pour le déploiement d’un projet Symfony 5.x, vous lancez généralement cette commande :

php bin/console doctrine:migration:migrate --all-or-nothing --no-interaction

Vous pouvez alors obtenir de MySQL l’erreur SAVEPOINT DOCTRINE_2 does not exist. Cela veut dire que MySQL n’arrive pas à utiliser le système de transaction pour effectuer un rollback.

À défaut de permettre à MySQL d’effectuer son rollback correctement, vous pouvez lui éviter d’avoir à le faire, en corrigeant la migration qui pose le problème.

Plutôt que d’exécuter toutes les migrations d’une traite, exécutez-les une par une, via la commande suivante :

php bin/console doctrine:migration:migrate next --no-interaction

Si la migration échoue, le vrai message d’erreur SQL qui vous intéresse s’affichera, et pas celle concernant le rollback. Si elle réussit, relancez la commande jusqu’à trouver la migration fautive.

Pour connaître l’état des migrations, utilisez la commande status :

php bin/console doctrine:migration:status

Erreur Docker – unauthorized: HTTP Basic: Access denied

Gitlab semble privilégier l’authentification via jeton d’accès personnel, pour se connecter au registre de conteneurs docker.

Remarque : Dans cet article, on considère que l’instance Gitlab hébergeant le registre de conteneurs avec lequel vous souhaitez interagir a pour URL https://my-gitlab.com. Remplacez-la par la vôtre, sans oublier d’y ajouter le port, si besoin.

Si vous n'êtes pas authentifié via un jeton, vous obtenez l’erreur suivante à chaque git pull ou git push :

# Utilisez l’URL de votre instance gitlab privée ou celle de gitlab.com
> docker pull my-gitlab.com/my-project-group/my-project/my/image:latest
Error response from daemon: Head "https://my-gitlab.com/my-project-group/my-project/my/image/manifests/latest": 
unauthorized: HTTP Basic: Access denied. The provided password or token is incorrect or your account has 2FA enabled 
and you must use a personal access token instead of a password. 
See https://my-gitlab.com/help/user/profile/account/two_factor_authentication#troubleshooting

Génération d’un jeton d’accès personnel

Pour éviter cela, il vous faut générer un nouveau jeton d’accès dans Gitlab :

  • Accédez à la page Personal access tokens de gitlab (https://my-gitlab.com/-/user_settings/personal_access_tokens)
  • Cliquez sur le bouton « Ajouter un nouveau jeton »
  • Précisez un nom de jeton (évitez les guillemets " et antislash \)
  • Définissez éventuellement la date d’expiration du jeton
  • Cochez les cases read_api, read_registry (et éventuellement write_registry si vous comptez faire des docker push)
  • Sauvegardez le jeton généré dans un espace sécurisé

Authentification avec le jeton

Authentifiez-vous auprès de votre instance Gitlab, en ligne de commande :

docker login my-gitlab.com --username "My token name" --password "glpat-as767nCwxmEfDhf_yHA7"

Cette commande revient à celle de login classique de docker, avec :

  • pour nom d’utilisateur, le nom du jeton tel qu’affiché dans le tableau listant les jetons
  • pour mot de passe, le jeton sauvegardé

Remarque :
Comme docker l’indique en message d’alerte au lancement de la commande, celle-ci n’est pas sécurisée. En effet, votre mot de passe sera stocké dans l’historique bash des commandes lancées.

Pour éviter cela, stockez plutôt le jeton dans un fichier :

# Créez un fichier ~/my_token.txt contenant votre jeton d’accès
cat ~/my_token.txt | docker login my-gitlab.com --username "My token name" --password-stdin
rm ~/my_token.txt

ou en variable d’environnement :

echo "$MY_TOKEN" | docker login my-gitlab.com --username "My token name" --password-stdin