Êtes-vous sûr d'écrire du code orienté objet?

Nous, les développeurs PHP, sommes fiers d'écrire en langage POO (vous pouvez facilement remplacer PHP ici par C #, Java ou un autre langage POO). Chaque poste vacant contient des exigences pour la connaissance de la POO. Dans chaque interview, ils posent des questions sur SOLID ou les trois baleines de la POO. Mais en fin de compte, nous avons juste des classes remplies de procédures. La POO est rare, généralement dans le code de la bibliothèque.



Une application Web typique est constituée de classes d'entités ORM qui contiennent des données d'une ligne dans une base de données et des contrôleurs (ou services - peu importe) contenant des procédures pour travailler avec ces données. La programmation orientée objet concerne les objets qui possèdent leurs propres données et ne les fournissent pas pour un traitement par un autre code. Une bonne illustration de ceci est une question qui a été posée dans un chat: "Comment puis-je améliorer ce code?"



private function getWorkingTimeIntervals(CarbonPeriod $businessDaysPeriod, array $timeRanges): array
{
    $workingTimeIntervals = [];
    foreach ($businessDaysPeriod as $date) {
        foreach ($timeRanges as $time) {
            $workingTimeIntervals[] = [
                'start' => Carbon::create($date->format('Y-m-d') . ' ' . $time['start']),
                'end' => Carbon::create($date->format('Y-m-d') . ' ' . $time['end'])
            ];
        }
    }

    return $workingTimeIntervals;
}

/**
 *    
 *
 * @param array $workingTimeIntervals
 * @param array $events
 * @return array
 */
private function removeEventsFromWorkingTime(array $workingTimeIntervals, array $events): array
{
    foreach ($workingTimeIntervals as $n => &$interval) {
        foreach ($events as $event) {
            $period = CarbonPeriod::create($interval['start'], $interval['end']);
            if ($period->overlaps($event['start_date'], $event['end_date'])) {
                if ($interval['start'] <= $event['start_date'] && $interval['end'] <= $event['end_date']) {
                    $interval['end'] = $event['start_date'];
                } elseif ($interval['start'] >= $event['start_date'] && $interval['end'] >= $event['end_date']) {
                    $interval['start'] = $event['end_date'];
                } elseif ($interval['start'] <= $event['start_date'] && $interval['end'] >= $event['end_date']) {
                    $interval['start'] = $event['start_date'];
                    $interval['end'] = $event['end_date'];
                } else {
                    unset($workingTimeIntervals[$n]);
                }
            }
        }
    }

    return $workingTimeIntervals;
}


. () (), . , . — ( ) , . , , . , , .



unit- . . ( ). .



class Interval
{
    //    PHP 7.4
    public DateTimeImmutable $start;
    public DateTimeImmutable $end;
}


DateTimeImmutable .



— .

, , , — .



unit-. . :



class Interval
{
    public DateTimeImmutable $start;
    public DateTimeImmutable $end;

    public function __construct(DateTimeImmutable $start, DateTimeImmutable $end)
    {
        $this->start = $start;
        $this->end = $end;
    }
}


PHPUnit- :



use App\Interval;
use PHPUnit\Framework\TestCase;

class IntervalTest extends TestCase
{
    private DateTimeImmutable $today;
    private DateTimeImmutable $yesterday;
    private DateTimeImmutable $tomorrow;

    protected function setUp(): void
    {
        $this->today = new DateTimeImmutable();
        $this->yesterday = $this->today->add(\DateInterval::createFromDateString("-1 day"));
        $this->tomorrow = $this->today->add(\DateInterval::createFromDateString("1 day"));

        parent::setUp();
    }

    public function testValidDates()
    {
        $interval = new Interval($this->yesterday, $this->today);

        $this->assertEquals($this->yesterday, $interval->start);
        $this->assertEquals($this->today, $interval->end);
    }

    public function testInvalidDates()
    {
        $this->expectException(\InvalidArgumentException::class);

        new Interval($this->today, $this->yesterday);
    }
}


, . , testValidDates, . , testInvalidDates, . , :



Failed asserting that exception of type "InvalidArgumentException" is thrown.


:



class Interval
{
    public DateTimeImmutable $start;
    public DateTimeImmutable $end;

    public function __construct(DateTimeImmutable $start, DateTimeImmutable $end)
    {
        if ($start > $end) {
            throw new \InvalidArgumentException("Invalid date interval");
        }

        $this->start = $start;
        $this->end = $end;
    }
}


. PHP, null . . , , . Interval . ? unit- . , . . , , isEmpty .



class Interval
{
    public DateTimeImmutable $start;
    public DateTimeImmutable $end;

    public function __construct(DateTimeImmutable $start, DateTimeImmutable $end)
    {
        if ($start > $end) {
            throw new \InvalidArgumentException("Invalid date interval");
        }

        $this->start = $start;
        $this->end = $end;
    }

    public function isEmpty(): bool
    {
        return $this->start->getTimestamp() == $this->end->getTimestamp();
    }
}

class IntervalTest extends TestCase
{
    //...

    public function testNonEmpty()
    {
        $interval = new Interval($this->yesterday, $this->today);

        $this->assertFalse($interval->isEmpty());
    }

    public function testEmpty()
    {
        $interval = new Interval($this->today, $this->today);

        $this->assertTrue($interval->isEmpty());
    }
}


. ['start'=>,'end'=>], . ! , . , :



-  08:00 - 12:00
-  13:00 - 17:00
  08:00 - 12:00
  13:00 - 17:00
...


, :



 :
-  08:00 - 09:00
-  16:00 - 17:00
  13:00 - 17:00

:
-  09:00 - 12:00
-  13:00 - 16:00
  08:00 - 12:00
...


Interval:



$period = CarbonPeriod::create($interval['start'], $interval['end']);
if ($period->overlaps($event['start_date'], $event['end_date'])) {
    if ($interval['start'] <= $event['start_date'] && $interval['end'] <= $event['end_date']) {
        $interval['end'] = $event['start_date'];
    } elseif ($interval['start'] >= $event['start_date'] && $interval['end'] >= $event['end_date']) {
        $interval['start'] = $event['end_date'];
    } elseif ($interval['start'] <= $event['start_date'] && $interval['end'] >= $event['end_date']) {
        $interval['start'] = $event['start_date'];
        $interval['end'] = $event['end_date'];
    } else {
        unset($workingTimeIntervals[$n]);
    }
}


Interval: remove(Interval $other) , . :



private function removeEventsFromWorkingTime($workingTimeIntervals, $events): array
{
    foreach ($workingTimeIntervals as $n => $interval) {
        foreach ($events as $event) {
            $interval->remove($event);

            if ($interval->isEmpty()) {
                unset($workingTimeIntervals[$n]);
            }
        }
    }

    return $workingTimeIntervals;
}


. . , , ! , . . , .



, $other .





class IntervalRemoveTest extends TestCase
{
    private DateTimeImmutable $minus10Days;
    private DateTimeImmutable $today;
    private DateTimeImmutable $yesterday;
    private DateTimeImmutable $tomorrow;
    private DateTimeImmutable $plus10Days;

    protected function setUp(): void
    {
        $this->today = new DateTimeImmutable();
        $this->yesterday = $this->today->sub(\DateInterval::createFromDateString("1 day"));
        $this->tomorrow = $this->today->add(\DateInterval::createFromDateString("1 day"));

        $this->minus10Days = $this->today->sub(\DateInterval::createFromDateString("10 day"));
        $this->plus10Days = $this->today->add(\DateInterval::createFromDateString("10 day"));

        parent::setUp();
    }

    public function testDifferent()
    {
        $interval = new Interval($this->minus10Days, $this->yesterday);

        $interval->remove(new Interval($this->tomorrow, $this->plus10Days));

        $this->assertEquals($this->minus10Days, $interval->start);
        $this->assertEquals($this->yesterday, $interval->end);
    }
}


, , .





class IntervalRemoveTest extends TestCase
{
    public function testFullyCovered()
    {
        $interval = new Interval($this->yesterday, $this->tomorrow);

        $interval->remove(new Interval($this->minus10Days, $this->plus10Days));

        $this->assertTrue($interval->isEmpty());
    }

    public function testFullyCoveredWithCommonStart()
    {
        $interval = new Interval($this->yesterday, $this->tomorrow);

        $interval->remove(new Interval($this->yesterday, $this->plus10Days));

        $this->assertTrue($interval->isEmpty());
    }

    // and testFullyCoveredWithCommonEnd()
}


, :





?





?! ! remove ! , :



} elseif ($interval['start'] <= $event['start_date'] && $interval['end'] >= $event['end_date']) {
        $interval['start'] = $event['start_date'];
        $interval['end'] = $event['end_date'];


.



, . , , . . , . , , . , , , . , , ! . .



IntervalCollection, :



class Interval
{
    public DateTimeImmutable $start;
    public DateTimeImmutable $end;

    public function __construct(DateTimeImmutable $start, 
                                DateTimeImmutable $end)
    {
        if ($start > $end) {
            throw new \InvalidArgumentException(
                                "Invalid date interval");
        }

        $this->start = $start;
        $this->end = $end;
    }

    public function isEmpty(): bool
    {
        return $this->start === $this->end;
    }

    /**
     * @param Interval $other
     * @return Interval[]
     */
    public function remove(Interval $other)
    {
        if ($this->start >= $other->end 
                || $this->end <= $other->start) return [$this];

        if ($this->start >= $other->start 
                && $this->end <= $other->end) return [];

        if ($this->start < $other->start 
                && $this->end > $other->end) return [
            new Interval($this->start, $other->start),
            new Interval($other->end, $this->end),
        ];

        if ($this->start === $other->start) {
            return [new Interval($other->end, $this->end)];
        }

        return [new Interval($this->start, $other->start)];
    }
}

/** @mixin Interval[] */
class IntervalCollection extends \ArrayIterator
{
    public function diff(IntervalCollection $other)
            : IntervalCollection
    {
        /** @var Interval[] $items */
        $items = $this->getArrayCopy();
        foreach ($other as $interval) {
            $newItems = [];
            foreach ($items as $ourInterval) {
                array_push($newItems, 
                ...$ourInterval->remove($interval));
            }
            $items = $newItems;
        }

        return new self($items);
    }
}


IntervalCollection — , . Interval, , .



https://github.com/adelf/intervals-example. , IntervalCollection::diff . , . . unit-.



, - coupling ( ), . private:



class Interval
{
    private DateTimeImmutable $start;
    private DateTimeImmutable $end;

    // methods
}


Cela peut être fait en ajoutant des méthodes de style print()qui nous aideront à extraire les données d'intervalle dans le format souhaité, mais qui fermeront complètement la possibilité de travailler avec les données d'intervalle de l'extérieur. Mais c'est certainement un sujet pour un autre article.




All Articles