• php

Instancier une classe qui n'existe pas en PHP

Vous voulez instancier une classe qui n’existe pas en PHP ? Hold my beer đŸș.

Un autoloader permet de dicter à PHP comment il doit s’y prendre afin d’instancier une nouvelle classe, en d’autres termes : vous avez le contrîle sur le comportement d’un “new”.

Une fois qu’on s’est dit ca, on peut s’imaginer faire quelques bĂȘtises, par exemple, instancier une classe qui n’existe “pas” rĂ©ellement.

Imaginez que votre projet possùde une unique classe App\Foo et que vous souhaitez l’instancier à partir d’un FQCN quelconque 
 du genre Domain\Foo ?

Actuellement, cette instanciation lùve une erreur, c’est normal, la classe Domain\Foo n’existe pas.

1
2
new Domain\Foo();
# Fatal error: Uncaught Error: Class "Foo" not found

À l’aide d’un autoloader, nous pouvons “attraper” cette instanciation en vol afin de la rediriger ailleurs :

1
2
3
4
5
6
7
8
9
10
spl_autoload_register(function ($class) {

    if (str_starts_with($class, 'Domain')) {

        $alias = str_replace('Domain', 'App', $class);

        return class_alias($alias, $class);
    }

}, true, true);

Ce code sera Ă  placer au plus haut de votre application, dans le cadre de Laravel, dans son fichier bootstrap/app.php.

Grace Ă  cet autoloader, nous instancions dĂ©sormais la classe Foo prĂ©sente dans le namespace App/, il n’y a plus d’erreur, le code fonctionne 
 bien Ă©videmment, Ă©vitez d’ĂȘtre contraint d’effectuer ce genre de trick pĂ©rilleux, mais cela pourrait peut-ĂȘtre vous aider dans des situations trĂšs exotiques.

1
2
3
$foo = new Domain\Foo();
var_dump($foo);
# object(Foo)#1 (0) {}

PS : CrĂ©er un alias d’une classe inexistante lĂšve un warning en PHP 8, mais entre nous, Ă  ce niveau de magie noire, on s’en moque un petit peu.

SĂ©quence corrompue dans pgSQL

Lors d’une migration de base de donnĂ©es un peu barbare il m’est peut-ĂȘtre dĂ©jĂ  arrivĂ© un jour de casser la sĂ©quence d’un index.

Par exemple, si une table possÚde 8944 lignes avec une colonne ID (étant une séquence), il est fort à parier que la prochaine valeur de cette colonne soit 8945 
 dans ma situation cette valeur était de 241 (?).

En gros, pour la faire courte, la séquence était corrompue.

Pour corriger cette anomalie avant que vos fichiers de logs explosent, on commence par identifier le nom exact de la séquence :

SELECT pg_get_serial_sequence('items', 'id');

Cela nous permet de vérifier avec un nextval la prochaine valeur théorique :

SELECT nextval('public.items_id_seq');

Si cette valeur est effectivement incohĂ©rente, nous pouvons la modifier arbitrairement Ă  l’aide d’un setval ;

SELECT setval('public.items_id_seq', 8945);

Laravel 9 : Les POST ne sont plus des GET

En Laravel 8, il Ă©tait malheureusement possible de rĂ©cupĂ©rer une valeur transmise en GET Ă  une URL 
 Ă  partir de la mĂ©thode post d’une instance de request :

// Laravel 8
// http://localhost/?foo=bar

request()->get('foo'); // bar
request()->post('foo'); // bar

Ce comportement, qui apparaĂźt Ă  raison comme une anomalie, a Ă©tĂ© corrigĂ© en Laravel 9 sans que cette correction soit mentionnĂ©e dans le changelog de montĂ©e de version. 😐

DĂ©sormais, Ă  partir de Laravel 9, le comportement est le suivant :

// Laravel ^9
// http://localhost/?foo=bar

request()->get('foo'); // bar
request()->post('foo'); // NULL

Si par le passĂ© vous avez malencontreusement utilisĂ© la mauvaise mĂ©thode sans vous en rendre compte, vous ĂȘtes bon pour lancer quelques commandes route:list pour vĂ©rifier que vous employez la bonne mĂ©thode :

php artisan route:list --method=GET

ps : bon courage si vous avez des routes any

Les fichiers temporaires de Snappy

Petite alerte ce matin d’un agent AWS, l’une de nos machines manque d’espace disque.

Si vous voulez passer une mauvaise journĂ©e, essayons donc d’attendre que votre serveur atteigne les 100%, vous verrez c’est sympathique de plus pouvoir se connecter en SSH.

Ni une ni deux nous cherchons les dossiers les plus volumineux à l’aide de la commande du :

du -h --max-depth=1 | sort -hr | head -n 10

En Ă©tudiant le rĂ©sultat de la recherche, nous ne sommes rendu compte que le dossier /tmp prĂ©sent Ă  la racine du serveur faisait plus de 10 GO 
 soit plus de la moitiĂ© des capacitĂ©s du disque de la machine.

Dans ce dossier, nous nous sommes retrouvĂ©s face Ă  une montagne de fichier ayant un pattern similaire : “knp_snappy651be73555b004.69764999.html”.

AprĂšs tĂ©lĂ©chargement, il s’avĂšre que les coupables sont des fichiers temporaires crĂ©Ă©s par le package laravel-snappy que nous utilisons pour gĂ©nĂ©rer des PDF.

Visiblement, dans certaines circonstances, le package n’est pas en mesure de supprimer ses propres fichiers temporaires 
 ce qui au fil des mois remplira votre serveur de plusieurs GO de fichiers inutiles, jusqu’à l’implosion.

Un rapide coup de suppression à l’aide d’un find :

find /tmp -name "knp_*.html" -type f -delete

Attention Ă  vous, laravel-snappy n’est qu’un wrapper de KnpLabs/snappy qui lui-mĂȘme encapsule le package wkhtmltopdf, un standard de l’industrie que vous utilisez probablement sans le savoir.

Le problĂšme que nous avons rencontrĂ© n’est peut-ĂȘtre pas directement liĂ© Ă  laravel-snappy mais Ă  l’une de ses dĂ©pendances, bon courage Ă  vous.

  • cli

La commande screen

Imaginez que vous devez lancer une commande sur une machine distante nĂ©cessitant plusieurs heures d’exĂ©cution.

Vous pourriez le faire en vous connectant en SSH sur cette machine et en exĂ©cutant votre commande, mais cela vous obligera Ă  conserver votre terminal ouvert 
 quand bien mĂȘme ce dernier pourrait ĂȘtre kill pour inactivitĂ© ou subir une quelconque coupure.

La commande screen permet de créer et de gérer des sessions de terminal virtuelles détachables sur un systÚme Unix.

On entend par “dĂ©tachable” qu’il sera possible de crĂ©er des sessions permettant de se dĂ©connecter de votre machine tout en laissant les processus en cours d’exĂ©cution dans la session, et de s’y reconnecter plus tard.

Une fois connectĂ© en SSH sur votre machine, crĂ©Ă© votre session Ă  l’aide de la commande screen -S :

screen -S my_screen_name

Cette commande vous amÚne directement sur votre session dans laquelle vous pourrez exécuter votre commande.

Une fois que cette derniĂšre sera en cours d’exĂ©cution, vous pourrez quitter cette session en tapant sur a puis d tout en maintenant la touche control 
 c’est un peu technique, mais vous y arriverez.

Une fois Ă  nouveau sur votre machine, toutes les sessions existantes sont affichables avec la commande suivante :

screen -ls

La commande screen -r vous permettra de sauter sur une session existante :

screen -r my_screen_name

Pour finir, un simple exit sur une session vous permettra de la kill.

Augmenter la RAM d'un serveur Mysql sur Laravel Forge

La configuration de votre serveur MySQL sur Laravel Forge se trouve dans un fichier “my.cnf” :

sudo vim /etc/mysql/my.cnf

Si vous avez perdu votre password sudo d’une machine sur Forge, cherchez dans votre boüte mail un certain “Forge: Server ({name server}) Provisioned”, vous me remercierez plus tard.

Désormais, éditez le fichier my.cnf et modifier la valeur en MO de la clé innodb_buffer_pool_size :

[mysqld]
default_authentication_plugin=mysql_native_password
skip-log-bin
innodb_buffer_pool_size = 400M

La clé innodb_buffer_pool_size est le paramÚtre de configuration de MySQL qui détermine la quantité de mémoire allouée pour mettre en cache les données et les index, vous améliorez les performances en accélérant les accÚs aux données fréquemment utilisées.

AprÚs avoir sauvegardé, redémarrer MySQL depuis le panel de configuration de votre serveur sur forge.

Pour finir, vous pouvez lancer cette requĂȘte sur votre base de donnĂ©es pour confirmer que la nouvelle valeur est bien prise en compte.

SHOW VARIABLES LIKE 'innodb_buffer_pool_size'; 
// 419430400

La résultat 419430400 est en octets, si vous la convertissez en MO en divisant par (1024 * 1024) vous obtiendrez bien 400MO.

Installer Xdebug avec Valet

En installant plusieurs versions de PHP sur votre Mac Ă  l’aide de Valet il est possible que Xdebug ne soit pas installĂ© par dĂ©fault sur tous les binaires de PHP.

La commande suivante affichera les extensions installĂ©es sur le binaire de PHP en question, rien ne s’affichera sur le rĂ©sultat du grep si Xdebug n’est pas installĂ©.

valet php -m | grep xdebug

Pour commencer, il sera necessaire d’identifier le dossier du binaire PHP à qui installer et configurer Xdebug, dans mon cas :

/usr/local/opt/php@8.2

Une fois le dossier identifié, une commande PECL sera nécessaire pour installer le package.

PECL (PHP Extension Community Library) est une bibliothĂšque pour les extensions PHP Ă©crites en C permettant d’ajouter des fonctionnalitĂ©s supplĂ©mentaires Ă  PHP.

/usr/local/opt/php@8.2/bin/pecl install xdebug

Une fois Xdebug installĂ© Ă  l’aide de PECL, il faudra l’activer dans le php.ini de la version PHP cible :

vim /usr/local/etc/php/8.2/php.ini

Ajoutez l’extension “xdebug.so” Ă  la clĂ© “zend_extension” :

zend_extension="xdebug.so"

Pour finir, vous serez forcĂ© de redĂ©marrer PHP pour activer la nouvelle extension, le plus simple sera tout simplement de restart valet lui-mĂȘme.

valet restart
  • php

Installer une version beta de PHP

Que ce soit pour tester une nouvelle beta ou s’amuser sur une vieille version, voyons comment installer en quelques minutes une version spĂ©cifique de php.

How to

Dans mon cas mon environnement de développement sera une simple image docker de debian.

docker run --name playground -it debian

Tout d’abord il est nĂ©cessaire d’installer quelques dĂ©pendances essentielles Ă  l’installation et au bon fonctionnement de php.

apt-get update && apt-get install build-essential wget pkg-config libxml2-dev vim libsqlite3-dev

Désormais à vous de trouver un lien pour télécharger une release de php, la plupart sont accessibles depuis le site officiel.

À l’heure oĂč j’écris ces lignes la beta en cours (8.1.0 BETA 3) est disponible Ă  cette url.

wget https://downloads.php.net/~ramsey/php-8.1.0beta3.tar.gz

Une fois download, dĂ©compressez l’archive de la release.

gunzip php-8.1.0beta3.tar.gz
tar -xf php-8.1.0beta3.tar

Déplacez-vous dans le dossier fraichement créé puis lancez la configuration.

cd php-8.1.0beta3
./configure

Si la configuration s’est correctement effectuĂ©e le message “Thank you for using PHP” devrait s’afficher, dans le cas contraire regardez quelques lignes plus haut Ă  la recherche des erreurs.

Il ne reste plus qu’à lancer l’installation qui vous laissera le temps de vous faire un ou deux cafĂ©s.

make && make install

C’est terminĂ©, la commande suivante doit dĂ©s lors afficher la bonne version de php.

php -v // PHP 8.1.0beta3 (cli)

Have fun !

Pourquoi trouve-t-on un hash hardcodé dans UserFactory ?

Lors d’une soirĂ©e live coding sur la chaine twitch DevMaker_tv nous nous sommes intĂ©ressĂ©s Ă  une subtilitĂ© de Laravel prĂ©sente dans le fichier UserFactory.

1
2
3
4
5
6
7
8
9
10
public function definition()
{
    return [
        'name' => $this->faker->name,
        'email' => $this->faker->unique()->safeEmail,
        'email_verified_at' => now(),
        'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password

        'remember_token' => Str::random(10),
    ];
}

Comment est-ce possible que le hash du champ password soit hardcodĂ© dans notre UserFactory alors que la valeur d’environnement APP_KEY de l’application n’est pas encore dĂ©finie ?

J’ai toujours bĂȘtement considerĂ© que APP_KEY Ă©tait utilisĂ© comme salt pour tous les hash, pour autant, lors d’une nouvelle installation de Laravel la valeur de APP_KEY
 est null.

Et puis entre nous, un salt statique commun Ă  toutes les nouvelles applications
 C’est sacrement naze comme idĂ©e.

Creusons dans le code pour comprendre le fin mot de l’histoire.

TL;DR

Le APP_KEY est uniquement utilisĂ© pour l’encryption.

Un password en base de données est quant à lui hashé par un password_hash avec bcrypt comme algorithme par défaut.

Cet hash de password est hardcodĂ© pour des questions d’optimisation, un password_hash Ă©tant fortement demandeur en ressources.

Exploration

Premiùres constatations, il n’y a rien de natif dans Laravel pour hash automatiquement l’attribut password d’un model User lors de son insertion.

C’est aux dĂ©veloppeurs de gĂ©rer ce besoin
 et la documentation n’est pas forcĂ©ment trĂšs loquace sur la marche Ă  suivre.

On apprend depuis la documentation que la mĂ©thode Auth::attempt(), permettant d’authentifier un utilisateur, procedera Ă  un hash du password submit pour le comparer avec celui prĂ©sent en base de donnĂ©es.

The attempt method accepts an array of key / value pairs as its first argument. The values in the array will be used to find the user in your database table. If the user is found, the hashed password stored in the database will be compared with the password value passed to the method via the array.

Pour comprendre ce qui se passe, nous allons retracer le code utilisĂ© lors d’une tentative d’authentification pour dĂ©terminer comment Laravel hash un password.

Comprendre l’authentification

Une tentative d’authentification fonctionne deux temps :

  • RĂ©cupĂ©rer un utilisateur.
  • VĂ©rifier que le password envoyĂ© est correct.

Le workflow d’authentification de Laravel est complexe et totalement configurable, la rĂ©cupĂ©ration d’un utilisateur et l’objet que retournera l’authentification sont dissociĂ©s.

Par dĂ©faut, la rĂ©cupĂ©ration de l’utilisateur se trouve dans la class EloquentUserProvider et l’objet retournĂ© est un model User.

Ce choix est configurable dans le fichier config\auth.php.

1
2
3
4
5
'providers' => [
    'users' => [
        'driver' => 'eloquent', // EloquentUserProvider 

        'model' => App\Models\User::class,
    ],

Lors d’une tentative d’authentification, apres avoir rĂ©cupĂ©rĂ© l’utilisateur depuis la mĂ©thode retrieveByCredentials du EloquentUserProvider, c’est au rĂŽle de la mĂ©thode validateCredentials de vĂ©rifier la concordance du password Ă  l’aide d’un objet hasher.

1
2
3
4
5
6
public function validateCredentials(UserContract $user, array $credentials)
{
    $plain = $credentials['password'];

    return $this->hasher->check($plain, $user->getAuthPassword());
}

AprĂšs vĂ©rification, il s’avĂšre que cette variable hasher est une instance de BcryptHasher, que Laravel gĂ©nĂšre depuis un design pattern Manager.

C’est ce BcryptHasher qui aura la charge de vĂ©rifier le pertinence du password.

Le check du password

Nous y sommes, voici comment Laravel hash un password lors d’une tentative d’authentification.

1
2
3
4
5
6
7
8
public function check($value, $hashedValue, array $options = [])
{
    if (strlen($hashedValue) === 0) {
        return false;
    }

    return password_verify($value, $hashedValue);
}

Le framework utilise une fonction password_verify native à php
 et notre APP_KEY n’intervient à aucun moment en tant que salt.

On peut donc affirmer que la valeur du APP_KEY n’impacte pas le hash des passwords.

Alors Ă  quoi il sert ?

Hasher n’est pas crypter

Le APP_KEY est uniquement utilisé en tant que salt pour crypter des informations depuis la class Crypt.

Les passwords des utilisateurs sont quant Ă  eux hashĂ© Ă  l’aide d’un algorithme que vous pouvez configuer dans config\hashing.php.

C’est pour cette raison que UserFactory contient un hash hardcodĂ©, $2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi correspondra toujours Ă  la valeur password.

Bisous, bonne journée.

  • php

Acceder aux éléments protégés d'une class

Nous sommes parfois amenés à vouloir manipuler une propriété ou une méthode protected de nos class.

L’utilisation de la reflection est envisageable pour modifier la visibilitĂ© mais une autre solution Ă©lĂ©gante Ă  base de closure est Ă©galement possible.

Considérons la class suivante :

1
2
3
4
class Foo {
    protected $bar;
    protected function bar() {}
}

L’idĂ©e est d’utiliser le Closure::call introduit en php7 pour injecter une closure dans le scope d’une instance de la class Foo.

1
2
3
4
5
6
7
8
$foo = new Foo();

$closure = function () {
    $this->bar = false;
    $this->bar();
};

$closure->call($foo);

Une fois notre closure injectĂ©, il lui est dĂ©sormais possible d’acceder Ă  toutes les proprietĂ©s et mĂ©thodes protected de notre class.

Certes, c’est pas usuel, mais vous ĂȘtes majeur et vaccinĂ©.