(Tuto) PHP POO & MySQL – Mémoire partagée entre plusieurs objets
- Introduction
- Première partie : la base de données
- Seconde partie : la classe abstraite « Process »
- Troisième partie : création de la classe Crawler et test de notre partage mémoire !
- Conclusion
Voici un petit PoC (Proof of concept) visant à faire un système de partage mémoire entre plusieurs objets dans plusieurs instances de PHP différentes.
La problématique : nous avons plusieurs scripts PHP lancés simultanément qui utilisent les mêmes objets. Les différentes instances de ces objets sont dans l’impossibilité de s’échanger des données directement. Comment faire communiquer de façon transparente ces différentes instances d’objets ?
La solution : surcharger l’accès des propriétés des objets avec les méthodes magiques « __set() » et « __get() » de PHP.
Introduction
Surcharger quoi ? Elles servent à quoi les « méthodes magiques » ? J’ai rien compris à tout ça moi !
![]()
Le principe de « surcharge » signifie « remplacer une fonction déjà existante ». En quelque sorte, nous allons remplacer le comportement par défaut de PHP lors de la manipulation des propriétés des objets.
Une « méthode magique » est une fonction qui permet ce remplacement. Il en existe plusieurs, mais nous nous restreindrons à « __get() » et « __set() » dans cet article
.
Feinter PHP avec les méthodes magiques !
Actuellement, PHP stocke et lit les propriétés dans un espace mémoire isolé que seul le script PHP peut lire. Vous l’avez compris, « __set() » va nous permettre de remplacer la fonction qui écrit les valeurs des propriétés, et « __get() », la fonction qui accède aux valeurs des propriétés.
Le remplacement de ces fonctions va nous permettre de stocker et lire ces valeurs un autre endroit, par exemple : MySQL !
Aller encore plus loin
Cela ne va-t-il pas générer beaucoup de trafic entre PHP et MySQL si l’on appelle la base de données à chaque lecture / écriture de variable ?
![]()
Effectivement, il est sûr que cette méthode peut être assez gourmande en ressources serveur. C’est pour cette raison que je réaliserai un autre PoC présentant le même concept avec la mémoire partagée linux. Avis aux amateurs de linux et de systèmes complexes
.
Cas d’exemple
Dans la suite de cet article nous prendrons comme exemple un « crawler multithread ». C’est, en gros, un « robot multi-processus qui télécharge toutes les pages d’un site web ».
En effet, il est intéressant dans notre cas d’avoir plusieurs petits robots qui téléchargent simultanément des pages tout en se partageant des informations comme :
- Les liens nouvellement découverts (car chaque robot pourra enregistrer de nouvelles pages que d’autres peuvent télécharger)
- Quelles pages sont déjà mémorisées (il est inutile de télécharger plusieurs fois la même page).
- etc, etc…
Première partie : la base de données
Bah oui, on va utiliser MySQL, il faut donc créer des tables
! Nous allons donc créer 3 tables :
- process_running : les processus parents en cours d’exécution
- process_thread_running : les processus enfants en cours d’exécution
- process_shared_memory : la table de partage mémoire où seront stockées toutes les variables à partager
C’est quoi cette histoire de processus parents et enfants ? Quel est l’intérêt d’avoir ces deux tables ?
![]()
Déjà, pour être clair : un processus correspondra à une instance d’un objet qui doit partager sa mémoire, dans notre exemple notre objet Crawler.
Cependant, nous allons départager deux familles de processus, les parents et les enfants :
- Le processus parent : c’est celui qui va être appelé manuellement. Il va initialiser l’environnement de travail. Par exemple, dans notre cas, il va stocker l’adresse du site qui devra être téléchargé.
- Les processus enfants (ou threads) : ce sont ceux qui seront appelés comme des « sous-processus » par le processus parent. Ils permettront à notre processus parent d’exécuter plusieurs tâches simultanément. Par exemple le téléchargement de plusieurs pages à la fois !
Vous avez compris le principe ? Bravo
; c’était le plus compliqué. La suite est plus simple !
La table des processus parents process_running
Comme nous l’avons dis précédemment, cette table stockera tous les processus parents qui ont été lancés (généralement un seul, mais sait-on jamais, si vous souhaitez réaliser une usine à gaz
). Voici la structure de la table que nous allons utiliser :
CREATE TABLE IF NOT EXISTS `process_running` ( `offset` int(10) unsigned NOT NULL AUTO_INCREMENT, `state` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT '1:running, 2:sleeping', `start` int(10) unsigned NOT NULL, PRIMARY KEY (`offset`) );
- « offset » sera le numéro d’identification de notre processus, qui sera attribué par la base de données.
- « state » définira l’état de notre processus (s’il est en cours de fonctionnement (1), s’il est en attente (2), ou s’il est en cours d’arrêt (0))
- « start » le timestamp PHP du lancement du processus (pour savoir depuis combien de temps il est démarré
)
La table des processus enfants (ou threads) process_thread_running
Cette table aura exactement la même structure que « process_running » à une exception près : la clé primaire sera double.
CREATE TABLE IF NOT EXISTS `process_thread_running` ( `process_offset` int(10) unsigned NOT NULL, `thread_offset` int(10) unsigned NOT NULL AUTO_INCREMENT, `state` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT '1:running, 2:sleeping', `start` int(10) unsigned NOT NULL, PRIMARY KEY (`process_offset`,`thread_offset`) );
Effectivement, un processus parent est identifié par un numéro unique. Mais un processus enfant est désigné par le sien, en plus de celui du parent… vous me suivez
? Donc on a :
- « process_offset » qui est le numéro du processus parent
- « thread_offset » qui est le numéro du processus enfant courant
La table de partage mémoire process_shared_memory
Enfin, le plus intéressant, la table qui permettra le partage mémoire de nos processus :
CREATE TABLE IF NOT EXISTS `process_shared_memory` ( `process_offset` int(10) unsigned NOT NULL AUTO_INCREMENT, `variable` varchar(255) NOT NULL, `value` mediumtext NOT NULL, PRIMARY KEY (`process_offset`, `variable`) );
- « process_offset » est le numéro du processus parent. Etant donné que toutes les variables seront partagées au sein d’un processus parent ainsi que tous ses enfants, nous allons lier les variables à partager à un numéro de processus parent.
- « variable » est le nom de la variable qui est partagée, tout simplement
- « value » est la valeur de cette variable. J’ai utilisé « mediumtext » comme type, mais on pourra utiliser ce que l’on veut.
Seconde partie : la classe abstraite « Process »
Nous allons créer une petite classe abstraite « Process » qui permettra à notre classe « crawler » de disposer de toutes les méthodes indispensables pour la création de processus (dans le but de partager nos variables dans les tables précédemment créées).
Heu… une classe abstraite ? Tu parles d’héritage là non ?
![]()
Oui, je vais faire un petit rappel
. Un héritage est le fait de partager des méthodes et des attributs d’une classe générique vers une classe plus spécifique. Cela permet une bonne réutilisation des méthodes déjà développées.
Dans notre exemple, nous allons partager les méthodes d’accès aux variables partagées (si vous avez suivi « __get() » et « __set() »
) d’une classe générique « Process » à notre classe spécifique « Crawler ».
Vous remarquerez par la suite que l’on ne partagera pas uniquement nos deux méthodes magiques. En effet, il nous reste toute la gestion des processus (création de threads, destruction de processus, suppression des variables de processus tués, etc…). Bon appétit
!
Présentation des méthodes de la classe
Notre classe « Process » dispose donc de plusieurs méthodes qui seront »ajoutées » à notre classe « Crawler ». Je vais vous présenter très rapidement les différentes méthodes :
- « __construct($offset = null) » est le constructeur. Il permet de créer une instance de processus parent ou enfant (thread). Sans paramètre, il va noter le processus dans la table « process_running », récupérer un numéro unique et retourner un processus parent. Sinon, si le constructeur est appelé avec un paramètre, il assumera le paramètre comme numéro unique d’un processus parent. Ainsi, il créera un thread de celui-ci, et le notera dans « process_thread_running ». Vous avez compris ? Bien, on continue
! - « __destruct() » est la méthode qui sera appelée lors de la destruction d’un processus (ou thread). Cette destruction est manuelle ou automatique (fin de document ou script CLI). Lors de la destruction d’un processus parent, un signal d’arrêt est envoyé à tous ses threads. La destruction d’un processus enfant entraine la suppression de son enregistrement dans sa table.
- « check($dontKill = false) » permet de voir l’état d’un thread (s’il doit être tué ou non). Il peut être tué dans deux cas : si son processus parent est tué ou s’il a reçu un signal d’extinction. Le paramètre $dontKill permet de définir l’action à réaliser si le processus doit être tué. Par défaut, la fonction exécutera un « exit; ». Cependant, si vous spécifiez « true », la fonction ne fera que retourner l’état du processus (true ou false).
- « __set($variable, $value) » est la méthode magique qui permettra de stocker automatiquement nos variables dans MySQL
- « __get($variable) » est celle qui récupèrera les valeurs des variables !
- Enfin, « createThread() » retourne un thread d’un processus parent (il partage donc l’espace mémoire du parent et de ses confrères threads du même parent
) !
Vous pouvez télécharger la classe ici : Process.class (n’oubliez pas de configurer les accès à votre base de données avant utilisation
)
Troisième partie : création de la classe Crawler et test de notre partage mémoire !
Trêve de plaisanteries, attaquons-nous au code !
Il nous reste plus qu’à créer une classe « Crawler » qui héritera de notre classe « Process » précédemment étudiée. Voici le code minimal de la classe :
class Crawler extends Process {
public function __construct($offset = null) {
parent::__construct($offset);
}
}
Collez ce code dans un fichier du nom de « Crawler.class.php » et tout est prêt ! Maintenant, testons si tout fonctionne bien. Nous allons donc créer un petit script qui va inclure nos deux classes, créer une instance de l’objet « Crawler », puis deux threads de l’instance et partager des variables. Voici notre code :
// On inclue nos classes include "Process.class.php"; include "Crawler.class.php"; // On construit notre processus parent $crawler = new Crawler(); // On initialise deux threads (processus enfants) $crawlerThread1 = $crawler->createThread(); $crawlerThread2 = $crawler->createThread(); // On stocke une variable dans le premier thread $crawlerThread1->yeah = "Yo ! T'aimes bien ifnot ? "; // On affiche cette variable à partir du second thread ! echo $crawlerThread2->yeah; // On peux aussi modifier cette variable dans le second thread // et l'afficher à nouveau dans le premier ! $crawlerThread2->yeah = "Oui, surtout depuis qu'on peut communiquer ensemble ! "; echo $crawlerThread1->yeah;
Sauvegardez, et lancez le script ! Si vous voyez écrit « Yo ! T’aimes bien ifnot ? Oui, surtout depuis qu’on peut communiquer ensemble ! » c’est que tout a bien fonctionné
!
Ok ça marche… Mais il n’y a rien de particulier ici, Il suffit d’utiliser une constante ou $GLOBALS pour l’échange entre objets …
![]()
Cet exemple ne le montre pas concrètement, mais l’avantage de ce système est que les objets partagent leurs variables en dehors de la mémoire du script PHP.
En effet, si vous lancez un processus parent, que vous copiez son numéro en base de données et que vous lancez deux threads simultanés sur deux scripts, ils pourront toujours partager des données.
Est-ce normal que ma base de données reste vide après exécution du script ?
Oui, à la fin de l’exécution du script, tous les objets sont détruits avec la méthode magique « __destruct() » (je vous avais dit qu’il y en avait d’autres
) . Cette méthode supprime les enregistrements de processus dans les tables ainsi que les variables liées à ces objets (référez-vous à la présentation des méthodes de la classe « Process »).
Si vous souhaitez voir les données dans la base de données, collez un « sleep(10); » à la fin du script de test, cela aura pour effet de demander à PHP d’attendre 10 secondes avant de détruire les objets. Vous pourrez alors consulter les données dans les tables pendant l’attente (si vous êtes lent de la souris, vous pouvez mettre 20 secondes
).
Conclusion
Nous avons vu ensemble comment réaliser un système de partage mémoire entre deux objets avec les méthodes magiques de PHP en utilisant MySQL. Cela peut vous permettre de réaliser des applications complexes ayant des besoins bien spécifiques en performances et en communications inter-classes.
Il est certain que ce système peut être lourd s’il y a beaucoup de manipulations de données. Un prochain article sera axé sur le même principe en utilisant les fonctions « shmop » de PHP (mémoire partagée linux).
Je suis à l’écoute de vos idées et de vos suggestions


