T: / Articles techniques / PHP
Découvre comment utiliser array_map en PHP avec notre guide complet. Exemples basiques et avancés.
La fonction array_map en PHP est une fonction dite intégrée à laquelle on applique une fonction spécifiée qui viendra impacter chaque élément d’un tableau. La fonction parcourt donc tout le tableau et permet de retourner un nouveau tableau contenant les résultats spécifiques voulus. C’est ce que nous appelons la fonction callback en PHP.
array_map(?callable $fonction, array $tableau, array ...$parametres): array
Il y a 3 paramètres :
En s’appuyant sur des exemples inspirés de nos challenges, nous allons voir :
Puis nous verrons pourquoi utiliser array_map à la place d’une boucle classique ?
Et enfin, on dira un petit mot sur les erreurs de stack overflow et comment les éviter.
Exemples d’utilisation de la fonction array_map en PHP
Les exemples seront illustrés avec WALL-E, célèbre petit robot coincé sur Terre pour la nettoyer. Il passe en revue des déchets et s’il y en a assez, il va pouvoir les compacter en un « bloc ».
$dechets = ['Petit Tas', 'Gros Tas', 'Dechet seul', 'Dechet seul', 'Gros Tas'];
On crée une fonction qui permet de transformer des déchets en bloc :
function faireUnBloc(string $dechet): string
{
if ($dechet === 'Gros Tas') {
return 'Bloc';
}
return $dechet;
}
Avec une boucle classique, on peut écrire ce code pour traiter les déchets :
$dechetsApresTraitement = [];
foreach ($dechets as $dechet) {
$dechetsApresTraitement[] = faireUnBloc($dechet);
}
Avec array_map, on peut écrire : de façon plus concise :
$dechets = array_map('faireUnBloc', $dechets);
On applique la fonction faireUnBloc à tous les déchets du tableau $dechets. On voit tout de suite qu’on a pas eu besoin de créer une autre variable. On fait aussi l’économie du foreach ( … as …) et des accolades.
Après le array_map, $dechets contient donc :
Array
(
[0] => Petit Tas
[1] => Bloc
[2] => Dechet seul
[3] => Dechet seul
[4] => Bloc
)
La fonction faireUnBloc peut se trouver dans une classe dédiée, par exemple :
class MachinerieInterne
{
public static function faireUnBloc(string $dechet): string
{
if ($dechet === 'Gros Tas') {
return 'Bloc';
}
return $dechet;
}
}
C’est la même logique mais cette fois-ci dans une méthode statique d’une classe.
Il faudra utiliser la syntaxe suivante pour l’appeler :
$dechets = array_map([MachinerieInterne::class, 'faireUnBloc'], $dechets);
Cette fois-ci $fonction contient un tableau qui précise la classe Et sa méthode. Le reste de la syntaxe est inchangée.
La fonction et les données peuvent aussi être dans la même classe.
// Instructions
$dechets = ['Petit Tas', 'Gros Tas', 'Dechet seul', 'Dechet seul', 'Gros Tas'];
$robot = new Robot($dechets);
$robot->traiteLesDechets();
// Classe
class Robot
{
public function __construct(
public array $dechets
) {}
public function faireUnBloc(string $dechet): string
{
if ($dechet === 'Gros Tas') {
return 'Bloc';
}
return $dechet;
}
public function traiteLesDechets(): void
{
// Ici :)
$this->dechets = array_map([$this, 'faireUnBloc'], $this->dechets);
}
}
Encore un tableau, mais avec $this pour bien préciser que la méthode faireUnBloc fait parti du même objet.
On va changer un peu, cette fois-ci on travaille avec le poids des déchets :
$poidsDesDechets = [1, 2, 3];
En passant sur les déchets, WALL-E va augmenter leur poids de 1. On n’est pas obligé de créer une fonction en amont, on peut directement passer la logique de la fonction dans le array_map en utilisant une fonction anonyme. En effet, on l’utilise sans lui donner de nom :
$poidsDesDechets = array_map(function ($poids) { return $poids + 1; }, $poidsDesDechets);
Mais c’est illisible tout ça ! Allez, on indente un peu pour faciliter la lecture :
$poidsDesDechets = array_map(
function ($poids) {
return $poids + 1;
},
$poidsDesDechets
);
Tout de suite plus clair, non ? On a donc déclaré une fonction directement dans le array_map. Cette fonction a forcément un paramètre, qui correspond à un élément du tableau passé en second paramètre (du array_map).
Quand on a une logique assez « basique » comme celle-ci, la fonction anonyme peut avoir du sens. Techniquement, on peut y mettre autant de complexité qu’on le souhaite, mais je le déconseille. S’il y a plus de 2 ou 3 instructions, il vaut mieux sortir la fonction pour clarifier le code et permettre de tester la fonction en dehors du array_map.
Après le array_map, $poidsDesDechets vaut :
Array
(
[0] => 2
[1] => 3
[2] => 4
)
Le mot clé use permet d’utiliser une variable qui n’est pas dans le scope de la fonction.
Je veux gérer l’incrémentation du poids :
$poidsDesDechets = [1, 2, 3];
$poidsAAjouter = 2;
$poidsDesDechets = array_map(
function ($poids) use ($poidsAAjouter) {
return $poids + $poidsAAjouter;
},
$poidsDesDechets
);
Après le array_map, $poidsDesDechets vaut :
Array
(
[0] => 3
[1] => 4
[2] => 5
)
Depuis PHP 7.4, à la manière d’autres langages, on peut utiliser des fonctions fléchées. La syntaxe est encore plus concise, et le use n’est plus nécessaire.
Avec $poidsAAjouter :
$poidsDesDechets = [1, 2, 3];
$poidsAAjouter = 3;
$poidsDesDechets = array_map(
fn($poids) => $poids + $poidsAAjouter,
$poidsDesDechets
);
Plus de « function », plus de « return », remplacé par « fn » et « => ». Même remarque que précédemment, c’est une syntaxe intéressante quand il y a peu d’instructions à exécuter, sinon la lecture et compréhension du code va (très) vite se complexifier.
Après le array_map, $poidsDesDechets vaut :
Array
(
[0] => 4
[1] => 5
[2] => 6
)
Sans pour autant lui donner de nom, il est possible de stocker une fonction dans une variable :
$traitementFort = function ($poids) {
return $poids + 10;
};
Et l’utilisation du array_map :
$dechets = [1, 2, 3];
$dechets = array_map($traitementFort, $dechets);
Cette fois-ci, pas de chaine de caractères ou de tableau, on passe directement $traitementFort en premier paramètre du array_map. C’est intéressant à utiliser si on veut pouvoir réutiliser la logique de cette fonction sans pour autant créer une fonction dédiée. Les remarques précédentes sur la complexité s’appliquent toujours 😉
On va compliquer un peu les choses ! On va mélanger le poids des déchets et leur forme au sein d’une classe dédiée :
class Dechet
{
public string $forme = 'Rien';
public function __construct(
public int $poids
) {}
public function transformeEnBloc(): void
{
if ($this->poids >= 4) {
$this->forme = 'Bloc';
}
}
}
Puis une nouvelle classe Robot qui contient directement des déchets dans une propriété :
class Robot
{
/**
* @var Dechet[] $dechets
*/
public array $dechets = [];
public function ajouteDechet(int $poids): void
{
$this->dechets[] = new Dechet($poids);
}
}
On va rajouter les méthodes une par une par la suite.
On peut initialiser le Robot de cette façon :
$robot = new Robot;
$robot->ajouteDechet(2);
$robot->ajouteDechet(4);
$robot->ajouteDechet(8);
$robot->ajouteDechet(3);
$robot a donc une propriété dechets qui contient :
Array
(
[0] => ArrayMap\Exemple2\Dechet Object
(
[forme] => Rien
[poids] => 2
)
[1] => ArrayMap\Exemple2\Dechet Object
(
[forme] => Rien
[poids] => 4
)
[2] => ArrayMap\Exemple2\Dechet Object
(
[forme] => Rien
[poids] => 8
)
[3] => ArrayMap\Exemple2\Dechet Object
(
[forme] => Rien
[poids] => 3
)
)
Ce qui va être intéressant dans un contexte objet, c’est qu’on aura plus forcément de return dans les fonctions passées à array_map puisque les modifications vont s’effectuer directement sur les instances des objets Dechet.
Voici quelques exemples, en reprenant les mêmes traitement qu’avant :
// Méthodes contenues DANS la classe Robot
// Pas de return, on appelle la méthode transformeBloc pour chaque élément de $this->dechets, donc on travaille bien sur une instance de Dechet
public function traiteLesDechets(): void
{
array_map(function($dechet) {
$dechet->transformeEnBloc();
}, $this->dechets);
}
// Utilisation d'une fonction fléchée
public function augmentePoids(): void
{
array_map(fn($dechet) => $dechet->poids++, $this->dechets);
}
// Utilisation d'un use
public function augmentePoidsPrecis(int $poidsPrecis): void
{
array_map(
function($dechet) use ($poidsPrecis) {
$dechet->poids += $poidsPrecis;
},
$this->dechets
);
}
// Fonction fléchée... avec un ternaire ;)
public function augmentePoidsPourUneForme(string $forme): void
{
array_map(
fn($dechet) => $dechet->forme == $forme ? $dechet->poids++ : $dechet,
$this->dechets
);
}
Je me répète mais il n’est pas obligatoire d’utiliser ces syntaxes. Si le array_map apporte une simplification dans bien des situations, ce n’est pas toujours le cas avec les fonctions anonymes. A utiliser avec parcimonie, et toujours privilégier la lisibilité du code.
Avec une boucle, la dernière méthode peut s’écrire comme ça :
public function augmentePoidsPourUneForme(string $forme): void
{
foreach ($this->dechets as $dechet) {
if ($dechet->forme != $forme) {
continue;
}
$dechet->poids++;
}
}
A toi de voir ce que tu préfères, ce que tu sais lire efficacement, toi, ton futur toi, tes collègues, etc. 😉
En reprenant l’exemple de la fonction stockée, je stocke une nouvelle fonction :
$traitementSurMesure = function ($poids, $poidsAAjouter) {
return $poids + $poidsAAjouter;
};
Cette fois-ci $poidsAAjouter n’est pas défini dans mon programme. Je vais pouvoir le définir au moment de l’appel de mon array_map :
$dechets = [1, 2, 3];
$modifications = [1, 1, 2];
$dechets = array_map(
$traitementSurMesure,
$dechets,
$modifications
);
Cela signifie que pour le premier élément de $dechets, c’est le premier élément de $modifications qu’il faut utiliser. Après le array_map, $dechets vaut donc :
Array ( [0] => 2 [1] => 3 [2] => 5 )
Et on peut avoir des fonctions avec autant de paramètres que nécessaire. Exemple avec 2 :
$traitementSurMesureComplexe = function ($poids, $poidsAAjouter, $ratio) {
return ($poids + $poidsAAjouter) * $ratio;
};
$dechets = [1, 2, 3];
$modifications = [1, 1, 3];
$ratios = [1, 2, 0.5];
$dechets = array_map(
$traitementSurMesureComplexe,
$dechets,
$modifications,
$ratios
);
Les tableaux de paramètres à appliquer se mettent les uns à la suite des autres.
Après le array_map, $dechets vaut donc :
Array
(
[0] => 2
[1] => 6
[2] => 3
)
Là on arrive à un peu de magie noire…
Voici ce que dit la documentation « null
peut être passé comme valeur à callback
pour exécuter une opération zip sur plusieurs tableaux. Si seulement array
est fourni, array_map() retournera le tableau d’entrée.«
Donc si je ne met qu’un seul tableau, il n’y a aucun changement :
$dechets = [1, 2, 3];
$dechets = array_map(null, $dechets);
// $dechets n'a pas changé
Par contre, si je passe plusieurs tableaux, les valeurs des tableaux vont être regroupées selon leurs index dans un nouveau tableau. Toutes les valeurs d’index 0 vont être mises ensemble, puis celles d’index 1, etc. le tableau retourné sera sous forme de clés séquentielles correspondantes entre les différentes colonnes de chaque tableau.
Voici un exemple pour illustrer :
$dechets = [1, 2, 3];
$dechets2 = [4, 5];
$dechets3 = [7, 8, 9, 10];
$dechets = array_map(null, $dechets, $dechets2, $dechets3);
$dechets vaut :
Array
(
[0] => Array
(
[0] => 1
[1] => 4
[2] => 7
)
[1] => Array
(
[0] => 2
[1] => 5
[2] => 8
)
[2] => Array
(
[0] => 3
[1] =>
[2] => 9
)
[3] => Array
(
[0] =>
[1] =>
[2] => 10
)
)
Au moment où j’écris cet article, je n’ai jamais utilisé cette syntaxe. Mais je pense que ça peut être utilisé dans des problèmes en 2 dimensions. Si par exemple j’ai des infos en ligne et que je veux les basculer en colonnes. Je vais tester des choses et je mettrais à jour l’article 😉
Quand on maitrise bien sa syntaxe et ses possibilités, array_map devient agréable à utiliser et peut être favorisé par rapport à une boucle for ou foreach pour ces raisons :
Encore une fois, au premier abord, array_map peut paraitre plus complexe à utiliser et comprendre qu’une boucle. A l’usage, il devient très pratique. Mais ce n’est pas pour autant qu’il faut en abuser ! Si on a trop d’opérations complexes à réaliser, plusieurs tableaux à manipuler, une boucle sera surement plus appropriée !
Lorsque l’on utilise la fonction array_map en PHP, il est possible de rencontrer des problèmes de performance voir des erreurs fatales.
Par exemple, si tu utilises array_map dans ton code de façon récursive et/ou avec une trop grande quantité de données à traiter, cela peut entraîner une trop forte consommation de la mémoire et générer ce que l’on appelle un stack overflow. Les fonction s’appellent elles-mêmes trop de fois, ce qui entraine un dépassement de la taille maximale de la pile d’appels (= stack overflow).
Plusieurs points à surveiller si une erreur de ce type survient :
Quelques challenges pour lesquels tu pourras mettre en pratique array_map :
Quelques corrigés qui utilisent array_map :
Other content to discover
Corrections, challenges, news, technical monitoring... no spam.