我们的PHP开发人员为使用OOP语言编写而感到自豪(您可以在此处轻松地用C#,Java或其他OOP语言替换PHP)。每个空缺都包含对OOP知识的要求。在每次采访中,他们都会询问有关SOLID或OOP的三头鲸的问题。但是归根结底,我们只是让类充满了过程。OOP很少,通常在库代码中。
一个典型的Web应用程序是ORM实体类,它们包含数据库中某一行的数据,而控制器(或服务-没关系)包含处理这些数据的过程。面向对象的程序设计是关于拥有自己数据的对象,并且不将其提供给其他代码进行处理。一个很好的说明就是一个聊天中提出的一个问题:“如何改进此代码?”
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
}
这可以通过添加样式方法来完成,print()
这将有助于我们以所需的格式提取间隔数据,但将完全关闭从外部处理间隔数据的功能。但这绝对是另一篇文章的主题。