T: / Corrigés des challenges / PHP
Utilisation d’un try/catch pour déceler l’arrêt du programme, avec une exception personnalisée.
Comme le challenge SURVIVAL_2 présente une condition d’arrêt (cf. dans l’énoncé « L’exploration s’arrête ») qui peut survenir presque à n’importe quel moment de l’exécution du programme, je vais utiliser un try/catch avec une exception personnalisée. Avant cela, je vais structurer mon code dans une classe en et tester mon code avec Pest PHP.
Voici les différentes étapes :
Pour faciliter la lecture de l’article, le code complet peut être consulté sur Github.
Je crée une classe Survivor, et je définis des constantes pour les informations spécifiques de l’énoncé, à savoir les lettres qui représentent les différentes zones :
<?php
declare(strict_types=1);
namespace Challenges\SURVIVAL_2;
class Survivor
{
public const ZONE_WATER = 'W';
public const ZONE_FOOD = 'F';
public const ZONE_DIFFICULT_FIELD = '_';
public function __construct(
public int $thirst,
public int $hunger,
public int $shape
) {}
}
J’utilise la promotion de propriétés dans le constructeur pour les 3 informations importantes du challenge : la soif, la faim et la forme de mon survivant.
Pour chacune des zones, je crée une méthode dédiée :
public function zoneWater(): void
{
$this->thirst++;
$this->shape--;
}
public function zoneFood(): void
{
$this->hunger++;
$this->shape--;
}
public function zoneDifficultField(): void
{
$this->shape -= 3;
}
public function zoneSimple(): void
{
$this->shape--;
}
Chacune de ces méthodes aura donc son test associé, réalisé ici avec Pest PHP, dans un fichier SurvivorTest.php :
test('Zone Water', function() {
$survivor = new Survivor(
thirst: 10,
hunger: 10,
shape: 100
);
$survivor->zoneWater();
$this->assertEquals(11, $survivor->thirst);
$this->assertEquals(99, $survivor->shape);
});
test('Zone Food', function() {
$survivor = new Survivor(
thirst: 10,
hunger: 10,
shape: 100
);
$survivor->zoneFood();
$this->assertEquals(11, $survivor->hunger);
$this->assertEquals(99, $survivor->shape);
});
test('Zone Terrain Difficile', function() {
$survivor = new Survivor(
thirst: 10,
hunger: 10,
shape: 100
);
$survivor->zoneDifficultField();
$this->assertEquals(97, $survivor->shape);
});
test('Zone simple', function() {
$survivor = new Survivor(
thirst: 10,
hunger: 10,
shape: 100
);
$survivor->zoneSimple();
$this->assertEquals(99, $survivor->shape);
});
Pour chaque test, on crée un survivant puis on applique une des méthodes. On s’assure alors que la ou les propriétés ont bien évoluées comme souhaité.
Le challenge doit s’arrêter si une des 3 propriétés tombe à zéro (ou moins). J’écris une méthode qui vérifie si on doit s’arrêter ou non :
public function mustStop(): bool
{
return ($this->thirst <= 0 || $this->hunger <= 0 || $this->shape <= 0);
}
Pour retourner false, il faut que toutes les valeurs soient supérieures à zéro. Je peux écrire 1 seul test pour m’assurer de ce fonctionnement :
test('Peut continuer', function() {
$survivor = new Survivor(
thirst: 10,
hunger: 10,
shape: 100
);
$this->assertFalse($survivor->mustStop());
});
Par contre, il y a plusieurs combinaisons de valeurs possibles qui vont retourner true. Je vais utiliser un dataprovider avec la méthode « with » :
test('Doit s\'arrêter', function(int $thirst, int $hunger, int $shape) {
$survivor = new Survivor(
thirst: $thirst,
hunger: $hunger,
shape: $shape
);
$this->assertTrue($survivor->mustStop());
})->with([
'Soif à zéro' => [0, 10, 100],
'Faim à zéro' => [10, 0, 100],
'Forme à zéro' => [10, 10, 0],
]);
Je crée d’abord une méthode qui permet de passer en revue une zone. A l’aide d’un switch, je vais me rediriger dans telle ou telle méthode :
public function zone(string $zone): void
{
switch ($zone) {
case self::ZONE_WATER:
$this->zoneWater();
break;
case self::ZONE_FOOD:
$this->zoneFood();
break;
case self::ZONE_DIFFICULT_FIELD:
$this->zoneDifficultField();
break;
default:
$this->zoneSimple();
break;
}
}
Et la méthode region qui permet de parcourir toute une région, c’est à dire une chaine de caractères qui comporte plusieurs zones à explorer :
public function region(string $region): void
{
foreach (str_split($region) as $zone) {
$this->zone($zone);
if ($this->mustStop()) {
throw new EndOfAdventureException;
}
}
}
Quelques explications :
Voici comment je crée cette exception personnalisée :
<?php
declare(strict_types=1);
namespace Challenges\SURVIVAL_2;
class EndOfAdventureException extends \Exception
{
public $message = 'End of Adventure';
}
Créer ses propres exceptions permet de clarifier la lecture du code et d’organiser correctement la gestion des erreurs dans son programme, dans son application.
Une exception peut être testée avec la méthode « expectException ». Il faut d’abord écrire la ligne avec « exceptException » pour indiquer qu’on « s’attend à avoir une exception », puis la ligne qui la déclenche :
test('Région difficile', function() {
$survivor = new Survivor(
thirst: 10,
hunger: 10,
shape: 10
);
$this->expectException(EndOfAdventureException::class);
$survivor->region('_________'); // équivalent de -30
});
Voici la méthode night() qui réalise la mise à jour des propriétés durant la nuit :
public function night(): void
{
$this->shape += (int) floor(($this->thirst + $this->hunger) / 2);
$this->thirst -= 5;
$this->hunger -= 5;
}
Et je crée une méthode adventure qui prend en paramètre une ile, c’est à dire un ensemble de régions (donc un tableau de chaines de caractères) :
/**
* @param string[] $island
*/
public function adventure(array $island): void
{
foreach ($island as $region) {
try {
$this->region($region);
} catch (EndOfAdventureException $e) {
echo $e->message;
return;
}
$this->night();
if ($this->mustStop()) {
return;
}
}
}
Quelques explications :
J’ai décrit dans cet articles les éléments intéressants du corrigé. Tout le reste du code peut être trouvé sur Github avec notamment :
J’ai utilisé Pest PHP dans ce corrigé. Si tu le souhaites tu peux retrouver notre introduction à Pest PHP.
Other content to discover