Hiérarchie des exceptions

Hiérarchie des exceptions

Petit script rapide que je viens de trouver au hasard de mes recherches et qui permet de lister hiérarchiquement les exceptions :

<?php
if (!function_exists('interface_exists')) {
    die('PHP version too old');
}
$throwables = listThrowableClasses();
$throwablesPerParent = splitInParents($throwables);
printTree($throwablesPerParent);
if (count($throwablesPerParent) !== 0) {
    die('ERROR!!!');
}
function listThrowableClasses()
{
    $result = [];
    if (interface_exists('Throwable')) {
        foreach (get_declared_classes() as $cn) {
            $implements = class_implements($cn);
            if (isset($implements['Throwable'])) {
                $result[] = $cn;
            }
        }
    } else {
        foreach (get_declared_classes() as $cn) {
            if ($cn === 'Exception' || is_subclass_of($cn, 'Exception')) {
                $result[] = $cn;
            }
        }
    }

    return $result;
}

function splitInParents($classes)
{
    $result = [];
    foreach ($classes as $cn) {
        $parent = (string)get_parent_class($cn);
        if (isset($result[$parent])) {
            $result[$parent][] = $cn;
        } else {
            $result[$parent] = [$cn];
        }
    }

    return $result;
}

function printTree(&$tree)
{
    if (!isset($tree[''])) {
        die('No root classes!!!');
    }
    printLeaves($tree, '', 0);
}

function printLeaves(&$tree, $parent, $level)
{
    if (isset($tree[$parent])) {
        $leaves = $tree[$parent];
        unset($tree[$parent]);
        natcasesort($leaves);
        $leaves = array_values($leaves);
        $count = count($leaves);
        for ($i = 0; $i < $count; ++$i) {
            $leaf = $leaves[$i];
            echo str_repeat('   ', $level), $leaf, "\n";
            printLeaves($tree, $leaf, $level + 1);
        }
    }
}

Références externes

Afficher les caractères depuis leur code ASCII

Utiliser la table ASCII

Exemple avec la lettre q. La table ascii donne :

  • Code décimal : 113
  • Code hexadécimal : 71
  • Code octal : 161

Les 3 lignes suivantes sont équivalentes, elles affichent toute la lettre q :

// Output : q
echo "\161";
echo "\x71";
echo chr(113);

Trouver une valeur ASCII à partir d’un caractère

Toujours avec la lettre q.

// Output : 113 (valeur décimale)
echo ord('q');
echo 0161;

// Output : 71 (valeur hexadecimale)
echo dechex(ord('q'));

// Output : 161 (valeur octale)
echo decoct(ord('q'));

Références externes

DateTime et jours ouvrés

Overview

La classe DateTime possède une méthode pour ajouter facilement une période de temps (ici on ajoute 10 jours):

$date = new DateTime('now');
$date->add(new DateInterval('P10D'));

Mais il n’est pas possible d’ajouter des jours ouvrés, ce qui est parfois bien utile. Une solution simple est d’étendre la classe DateTime avec deux méthodes :

  • addBusinessDays qui prend en seul paramètre : le nombre de jours ouvrés à ajouter à la date
  • addBusinessDay, méthode sans paramètre qui va ajouter 1 jour ouvrés en vérifiant avant si la date courante est vendredi ou samedi

Il est importer d’utiliser un espace de nom si le nom de la classe est DateTime :

namespace LDDW;

class DateTime extends \DateTime
{
    private $periods;

    public function __construct(string $time = 'now', \DateTimeZone $timezone = null)
    {
        parent::__construct($time, $timezone);

        // Put periods in a var cache
        $this->periods = [
            1 => new \DateInterval('P1D'),
            2 => new \DateInterval('P2D'),
            3 => new \DateInterval('P3D'),
        ];
    }

    public function addBusinessDays(int $nb)
    {
        if($nb > 0) {
            for($a = 0; $a < $nb; $a++) {
                $this->addBusinessDay();
            }
        }

        return $this;
    }

    private function addBusinessDay()
    {
        if($this->isFriday()) {
            $this->add($this->periods[3]);
        } elseif($this->isSaturday()) {
            $this->add($this->periods[2]);
        } else {
            $this->add($this->periods[1]);
        }
    }

    private function isFriday(): bool
    {
        return $this->format('N') == 5;
    }

    private function isSaturday(): bool
    {
        return $this->format('N') == 6;
    }
}

Utilisation

La classe s’utilise comme DateTime, à savoir :

use LDDW\DateTime;

$date = new DateTime('now');
$date->addBusinessDays(10);

Bien s’assurer d’utiliser le bon espace de nom avec use LDDW\DateTime;

Partons en vacances

Supposons que certains jours ouvrés sont en réalité des jours de vacances. Dans ce cas, si après avoir ajouté x jours ouvrés on tombe sur un jour de vacance, on doit renvoyer la prochaine date considérée comme ouvrée. Ajoutons donc quelques méthodes à notre classe :

  • une propriété $holidays qui va contenir les jours considérés comme des vacances
  • les getters/setters de la propriété $holidays
  • une méthode addHoliday() qui va ajouter un jour de vacance
  • une méthode isSunday() qui va nous dire si la date est un dimanche. Cette méthode n’est pas fondamentalement obligatoire mais je vais m’en servir dans la suivante
  • une méthode isWeekend() qui va nous dire si la date est un des 2 jours de week-end
  • une méthode isBusinnessDay() qui va nous dire si je jour est ouvré : si on est pas en week-end ni en vacances, on a donc un jour ouvré..
class DateTime extends \DateTime
{
    /** @var array */
    private $holidays;

    // ...

    /**
     * @return array
     */
    public function getHolidays(): array
    {
        return $this->holidays;
    }

    /**
     * @param array $holidays
     * @return $this
     */
    public function setHolidays(array $holidays)
    {
        $this->holidays = array_fill_keys($holidays, true);

        return $this;
    }

    /**
     * @param $day dd-mm-yyyy
     * Ex: $this->addHoliday('06-12-2018');
     * @return $this
     */
    public function addHoliday($day)
    {
        if(preg_match('/^[0-9]{2}-[0-9]{2}-20[0-9]{2}$/', $day)) {
            $this->holidays[$day] = true;
        }

        return $this;
    }

    /**
     * @return bool
     */
    private function isBusinnessDay(): bool
    {
        $key = $this->format('d-m-Y');

        return !$this->isWeekend() && empty($this->holidays[$key]);
    }

    public function isSunday(): bool
    {
        return $this->format('N') == 7;
    }

    public function isWeekend(): bool
    {
        return ($this->isSunday() || $this->isSaturday());
    }
}

Reste plus qu’à modifier la méthode addBusinessDays() qui va maintenant vérifier que le résultat est bien un jour ouvré :

public function addBusinessDays(int $nb)
{
    // ...

    // Check for holidays
    while(!$this->isBusinnessDay()) {
        $this->addBusinessDay();
    }

    return $this;
}

Petit rappel : un jour est ouvré si on est ni en vacances ni en week-end.

Et voilà comment utiliser la classe :

use LDDW\DateTime;

$date = new DateTime('now');
$date->setHolidays(array(
    '22-12-2018',
    '23-12-2018'
));
$date->addBusinessDays(10);

Amélioration

Cette classe a un défaut majeur : elle ne prend en compte que les week-ends, passant sous silence les jours fériés et autres joyeusetés du calendrier. Une belle amélioration serait de les intégrer, au moins en Français et Anglais.

La classe complète

Voilà la classe au grand complet, merci de m’avoir lu jusqu’ici !

namespace LDDW;

class DateTime extends \DateTime
{
    private $periods;
    private $holidays;

    /**
     * @return array
     */
    public function getPeriods(): array
    {
        return $this->periods;
    }

    /**
     * @param array $periods
     */
    public function setPeriods(array $periods): void
    {
        $this->periods = $periods;
    }

    public function __construct(string $time = 'now', \DateTimeZone $timezone = null)
    {
        parent::__construct($time, $timezone);

        // Put periods in a var cache
        $this->periods = [
            1 => new \DateInterval('P1D'),
            2 => new \DateInterval('P2D'),
            3 => new \DateInterval('P3D'),
        ];
    }

    public function addBusinessDays(int $nb)
    {
        if($nb > 0) {
            for($a = 0; $a < $nb; $a++) {
                $this->addBusinessDay();
            }
        }

        return $this;
    }

    private function addBusinessDay()
    {
        if($this->isFriday()) {
            $this->add($this->periods[3]);
        } elseif($this->isSaturday()) {
            $this->add($this->periods[2]);
        } else {
            $this->add($this->periods[1]);
        }

        // Check for holidays
        while(!$this->isBusinnessDay()) {
            $this->addBusinessDay();
        }
    }

    public function getHolidays(): array
    {
        return $this->holidays;
    }

    /**
     * @param array $holidays
     * @return $this
     */
    public function setHolidays(array $holidays)
    {
        $this->holidays = array_fill_keys($holidays, true);

        return $this;
    }

    /**
     * @param $day dd-mm-yyyy
     * Ex: $this->addHoliday('06-12-2018');
     * @return $this
     */
    public function addHoliday($day)
    {
        if(preg_match('/^[0-9]{2}-[0-9]{2}-20[0-9]{2}$/', $day)) {
            $this->holidays[$day] = true;
        }

        return $this;
    }

    /**
     * @return bool
     */
    private function isBusinnessDay(): bool
    {
        $key = $this->format('d-m-Y');

        return !$this->isWeekend() && empty($this->holidays[$key]);
    }

    private function isFriday(): bool
    {
        return $this->format('N') == 5;
    }

    private function isSaturday(): bool
    {
        return $this->format('N') == 6;
    }

    public function isSunday(): bool
    {
        return $this->format('N') == 7;
    }

    public function isWeekend(): bool
    {
        return ($this->isSunday() || $this->isSaturday());
    }
}

Références externes

Hiérarchie des exceptions

Options et comparaison bit à bit

Qui ne s’est jamais demandé : à quoi peuvent bien servir les opérateurs sur les bits ? Voici un exemple : ils peuvent permettre de gérer facilement des options dans vos classes. Imaginons une classe personne qui représente une… personne :

class Person
{
    private $options;
    private $name;

    /**
     * Person constructor.
     * @param $options
     */
    public function __construct($name, $options = 0)
    {
        $this->options = $options;
        $this->name = $name;
    }

    /**
     * @return mixed
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * @return mixed
     */
    public function getOptions($binary = null)
    {
        return $binary ? sprintf("%'08b", $this->options) : $this->options;
    }
}

$nicolas = new Person('Nicolas');

Maintenant, ajoutons des options :

  • la personne peut être grande
  • la personne peut être un homme ou une femme
  • la personne peut être chauve
  • la personne peut être drôle

On utilise 4 constantes, des puissances de deux (c’est important) :

class Person
{
    const TALL = 1; // 00000001
    const FUNNY = 2; // 00000010
    const BALD = 4; // 00000100
    const MALE = 16; // 00001000
    
    //--
}

Créons un homme grand et drôle :

$nicolas = new Person('Nicolas', Person::TALL | Person::FUNNY);
echo $nicolas->getOptions(true); // 00000011

Améliorons la classe pour modifier les options après la création de l’objet (on se s’occupe que de l’option FUNNY) :

class Person
{
    //--

    /**
     * @return int
     */
    public function isFunny()
    {
        return $this->options & self::FUNNY;
    }

    public function setFunny()
    {
        $this->options |= self::FUNNY;
    }

    public function setNotFunny()
    {
        $this->options &= ~self::FUNNY;
    }

    public function toggleFunny()
    {
        $this->options ^= self::FUNNY;
    }
}

Et maintenant jouons avec nos nouvelles méthodes : on active et désactive l’option FUNNY tout en vérifiant le résultat :

$nicolas = new Person('Nicolas', Person::TALL);
$nicolas->setFunny();

// Output : Nicolas is funny
if($nicolas->isFunny()) {
    echo "{$nicolas->getName()} is funny\n";
} else {
    echo "{$nicolas->getName()} is boring\n";
}

$nicolas->setNotFunny();
// Output : Nicolas is boring
if($nicolas->isFunny()) {
    echo "{$nicolas->getName()} is funny\n";
} else {
    echo "{$nicolas->getName()} is boring\n";
}

$nicolas->toggleFunny();
// Output : Nicolas is funny
if($nicolas->isFunny()) {
    echo "{$nicolas->getName()} is funny\n";
} else {
    echo "{$nicolas->getName()} is boring\n";
}

$nicolas->toggleFunny();
// Output : Nicolas is boring
if($nicolas->isFunny()) {
    echo "{$nicolas->getName()} is funny\n";
} else {
    echo "{$nicolas->getName()} is boring\n";
}

Explication

Récapitulons un peu ce que nous devons savoir :

  • pour activer une option, on utilise l’opérande binaire OR |
  • pour désactiver une option, on utilise l’opérande binaire AND & en complément avec le NON binaire ~
  • pour inverser une option, on utilise l’opérande binaire XOR ^

Il est facile d’étendre cette astuce aux autres options, et même d’écrire une méthode setXXXX plus générique pour éviter la duplication de code à travers les options. Mais là n’est pas le sujet.

Références externes