Top Code 2024, les challenges sont de nouveau disponibles dans les boards pour les participant(e)s => Boards

array_map en PHP – Le guide complet

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.

Description de la fonction array_map en PHP

array_map(?callable $fonction, array $tableau, array ...$parametres): array

Il y a 3 paramètres :

  • $fonction qui est la fonction qui sera appliquée aux éléments du tableau. On verra qu’il y a plusieurs façons de définir cette fonction. Comme l’indique le « ? » on peut également passer null comme valeur.
  • $tableau, notre fameux tableau qui contient les éléments qui seront traités par la fonction
  • $parametres avec l’opérateur de décomposition signifie qu’on va pouvoir passer des valeurs supplémentaires en paramètre de $fonction. On verra ça à la fin.

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'];

array_map fait appel à une fonction pré-définie

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
)

array_map en PHP fait appel à une méthode d’une classe

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.

array_map en PHP fait appel à une méthode d’une classe, dans la même classe

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.

array_map en PHP utilise une fonction anonyme

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
)

array_map en PHP utilise une fonction anonyme, avec un use

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
)

array_map en PHP utilise une fonction fléchée

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
)

array_map en PHP utilise une fonction anonyme stockée

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 😉

array_map en PHP dans un contexte objet

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. 😉

array_map en PHP avec des paramètres supplémentaires

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
)

array_map en PHP avec null comme fonction

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 😉

Pourquoi utiliser array_map à la place d’une boucle classique en PHP ?

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 :

  1. Lisibilité du code : le code est plus concis et plus facile à lire. On comprend tout de suite qu’on souhaite réaliser une opération sur tous les éléments d’un tableau. Dans une boucle classique, on peut faire des choses complètement différentes.
  2. Fonctions de rappel : Passer la logique dans une fonction externe à la boucle permettra notamment de réutiliser cette fonction ailleurs (dans un enchainement de fonctions, dans une concaténation, etc.) mais aussi d’en faciliter les tests unitaires. Le code est alors plus robuste et réutilisable.
  3. Immutabilité des données : array_map retourne un nouveau tableau. Le tableau passé en paramètre n’est pas modifié.

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 !

Comment gérer les erreurs de stack overflow lors de l’utilisation de array_map en PHP

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 :

  • Taille du ou des tableaux manipulés : si les tableaux contiennent trop de données, tu peux essayer de découper les données en plusieurs tableaux.
  • Récursivité : dans un contexte récursif, on évitera d’utiliser array_map et on préfèrera un foreach.
  • Architecture : bien réfléchir à la façon dont le code est ordonné, et réfléchir à décomplexifier certaines sections.
  • Mémoire disponible : c’est le dernier recours, pas forcément recommandé, si jamais les autres points ont été traités et que le code DOIT absolument s’exécuter de cette façon, il est alors envisageable de modifier la mémoire disponible. C’est un réglage qui se fait le plus souvent en amont du programme, en gérant la directive « memory_limit » du fichier php.ini ou en effectuant des réglages dans le panel du serveur.

Pour aller + loin

Quelques challenges pour lesquels tu pourras mettre en pratique array_map :

Quelques corrigés qui utilisent array_map :


Qui a codé ce superbe contenu ?

Keep learning

Autres contenus à découvrir


Ta newsletter chaque mois

Corrigés, challenges, actualités, veille technique... aucun spam.