{"id":24650808,"url":"https://github.com/tangoman75/globalis","last_synced_at":"2026-04-18T04:03:14.774Z","repository":{"id":114061577,"uuid":"344609987","full_name":"TangoMan75/globalis","owner":"TangoMan75","description":"[TUTO] Exercice d'algorithmique en pur PHP en Test Driven Developpement (TDD), en respectant les principes de la Programmation Orienté Objet (POO) avec Composer et PHPStorm.","archived":false,"fork":false,"pushed_at":"2021-03-10T14:11:28.000Z","size":598,"stargazers_count":1,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-20T21:07:56.770Z","etag":null,"topics":["algorithmic","article","composer","php","phpstorm","poo","prolosaures","tangoman","tdd"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/TangoMan75.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-03-04T21:08:20.000Z","updated_at":"2022-07-14T22:31:24.000Z","dependencies_parsed_at":null,"dependency_job_id":"b7b9e7cc-656d-4f3f-af5b-c78b483dba26","html_url":"https://github.com/TangoMan75/globalis","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/TangoMan75/globalis","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TangoMan75%2Fglobalis","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TangoMan75%2Fglobalis/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TangoMan75%2Fglobalis/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TangoMan75%2Fglobalis/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/TangoMan75","download_url":"https://codeload.github.com/TangoMan75/globalis/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TangoMan75%2Fglobalis/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31955920,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-18T00:39:45.007Z","status":"online","status_checked_at":"2026-04-18T02:00:07.018Z","response_time":103,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["algorithmic","article","composer","php","phpstorm","poo","prolosaures","tangoman","tdd"],"created_at":"2025-01-25T18:16:43.439Z","updated_at":"2026-04-18T04:03:14.767Z","avatar_url":"https://github.com/TangoMan75.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"![GH language](https://img.shields.io/github/languages/top/TangoMan75/globalis)\n[![GH tag](https://img.shields.io/github/v/tag/TangoMan75/globalis)](https://github.com/TangoMan75/globalis/tags)\n[![GH release](https://img.shields.io/github/v/release/TangoMan75/globalis)](https://github.com/TangoMan75/globalis/releases)\n![GH license](https://img.shields.io/github/license/TangoMan75/globalis)\n![GH stars](https://img.shields.io/github/stars/TangoMan75/globalis)\n![PHP CI](https://github.com/TangoMan75/globalis/workflows/PHP%20CI/badge.svg)\n![visitors](https://visitor-badge.glitch.me/badge?page_id=TangoMan75.globalis)\n\nGlobalis\n========\n\nExercice d'algorithmique en pur PHP en _Test Driven Developpement_ (TDD), en respectant les principes de la Programmation Orienté Objet (POO) avec Composer et PHPStorm.\n\nJ'ai récemment répondu à une offre pour un poste de **[Développeur PHP confirmé](https://www.globalis-ms.com/jobs/offres-emploi-stage-mission/cdi-developpeur-php-confirme/)** chez [Globalis](https://globalis-ms.com) qui est une ESN Parisienne qui est assez investie dans la communauté PHP, puisqu'ils sont membres de l'[AFUP](https://afup.org/profile/company/169-globalis-media-systems) 👍.\n\nIls m'ont proposé un test technique sous la forme d'un exercice d'algorithmique dont voici l'énoncé :\n\nParavent\n--------\n\n### Énoncé\n\nPas de répit pour les Prolosaures ! La mer étant à peine redescendue à son niveau normal, une nouvelle prophétie annonce la venue d'un ouragan.\n\nDes vents violents venus de l'ouest emporteront tout ce qui sera exposé, mais ils seront interceptés par le relief montagneux, derrière lequel les\nProlosaures seront à l'abri de la catastrophe imminente.\n\nVotre but est de déterminer la surface totale protégée par les montagnes.\n\n### Entrée\n\n- La première ligne est un entier n, la largeur du continent.\n- La ligne suivante contient n entiers h 1 , ..., h n séparés par des espaces donnant les altitudes du terrain, d'ouest en est.\n\nLe vent arrive de la gauche (de l'ouest) et lorsqu'il rencontre une montagne, toutes les terres qui sont plus à droite et de hauteurs inférieures à celle-ci\nsont à l'abri.\n\nChaque altitude correspond à un terrain d'une unité de surface.\n\n### Sortie\n\nLa sortie est un unique entier qui est la surface d'abri disponible.\n\n### Contraintes\n\n- 1 ≤ n ≤ 100 000\n- 0 ≤ h ≤ 100 000\n\n### Contraintes d'exécution\n\n| Utilisation mémoire maximum | Temps d'exécution maximum |\n|-----------------------------|---------------------------|\n| 2000 kilo-octets            | 500 millisecondes         |\n\n### Exemples d'entrée/sortie\n\n\u003e Exemple d'entrée\n```\n10\n30 27 17 42 29 12 14 41 42 42\n```\n\n\u003e Exemple de sortie\n```\n6\n```\n\n### Commentaire\n\n![./doc/images/paravent.png](./doc/images/paravent.png)\n\nTout ce qui est dans la zone rouge sera emporté par la tempête.\n\n---\n\nIntroduction\n------------\n\nCe petit exercice à l'air bien sympathique, chez Globalis ils ne se sont pas contenté de m'envoyer un test sur [codility](https://www.codility.com), [codingame](https://www.codingame.com) ou [hackerrank](https://www.hackerrank.com), ce que j'apprécie.\n\nDu coup je vais en profiter pour me lancer un _challenge_ perso ; **Je vais réaliser ce test ET écrire un tuto complet du projet étape par étape, SANS utiliser Symfony.**\n\n🏁 Allez, c'est parti mes canards ! 🏁\n\n![https://media.giphy.com/media/I4bPiOcVZNJZ2b0lPM/giphy.gif](https://media.giphy.com/media/I4bPiOcVZNJZ2b0lPM/giphy.gif)\n\n📝 NOTE : Alors je vais considérer que vous êtes débutant total et partir de zéro, ne vous offusquez pas, mon but est de donner le maximum de détails.\n\n📝 NOTE 2 : J'ai écrit ce tuto en français, mais tous les commentaires dans le code sont en anglais. Ce n'est pas du code que j'ai copié de [Stack Overflow](https://stackoverflow.com) ; C'est fait exprès, on ne devrait **jamais** utiliser autre chose que de l'anglais dans le code.\n\n---\n\n\u003e **TL;DR**\n\u003e\n\u003e Entrez les commandes suivantes dans votre terminal :\n\u003e\n\u003e ```bash\n\u003e $ git clone https://github.com/TangoMan75/globalis\n\u003e $ cd globalis\n\u003e $ make install\n\u003e $ make tests\n\u003e ```\n\nTable des matières\n------------------\n\n- [1 ⚡ Environnement de développement](#1--environnement-de-développement)\n- [2 ⚡ Création du projet](#2--création-du-projet)\n- [3 ⚡ Installation des dépendances du projet](#3--installation-des-dépendances-du-projet)\n- [4 ⚡ Commencons à coder](#4--commencons-à-coder)\n- [5 ⚡ Mise en place du TDD](#5--mise-en-place-du-tdd)\n- [6 ⚡ Solution](#6--solution)\n- [⚡ Conclusion](#-conclusion)\n\n1 ⚡ Environnement de développement\n-----------------------------------\n\n### 👉 Une parenthèse sur Ubuntu\n\nJ'utilise Ubuntu, c'est mon système d'exploitation préféré, il me permet de gagner énormément en productivité. Je vais considérer que vous êtes sur un environnement Linux vous aussi, si vous êtes sur Mac certaines commandes risquent de ne pas fonctionner. Si vous êtes sur Windows 👎, formatez directement votre disque dur et installez la dernière version LTS d'[Ubuntu](https://ubuntu.com/download) on est là pour faire du code, pas pour jouer à _Fortnite_.\n\n![https://media.giphy.com/media/3og0ICG4WxdKSRzE3K/giphy.gif](https://media.giphy.com/media/3og0ICG4WxdKSRzE3K/giphy.gif)\n\n### 👉 Installer Make\n\nSi vous étiez en train de jouer à _Fortnite_ et que vous venez donc de formater votre DD ; Vous aurez sûrement besoin de `make` qui n'est pas forcément installé par défaut sur votre système :\n\n```bash\n$ sudo apt-get install --assume-yes make\n```\n\n### 👉 Installer PHP\n\nÉvidemment nous avons besoin d'installer [PHP7.4](https://www.php.net).\n\n```bash\n$ sudo apt-get install --assume-yes php7.4\n```\n\nNous n'aurons pas besoin d'extension PHP particulière pour faire fonctionner notre projet.\n\n### 👉 Installer Composer\n\nComposer va nous permettre de gérer les dépendances de notre projet.\n\n```bash\n# download latest stable composer.phar\n$ php -r \"copy('https://getcomposer.org/composer-stable.phar', 'composer.phar');\"\n# install composer globally\n$ sudo mv composer.phar /usr/local/bin/composer\n# fix permissions\n$ sudo chmod uga+x /usr/local/bin/composer\n$ sync\n$ composer clear-cache\n```\n\n### 👉 En option : vim\n\n_vim_ est un éditeur de texte en ligne de commande, c'est [ma préférence à moi](https://www.youtube.com/watch?v=1hjPwWfvXh4) pour les `git rebase` interactifs (mais il n'y a vraiment pas d'obligation si vous préférez utiliser _nano_).\n\n```bash\n$ sudo apt-get install --assume-yes vim\n# set vim as git default editor if installed\n$ git config --global core.editor 'vim'\n```\n\n\u003e L'ASTUCE DU CHEF :\n\u003e\n\u003e Pour quitter vim il faut simplement entrer:\n\u003e\n\u003e \u003ckbd\u003e:\u003c/kbd\u003e\u003ckbd\u003eq\u003c/kbd\u003e\u003ckbd\u003e!\u003c/kbd\u003e\n\u003e\n\u003e Pour enregistrer un fichier et quitter:\n\u003e\n\u003e \u003ckbd\u003e:\u003c/kbd\u003e\u003ckbd\u003ew\u003c/kbd\u003e\u003ckbd\u003eq\u003c/kbd\u003e\u003ckbd\u003e!\u003c/kbd\u003e\n\n### 👉 Installer git\n\nGit est l'outil indispensable pour versionner notre code, pour l'installer entrez cette commande dans votre terminal :\n\n```bash\n$ sudo apt-get install --assume-yes git\n```\n\nEt pour la configuration de base :\n\n```bash\n# default git config\n$ git config --global push.default simple\n# set git to use the credential memory cache\n$ git config --global credential.helper cache\n# set the cache to timeout after 1 hour (setting is in seconds)\n$ git config --global credential.helper 'cache --timeout=3600'\n# set vim as git default editor if installed\n$ git config --global core.editor 'vim'\n# set your username and email\n$ git config --replace-all --global user.name \"Votre nom\"\n$ git config --replace-all --global user.email \"Votre email\"\n```\n\n### 👉 En option : github-cli\n\nIl n'est pas absolument indispensable, mais le client de [github](https://github.com) permet de se connecter à son compte et de créer des dépôts en ligne de commande.\n\n```bash\n$ wget https://github.com/cli/cli/releases/download/v1.7.0/gh_1.6.1_linux_amd64.tar.gz\n# extract archive\n$ tar xvzf gh_1.7.0_linux_amd64.tar.gz\n# install globally\n$ sudo mv ./gh_1.7.0_linux_amd64/bin/gh /usr/local/bin/gh\n# fix permissions\n$ sudo chmod uga+x /usr/local/bin/gh\n$ rm -rf gh_1.7.0_linux_amd64\n$ rm -f gh_1.7.0_linux_amd64.tar.gz\n$ sync\n```\n\n### 👉 Installer le meilleur IDE de l'univers\n\nL'excellent [PHPStorm](https://www.jetbrains.com/fr-fr/phpstorm) est pour moi vraiment le meilleur outil pour coder en PHP il n'y a pas photo.\n\n```bash\n$ sudo snap install phpstorm --classic\n```\n\n\u003e 📝 NOTE : Il est payant, mais JetBrains offre 30 jours d'essai gratuit, ensuite il faudra mettre la main à la poche ou vous contenter de [Sublime Text](https://www.sublimetext.com) qui n'est pas gratuit non plus, mais qui au lieu d'expirer va juste vous envoyer des notifications de temps en temps, (non, je ne vais pas aller jusqu'à vous recommander d'utiliser _vim_).\n\n---\n\nVoilà, c'est tous les outils dont nous aurons besoin pour ce projet. Passons à la suite.\n\n2 ⚡ Création du projet\n-----------------------\n\nPour initialiser notre projet nous avons juste besoin de quelques commandes.\n\n### 👉 Initialisation du dépôt git\n\nMaintenant, nous allons créer un nouveau dépôt ; Si vous êtes comme moi et que vous kiffez la ligne de commande, avec github-cli:\n\n```bash\n$ gh auth login\n$ gh repo create globalis\n```\nRépondez _Yes_ aux deux questions. 👍\n\n![./doc/images/gh_repo_create_globalis.png](./doc/images/gh_repo_create_globalis.png)\n\nSinon dans votre navigateur créez un nouveau dépôt [https://github.com/new](https://github.com/new), puis clonez simplement le dépôt que vous venez de créer.\n\n```bash\n$ git clone https://gihub.com/VotreNomDUtilisateur/globalis\n```\n\n### 👉 Initialisation du projet avec Composer\n\nTout d'abord il faut créer un nouveau projet avec composer, changez de dossier courant avec `cd globalis` et entrez la commande suivante :\n\n```bash\n$ composer init\n```\n\nRépondez aux questions de l'assistant :\n\n![./doc/images/composer_init.png](./doc/images/composer_init.png)\n\n3 ⚡ Installation des dépendances du projet\n-------------------------------------------\n\n### 👉 Installation de PHP\n\nNous allons utiliser php7.4\n\n```bash\n$ composer require php\n```\n\nNous allons modifier le critère de `composer.json` pour qu'il accepte toutes les versions de PHP supérieures à la 7.4.\n\n[./composer.json](./composer.json)\n```json\n    \"require\": {\n        \"php\": \"\u003e=7.4\"\n    },\n```\n\nC'est juste un détail mais ça va être utile pour la _pipeline_ d'intégration continue (à un moment donné sera obligé de passer à la version 8 de PHP, il faut bien s'y préparer). Nous allons utiliser [GitHub Actions](https://github.com/features/actions), nous verrons ça plus tard.\n\n### 👉 Installation de _PHPUnit_\n\nEt nous allons avoir besoin de _PHPUnit_ pour tester notre code.\n\nOui, oui, oui, nous allons faire du _\"TDD\"_, tout à fait.\n\n![https://media.giphy.com/media/efaobeEK8pKHgVU5Ys/giphy.gif](https://media.giphy.com/media/efaobeEK8pKHgVU5Ys/giphy.gif)\n\n```bash\n$ composer require --dev phpunit/phpunit\n```\n\nEt ajoutons la config qui va bien, dans le fichier `phpunit.xml.dist`:\n\n[./phpunit.xml.dist](./phpunit.xml.dist)\n```xml\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n\u003c!-- https://phpunit.readthedocs.io/en/latest/configuration.html --\u003e\n\u003cphpunit xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:noNamespaceSchemaLocation=\"vendor/phpunit/phpunit/phpunit.xsd\"\n         convertDeprecationsToExceptions=\"true\"\n         convertErrorsToExceptions=\"true\"\n         convertNoticesToExceptions=\"true\"\n         convertWarningsToExceptions=\"true\"\n         colors=\"true\"\n\u003e\n    \u003ctestsuites\u003e\n        \u003ctestsuite name=\"unit\"\u003e\n            \u003cdirectory\u003etests/Unit\u003c/directory\u003e\n        \u003c/testsuite\u003e\n    \u003c/testsuites\u003e\n\u003c/phpunit\u003e\n```\n\n### 👉 Installation de _PHP_Codesniffer_\n\nCe n'est pas indispensable mais nous allons aussi installer `php_codesniffer` pour _linter_ notre code:\n\n```bash\n$ composer require --dev squizlabs/php_codesniffer\n```\n\nAjoutez cette config dans le fichier `phpcs.xml.dist`:\n\n[./phpcs.xml.dist](./phpcs.xml.dist)\n```xml\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n\n\u003cruleset xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:noNamespaceSchemaLocation=\"vendor/squizlabs/php_codesniffer/phpcs.xsd\"\u003e\n\n    \u003c!-- Documentation: https://github.com/squizlabs/PHP_CodeSniffer --\u003e\n\n    \u003carg name=\"basepath\" value=\".\"/\u003e\n    \u003carg name=\"cache\" value=\".phpcs-cache\"/\u003e\n    \u003carg name=\"colors\"/\u003e\n    \u003carg name=\"extensions\" value=\"php\"/\u003e\n\n    \u003cfile\u003esrc/\u003c/file\u003e\n    \u003cfile\u003etests/\u003c/file\u003e\n\n    \u003crule ref=\"PSR12\" /\u003e\n\n    \u003crule ref=\"Generic.PHP.RequireStrictTypes.MissingDeclaration\" /\u003e\n    \u003crule ref=\"Generic.Arrays.DisallowLongArraySyntax\"/\u003e\n\n\u003c/ruleset\u003e\n```\n\n### 👉 .gitignore\n\nN'oublions pas de `.gitignore` les fichiers de cache de _phpunit_ et _phpcs_:\n\n```\n/vendor/\n.phpcs-cache\n.phpunit.result.cache\ncomposer.lock\n```\n\nOn va aussi ignorer `composer.lock` pour éviter des problèmes de compatibilités dans la _pipeline_ de _CI/CD_ (nous allons tester avec **PHP7 ET PHP8** du coup c'est mieux si le _builder_ de _GitHub_ ne se base pas sur le `.lock` pour installer notre script).\n\n### 👉 _Pipeline_ d'intégration continue\n\nL'intégration continue nous permet d'automatiser un certain nombre de tâches.\nPar exemple nous voulons vérifier que les tests unitaires passent et que le _linter_ ne retourne pas d'erreurs.\n\nPour mettre en place la configuration de _GitHub workflow_ créez les dossiers `.github/workflows`\n\n```bash\n$ mkdir -p .github/workflows\n```\n\nEt copiez-y le fichier `php.yaml` suivant :\n\n[./.github/workflows/php.yaml](./.github/workflows/php.yaml)\n```yaml\nname: PHP CI\non:\n  push:\n    branches: [ master ]\n  pull_request:\n    branches: [ master ]\n\njobs:\n  php:\n    name: PHP project (PHP ${{ matrix.php-versions }})\n\n    runs-on: ubuntu-latest\n    if: \"! contains(toJSON(github.event.commits.*.message), '[skip ci]')\"\n\n    strategy:\n      fail-fast: true\n      matrix:\n        php-versions: ['7.4', '8.0']\n\n    steps:\n      # Setup Github actions\n      # https://github.com/actions/checkout (official)\n      - name: Checkout\n        uses: actions/checkout@v2\n\n      # Setup PHP\n      # https://github.com/shivammathur/setup-php (community)\n      - name: Setup PHP, extensions and composer with shivammathur/setup-php\n        uses: shivammathur/setup-php@v2\n        with:\n          php-version: ${{ matrix.php-versions }}\n          tools: composer\n\n      - name: Install Project\n        run: make install\n\n      - name: Check for code quality errors\n        run: make lint\n\n      - name: Run Unit tests\n        run: make tests\n```\n\nCopiez également le fichier `Makefile` à la racine de votre projet : [https://raw.githubusercontent.com/TangoMan75/globalis/master/Makefile](https://raw.githubusercontent.com/TangoMan75/globalis/master/Makefile)\n\nIls contiennent les scripts à tonton qui permettent de gérer tout ça.\nJe ne vais pas m'étendre là dessus puisque ce n'est pas le sujet de ce tuto.\nSachez juste que le script affiche sa propre documentation en tapant cette commande dans votre terminal :\n\n```bash\n$ make\n```\n\n![./doc/images/make.png](./doc/images/make.png)\n\n### 👉 Vérifions que tout est en ordre\n\n```bash\n$ cat composer.json\n```\n\nSi tout s'est passé comme prévu, à cette étape le fichier `composer.json` (qui liste les dépendances installées dans le dossier `./vendor`) devrait contenir ceci :\n\n[./composer.json](./composer.json)\n```json\n{\n    \"name\": \"tangoman/globalis\",\n    \"description\": \"Test technique Globalis\",\n    \"type\": \"project\",\n    \"license\": \"MIT\",\n    \"authors\": [\n        {\n            \"name\": \"Matthias Morin\",\n            \"email\": \"mat@tangoman.io\"\n        }\n    ],\n    \"minimum-stability\": \"stable\",\n    \"require\": {\n        \"php\": \"\u003e=7.4\"\n    },\n    \"require-dev\": {\n        \"phpunit/phpunit\": \"^9.5\",\n        \"squizlabs/php_codesniffer\": \"^3.5\"\n    }\n}\n```\n\n### 👉 Faisons une petite sauvegarde de notre travail\n\n```bash\n$ git add .\n$ git commit -m \"Initial Commit\"\n$ git push\n```\n\n![https://media.giphy.com/media/101IgDtwWFQKti/giphy.gif](https://media.giphy.com/media/101IgDtwWFQKti/giphy.gif)\n\nÀ cette étape si vous avez suivi à la lettre toutes mes instructions vous devriez recevoir un mail de la part de _GitHub_ qui vous prévient que la pipeline de ci à échoué, c'est tout fait normal... Nous n'avons pas encore écrit la moindre ligne de code.\n\n4 ⚡ Commençons à coder\n-----------------------\n\n### 👉 Création de l'objet _Continent_\n\nNous allons mettre le script principal dans le dossier `./src/Continent`:\n\n```bash\n$ mkdir -p ./src/Continent\n```\n\nOuvrez le projet avec _PHPStorm_, puis faites un clic droit sur le dossier `Continent` et ensuite `new \u003e PHP Class`. Nous allons nommer cette classe \"Continent\", on ne va pas se casser la tête.\n\n![./doc/images/create_continent.png](./doc/images/create_continent.png)\n\n### 👉 Création des propriétés de l'objet _Continent_\n\nNous avons besoin de deux propriétés dans le fichier `Continent.php` que nous venons de créer:\n\n[./scr/Continent/Continent.php](./scr/Continent/Continent.php)\n```php\n    // ...\n\n    /**\n     * @var int Continent width\n     */\n    private int $width;\n\n    /**\n     * @var array Array representing terrain heights\n     */\n    private array $terrain;\n\n    // ...\n```\n\n📝 NOTE : Nous somme en PHP 7.4, n'oubliez pas de _typer_ les propriétés de l'objet.\n\nLa première propriété représente la largeur du continent (de type entier), la seconde représente le terrain (de type tableau. Oui, oui, vous avez bien vu nous voulons bien un `array`, pas une _chaîne de caractères_).\n\n### 👉 Création du constructeur\n\nMaintenant faites \u003ckbd\u003ealt\u003c/kbd\u003e+\u003ckbd\u003eins\u003c/kbd\u003e pour faire apparaître le menu _Generate..._ de _PHPStorm_.\n\nCliquez sur _Constructor..._, selectionnez les deux propriétés et cliquez sur _OK_.\n\n![./doc/images/create_constructor.png](./doc/images/create_constructor.png)\n\nEt voilà ! _PHPStorm_ a généré le _constructeur_ et a initialisé les _propriétés_ de l'objet automatiquement ! Super ! 🤟\n\nNous allons passer les paramètres pour créer le continent des _prolosaures_ directement dans le _constructeur_.\n\n\u003e C'est un continent ; à priori il ne va pas changer de taille tous les jours... Et le profil du terrain ne devrait pas changer non plus... En tout cas probablement pas pendant l'exécution de notre calcul.\n\nLe constructeur de notre classe par contre **doit accepter une _chaîne de caractères_ pour la variable \"terrain\"**, le plan c'est de transformer ensuite cette valeur en tableau.\n\n[./src/Continent/Continent.php](./src/Continent/Continent.php)\n```php\n    // ...\n\n    public function __construct(int $width, string $terrain)\n    {\n\n    // ...\n```\n\n### 👉 Création des _setters_\n\nFaites à nouveau \u003ckbd\u003ealt\u003c/kbd\u003e+\u003ckbd\u003eins\u003c/kbd\u003e, cette fois ci séléctionnez _Setters..._\n\n![./doc/images/create_setters.png](./doc/images/create_setters.png)\n\nNous allons les modifier de sorte qu'ils permettent de convertir et de valider les entrées que nous allons leur passer.\n\nCes méthodes ne **doivent pas** être accessibles depuis l'extérieur de la classe `Continent`, nous les passons en `private`.\n\nNous allons juste apporter une petite modification à la méthode `getTerrain` pour qu'elle accepte un paramètre de type `string`.\n\n[./src/Continent/Continent.php](./src/Continent/Continent.php)\n```php\n    // ...\n\n    /**\n     * This method will convert string to array and validate value.\n     *\n     * @param string $terrain\n     */\n    private function setTerrain(string $terrain): void\n    {\n\n    // ...\n```\n\nLaissons ça de côté pour l'instant, nous reviendrons dessus un peu plus tard.\n\nDans le constructeur maintenant nous allons utiliser les _setters_ que nous venons de créer au lieu de définir les valeurs directement.\n\n[./src/Continent/Continent.php](./src/Continent/Continent.php)\n```php\n    // ...\n\n    public function __construct(int $width, string $terrain)\n    {\n        $this-\u003esetWidth($width);\n        $this-\u003esetTerrain($terrain);\n    }\n\n    // ...\n```\n\n### 👉 Création de la méthode `getSafeArea`\n\nC'est la méthode qui va nous permettre de calculer l'aire de la zone dans laquelle les _prolosaures_ sont à l'abri du vent.\n\n[./src/Continent/Continent.php](./src/Continent/Continent.php)\n```php\n    // ...\n\n    /**\n     * This method computes the area where prolosaurs are safe.\n     *\n     * @return int\n     */\n    public function getSafeArea(): int\n    {\n        return 1;\n    }\n\n    // ...\n```\n\nPour le moment elle ne va retourner que le chiffre _1_.\n\n---\n\nBien ! Nous avons terminé avec cette première partie. Je vous ai promis du **TDD** alors à partir de maintenant écrivons nos tests.\n\n5 ⚡ Mise en place du TDD\n-------------------------\n\n### 👉 Création des tests unitaires\n\nComme les fois précédentes dans le menu _Generate..._ cette fois-ci sélectionnez _Test..._\n\nSélectionnez la méthode `getSafeArea` uniquement et cliquez sur _OK_.\n\n![./doc/images/create_test.png](./doc/images/create_test.png)\n\n🎉 Youpi ! _PHPStorm_ à créé notre fichier de test automatiquement ! 🎉\n\n[./tests/Continent/ContinentTest.php](./tests/Continent/ContinentTessmall)\n```php\n\u003c?php\n\nnamespace App\\Tests\\Continent;\n\nuse App\\Continent\\Continent;\nuse PHPUnit\\Framework\\TestCase;\n\nclass ContinentTest extends TestCase\n{\n\n    public function testGetSafeArea()\n    {\n\n    }\n}\n```\n\nBon, pour le moment il est vide, mais on va s'en occuper tout de suite !!!\n\n### 👉 Tester la validation des paramètres\n\nLa première chose à faire est de tester que les arguments que nous passons dans le constructeur sont correctement validés, c'est-à-dire qu'ils vont déclencher une erreur s'ils ne respectent pas un certain nombre de contraintes.\n\n- L'énoncé nous précise que la largeur du continent ne peut pas être plus petit que _1_ et ne peut pas être plus grand que _100000_;\n\n\u003e Ce sont des unités arbitraires, à l'époque j'imagine que les dinosaures mesuraient les distances en \"kilo-griffes\", ou en \"bras de T-Rex\" pour les distances plus courtes.\n\n![./doc/images/stoppable.jpg](./doc/images/stoppable.jpg)\n\n- Il est précisé également que le terrain ne peut contenir que des hauteurs entre _0_ et _100000_.\n\n- Et aussi, logiquement notre objet devrait faire péter une erreur si nous essayons de créer un continent qui ne peut pas contenir tous les éléments du terrain.\n\n### 👉 Tester `setWidth`\n\nNous voudrions voir apparaitre le message _'Width cannot be lower than 1 ; \"X\" given'_ quand nous passons un nombre inférieur à un pour la largeur du continent.\n\nEt le message _'Width cannot be greater than 100000 ; \"X\" given'_ quand nous passons un nombre supérieur à cent mille.\n\nÉcrivons les tests correspondants :\n\n[./tests/Continent/ContinentTest.php](./tests/Continent/ContinentTessmall)\n```php\n\n    // ...\n\n    public function testSmallWidthShouldRaiseInvalidArgumentException()\n    {\n        $this-\u003eexpectException(\\InvalidArgumentException::class);\n\n        $this-\u003eexpectExceptionMessage('Width cannot be lower than 1 ; \"0\" given');\n\n        new Continent(0, '1');\n    }\n\n    public function testLargeWidthShouldRaiseInvalidArgumentException()\n    {\n        $this-\u003eexpectException(\\InvalidArgumentException::class);\n\n        $this-\u003eexpectExceptionMessage('Width cannot be greater than 100000 ; \"100001\" given');\n\n        new Continent(100001, '1');\n    }\n\n    // ...\n```\n\nMaintenant, si vous lancez le test vous devriez voir une belle erreur :\n\n![./doc/images/test_fail.png](./doc/images/test_fail.png)\n\nParfait, c'est exactement ce que nous voulons.\n\n### 👉 Implémenter `setWidth`\n\nRevenons maintenant dans la méthode `setWidth` de l'objet `Continent`.\nLa consigne nous indique que la largeur du continent doit être au moins égale à un, et ne pas dépasser _100000_ au maximum.\n\nCréons donc deux constantes de classe comme ceci :\n\n[./src/Continent/Continent.php](./src/Continent/Continent.php)\n```php\n\n    // ...\n\n    public const DEFAULT_MIN_WIDTH = 1;\n    public const DEFAULT_MAX_WIDTH = 100000;\n\n    // ...\n```\n\nPuis dans la méthode `setWidth` nous allons générer une erreur quand l'_argument_ `$width` est inférieur à _1_ avec un joli message :\n\n[./src/Continent/Continent.php](./src/Continent/Continent.php)\n```php\n\n        // ...\n\n        if ($width \u003c 1) {\n            throw new \\InvalidArgumentException(\n                sprintf(\n                    'Width cannot be lower than %s ; \"%s\" given',\n                    self::DEFAULT_MIN_WIDTH,\n                    $width\n                )\n            );\n        }\n\n        // ...\n```\n\nLa même chose quand l'_argument_ `$width` est supérieur à _100000_ avec le message correspondant :\n\n[./src/Continent/Continent.php](./src/Continent/Continent.php)\n```php\n\n        // ...\n\n        if ($width \u003e 100000) {\n            throw new \\InvalidArgumentException(\n                sprintf(\n                    'Width cannot be greater than %s ; \"%s\" given',\n                    self::DEFAULT_MAX_WIDTH,\n                    $width\n                )\n            );\n        }\n\n        // ...\n```\n\nSi la variable est validée, la méthode va `setter` la valeur.\n\n[./src/Continent/Continent.php](./src/Continent/Continent.php)\n```php\n        // ...\n\n        $this-\u003ewidth = $width;\n    }\n\n    // ...\n```\n\nMaintenant essayons de voir si nos tests passent :\n\n```bash\n$ make tests\n```\n\n![./doc/images/test_success.png](./doc/images/test_success.png)\n\n🎉 YEAH ! ÇA MARCHE !! 🎉\n\n![!https://media.giphy.com/media/tGokBeqpHuSgE/giphy.gif](https://media.giphy.com/media/tGokBeqpHuSgE/giphy.gif)\n\n### 👉 Tester `setTerrain`\n\nNotre objet doit lever une exception si nous essayons de créer un continent qui ne peut pas contenir tous les éléments de notre terrain. Écrivons le test correspondant :\n\n[./tests/Continent/ContinentTest.php](./tests/Continent/ContinentTessmall)\n```php\n\n    // ...\n\n    public function testInvalidTerrainShouldRaiseInvalidArgumentException()\n    {\n        $this-\u003eexpectException(\\InvalidArgumentException::class);\n\n        $this-\u003eexpectExceptionMessage('Terrain cannot be wider that continent');\n\n        new Continent(1, '1 2');\n    }\n\n    // ...\n```\n\n### 👉 Implémenter `setTerrain`\n\nCommençons par créer deux constantes de classe comme précédemment, avec les valeurs correspondantes :\n\nLa hauteur du terrain ne peut pas être négative, et ne pas dépasser _100000_ au maximum.\n\n[./src/Continent/Continent.php](./src/Continent/Continent.php)\n```php\n\n    // ...\n\n    public const DEFAULT_MIN_HEIGHT = 0;\n    public const DEFAULT_MAX_HEIGHT = 100000;\n\n    // ...\n```\n\nN'oublions pas que notre méthode reçoit une _chaîne de caractères_ en entrée, nous devons d'abord la convertir en tableau avec la fonction `explode`. Elle va être découpée à chaque espace.\n\n[./src/Continent/Continent.php](./src/Continent/Continent.php)\n```php\n\n    // ...\n\n    private function setTerrain(string $terrain): void\n    {\n        // Split string to array\n        $terrain = explode(' ', $terrain);\n\n        if (\\count($terrain) \u003e $this-\u003ewidth) {\n            throw new \\InvalidArgumentException('Terrain cannot be wider that continent');\n        }\n\n        // ...\n```\n\nVous commencez à connaître la routine maintenant.\n\n![https://media.giphy.com/media/bNpLfNOskgvGIfKIZN/giphy.gif](https://media.giphy.com/media/bNpLfNOskgvGIfKIZN/giphy.gif)\n\nLancez les tests, ça devrait passer !\n\n```bash\n$ make tests\n```\n\n### 👉 Hauteur minimale et maximale dans `setTerrain` : Tests\n\nD'abord, que se passerait-il si notre `string` contenait des caractères alphabétiques ? Dans ce cas, on préfère que la méthode `setTerrain` nous retourne une erreur propre. Parce qu'on n'est pas des sauvages.\n\n[./tests/Continent/ContinentTest.php](./tests/Continent/ContinentTessmall)\n```php\n\n    // ...\n\n    public function testInvalidHeightShouldRaiseInvalidArgumentException()\n    {\n        $this-\u003eexpectException(\\InvalidArgumentException::class);\n\n        $this-\u003eexpectExceptionMessage('Invalid Height ; \"foobar\" given');\n\n        new Continent(1, 'foobar');\n    }\n\n    // ...\n```\n\nContinuons, la consigne dit que toutes les hauteurs du terrain doivent être supérieures à zéro, et ne pas dépasser _100000_ au maximum.\n\n[./tests/Continent/ContinentTest.php](./tests/Continent/ContinentTessmall)\n```php\n\n    // ...\n\n    public function testNegativeHeightShouldRaiseInvalidArgumentException()\n    {\n        $this-\u003eexpectException(\\InvalidArgumentException::class);\n\n        $this-\u003eexpectExceptionMessage('Height cannot be lower than 0 ; \"-1\" given');\n\n        new Continent(1, '-1');\n    }\n\n    public function testLargeHeightShouldRaiseInvalidArgumentException()\n    {\n        $this-\u003eexpectException(\\InvalidArgumentException::class);\n\n        $this-\u003eexpectExceptionMessage('Height cannot be greater than 100000 ; \"100001\" given');\n\n        new Continent(1, '100001');\n    }\n\n    // ...\n```\n\n### 👉 Hauteur minimale et maximale : Implémentation\n\nLa première chose à faire c'est utiliser la fonction `is_numeric` pour tester que notre `string` ne contient que des nombres.\n\n[./src/Continent/Continent.php](./src/Continent/Continent.php)\n```php\n\n        // ...\n\n        foreach ($terrain as $height) {\n            if (! is_numeric($height)) {\n                throw new \\InvalidArgumentException(\n                    sprintf(\n                        'Invalid Height ; \"%s\" given',\n                        $height\n                    )\n                );\n            }\n\n            // ...\n```\n\nEnsuite nous allons _checker_ que la variable est un nombre entre 1 et 100000.\n\n[./src/Continent/Continent.php](./src/Continent/Continent.php)\n```php\n            // ...\n\n            if ($height \u003c 1) {\n                throw new \\InvalidArgumentException(\n                    sprintf(\n                        'Height cannot be lower than %s ; \"%s\" given',\n                        self::DEFAULT_MIN_HEIGHT,\n                        $height\n                    )\n                );\n            }\n\n            if ($height \u003e 100000) {\n                throw new \\InvalidArgumentException(\n                    sprintf(\n                        'Height cannot be greater than %s ; \"%s\" given',\n                        self::DEFAULT_MAX_HEIGHT,\n                        $height\n                    )\n                );\n            }\n\n            // ...\n```\n\nQuand tout s'est bien passé la méthode va `setter` la valeur qui vient d'être validée.\n\n[./src/Continent/Continent.php](./src/Continent/Continent.php)\n```php\n        // ...\n\n        $this-\u003eterrain = $terrain;\n    }\n\n    // ...\n```\n\nLancez les tests :\n```bash\n$ make tests\n```\n\nÇa passe ! Pouêt ! 🎉\n\n6 ⚡ Solution\n-------------\n\n### 👉 Création du test de `getSafeArea`\n\nUtilisons l'exemple donné dans la consigne, l'algorithme devrait nous retourner le nombre _88_. (Non, non, non, je n'ai pas triché je l'ai bel et bien calculé de tête... Oui je sais, c'est impressionnant.)\n\n\u003e On reproche souvent aux développeurs PHP d'être arrogants, mais c'est franchement pas si facile de rester modeste quand on maîtrise les additions et les soustractions... Hé ouais...\n\n[./tests/Continent/ContinentTest.php](./tests/Continent/ContinentTessmall)\n```php\n\n    // ...\n\n    public function testGetSafeArea()\n    {\n        $continent = new Continent(10, '30 27 17 42 29 12 14 41 42 42');\n\n        $this-\u003eassertEquals(88, $continent-\u003egetSafeArea());\n    }\n}\n```\n\n### 👉 Implémentation de la logique de l'algorithme principal\n\n![./doc/images/infography.png](./doc/images/infography.png)\n\n1. On parcourt la liste des hauteurs du terrain une par une.\n2. Quand le continent est plus grand que le terrain, on considère que la hauteur est égale à zéro pour les valeurs manquantes.\n3. On enregistre dans une variable la hauteur la plus élevée que nous avons rencontrée jusqu'à présent.\n4. Si la hauteur de terrain courante est inférieure à la hauteur la plus haute rencontrée précédemment, on ajoute au résultat la différence entre la hauteur la plus élevée et la hauteur courante.\n\n[./src/Continent/Continent.php](./src/Continent/Continent.php)\n```php\n\n    // ...\n\n    /**\n     * This method computes the area where prolosaurs are safe.\n     *\n     * @return int\n     */\n    public function getSafeArea(): int\n    {\n        $area = 0;\n        $highest = 0;\n\n        // [1] Iterate through all continent\n        for ($i = 0; $i \u003c $this-\u003ewidth; ++$i) {\n            // [2] Handle case where list shorter than continent\n            $height = $this-\u003eterrain[$i] ?? 0;\n\n            if ($height \u003e $highest) {\n                // [3] Prolosaurs on this terrain are vulnerable\n                $highest = $height;\n            } else {\n                // [4] Prolosaurs on this terrain are protected from wind\n                $area += $highest - $height;\n            }\n        }\n\n        return $area;\n    }\n}\n```\n\n### 👉 Testons\n\n![./doc/images/tests.png](./doc/images/tests.png)\n\n🎉 **C'EST UN SUCCÈS !** 🎉\n\n![https://media.giphy.com/media/yoJC2GnSClbPOkV0eA/giphy.gif](https://media.giphy.com/media/yoJC2GnSClbPOkV0eA/giphy.gif)\n\nAlors évidemment, vous allez me dire qu'on a consommé plus que 2000 kilo-octets de ram.\nMais c'est _PHPUnit_ qui utilise cette mémoire pour l'essentiel, l'algorithme lui-même consomme très peu en fait.\nOn parcourt le tableau des hauteurs du terrain une seule fois, la complexité est donc linéaire.\n\nSi vous êtes familier avec la notation _Big O_ nous sommes en `O(n)`, donc je crois que nous pouvons dire que le challenge est réussi. 😉\n\n⚡ Conclusion\n-------------\n\nEt voilà, cette victoire sonne la fin de cette petite aventure !\n\nEt si vous avez suivi jusque-là, **félicitations à vous aussi les amis !**\n\n![https://media.giphy.com/media/KffdTQfewxdbKTGEJY/giphy.gif](https://media.giphy.com/media/KffdTQfewxdbKTGEJY/giphy.gif)\n\nJ'espère que vous avez pris goût à la POO, le TDD, Linux, et PHP !\n\nPour me contacter :\n\n[![mat@tangoman.io](https://img.shields.io/badge/mat@tangoman.io-%23EA4335.svg?\u0026style=flat-square\u0026logo=mail.ru\u0026logoColor=white)](mailto:mat@tangoman.io)\n[![TangoMan.io](https://img.shields.io/badge/TangoMan.io-%23337AB7.svg?\u0026style=flat-square\u0026logo=google-chrome\u0026logoColor=white)](https://tangoman.io)\n[![GitHub](https://img.shields.io/badge/GitHub-%23181717.svg?\u0026style=flat-square\u0026logo=github\u0026logoColor=white)](https://github.com/TangoMan75)\n[![LinkedIn](https://img.shields.io/badge/LinkedIn-%230077B5.svg?\u0026style=flat-square\u0026logo=linkedin\u0026logoColor=white)](https://linkedin.com/in/matthiasmorin)\n[![Twitter](https://img.shields.io/badge/Twitter-%231DA1F2.svg?\u0026style=flat-square\u0026logo=twitter\u0026logoColor=white)](https://twitter.com/tangomanparis)\n\n🤝 Contribuer\n-------------\n\nMerci pour votre intérêt à apporter votre contribution à **Globalis**.\n\nVeuillez consulter le [code de conduite](./CODE_OF_CONDUCT.md) et les [directives de contribution](./CONTRIBUTING.md) avant de commencer à travailler sur des fonctionnalités.\n\nSi vous souhaitez ouvrir un rapport de bug, veuillez d'abord vérifier qu'il n'a pas [déjà été signalé](https://github.com/TangoMan75/globalis/issues) avant d'en créer un nouveau.\n\n📜 License\n----------\n\nCopyrights (c) 2021 \u0026quot;Matthias Morin\u0026quot; \u0026lt;mat@tangoman.io\u0026gt;\n\n[![License](https://img.shields.io/badge/Licence-MIT-green.svg)](LICENSE)\nDistribué sous la licence MIT.\n\nSi vous aimez **Globalis**, mettez une étoile, suivez-moi ou publiez un tweet.\n\n[![GitHub stars](https://img.shields.io/github/stars/TangoMan75/globalis?style=social)](https://github.com/TangoMan75/globalis/stargazers)\n[![GitHub followers](https://img.shields.io/github/followers/TangoMan75?style=social)](https://github.com/TangoMan75)\n[![Twitter](https://img.shields.io/twitter/url?style=social\u0026url=https%3A%2F%2Fgithub.com%2FTangoMan75%2Fglobalis)](https://twitter.com/intent/tweet?text=Wow:\u0026url=https%3A%2F%2Fgithub.com%2FTangoMan75%2Fglobalis)\n\n... Et jetez un oeil à mes autres projets.\n\n🙏 Remerciements\n----------------\n\nCe script a été généré avec [makefile-generator](https://github.com/TangoMan75/makefile-generator)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftangoman75%2Fglobalis","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftangoman75%2Fglobalis","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftangoman75%2Fglobalis/lists"}