SlideShare a Scribd company logo
Time-driven
applications
Piotr Horzycki
Team leader @ gwo.pl
phpCE 2017, Rawa Mazowiecka, Poland
What's time got to do with
web applications?
●
News, movies & other content
●
Voting systems
●
Subscriptions with time constraints
(like "Call For Papers":
https://github.com/opencfp/opencfp)
●
Discount systems
●
Reports, statistics
A very bad example...
class SuperMegaFancyPromotionController
{
public function showAction()
{
if (time() > 1496268000) {
$this->showForm();
} elseif (time() < 1490997600 && time() > 1488322800) {
$this->showWarning();
} else {
/* ... */
}
}
}
This code:
- is hard to read
- cannot be easily tested (see uopz extension)
- is not reusable
- must be modified even if somebody wants to change the dates (hardcoding)
Date & time operations in PHP
●
date(), time(), strtotime(), strftime()...
●
DateTime
●
DateInterval
●
DatePeriod
●
DateTimeZone
UNIX timestamps
●
A signed integer; a number of seconds since the "epoch"
– 1970-01-01 Coordinated Universal Time (UTC)
●
On 32-bit systems, the maximum supported year was 2038
●
On 64-bit systems, PHP 7 works beyond the year 2038:
$ php -r "echo strtotime('1939-09-01');"
-957315600
$ php -r "echo strtotime('2049-09-01');"
2514063600
Why use functions if we have
classes?
function getNextMonthAsRSSDate(DateTimeImmutable $date): string
{
$nextMonth = $date->add(new DateInterval('P1M'));
return $nextMonth->format(DateTime::RSS);
}
function getNextMonthAsRSSDate(int $date): string
{
$nextMonth = mktime(
date('H', $date), date('i', $date),
date('s', $date), date('n', $date) + 1
);
return date('D, d M Y H:i:s O', $nextMonth);
}
DateTimeImmutable vs DateTime
PHP Manual on DateTimeImmutable:
„This class behaves the same as DateTime except it never modifies itself but
returns a new object instead.”
Thus, DateTimeImmutable is a value object, known from DDD.
$now = new DateTime();
$nextMonth = $now->add(new DateInterval('P1M'));
/* $now and $nextMonth are pointers to the same object;
* we need two separate value objects! */
debug_zval_dump($now);
debug_zval_dump($nextMonth);
Timezones are tricky
●
Your server has its local time, your users have their
own local time
●
Daylight Saving Time works differently in different
countries
●
Timezones are not always shifted by whole hours
(like +8:45 in Australia)
●
Solution: store all dates in Coordinated Universal
Time – and convert back to local time in the
presentation layer
Time-driven applications
Time-driven applications
Intervals
$june = new DateTimeImmutable('2017-06-01Z');
$november = new DateTimeImmutable('2017-11-01Z');
echo $november
->diff($june) // returns DateInterval
->format('%r%m') . ' months' . PHP_EOL;
// Output: -5 months
Creating intervals in ISO 8601 syntax
$interval = new DateInterval('P1Y2M3DT4H5M6S');
$interval = new DateInterval('P0001-02-03T04:05:06');
=
1 year, 2 months, 3 days,
4 hours, 5 minutes and 6 seconds
Periods
function printSchoolYearMondays()
{
$start = new DateTimeImmutable('2017-09-01Z');
$end = new DateTimeImmutable('2018-06-30T23:59:59Z');
$interval = DateInterval::createFromDateString('next monday');
$period = new DatePeriod(
$start,
$interval,
$end,
DatePeriod::EXCLUDE_START_DATE
);
foreach ($period as $date) {
// $date is immutable if $start is too
echo $date->format('l, Y-m-d') . PHP_EOL;
}
}
Date ranges in MySQL
SELECT *
FROM news
WHERE
YEAR(published_at) = 2017 AND MONTH(published_at) = 11;
SELECT *
FROM news
WHERE
published_at BETWEEN '2017-11-01' AND '2017-11-30 23:59:59';
SELECT *
FROM news
WHERE
published_at LIKE '2017-11-%';
class MonthPeriod
{
public function getStart()
{
return new
DateTimeImmutable('first day of this month midnight');
}
public function getEnd()
{
return new
DateTimeImmutable('first day of next month midnight -1 second');
}
}
define('SQL_DATETIME', 'Y-m-d H:i:s');
$period = new MonthPeriod();
echo $period->getStart()->format(SQL_DATETIME) . PHP_EOL;
echo $period->getEnd()->format(SQL_DATETIME) . PHP_EOL;
// Output:
// 2017-11-01 00:00:00
// 2017-11-30 23:59:59
These two values are ready to be bound into a BETWEEN … AND … statement.
Remember about tests!
class MonthPeriod
{
private $now;
public function __construct(DateTimeImmutable $now)
{
$this->now = $now;
}
public function getStart()
{
return $this->now->modify('first day of this month midnight');
}
public function getEnd()
{
return $this->now->modify('first day of next month midnight -1 second');
}
}
class MonthPeriodTest extends PHPUnitFrameworkTestCase
{
/** @dataProvider getDateRanges */
public function testMonth(
string $today,
string $expectedStartDay,
string $expectedEndDay
) {
$now = new DateTimeImmutable($today);
$period = new MonthPeriod($now);
$expectedStart = new DateTimeImmutable($expectedStartDay);
$expectedEnd = new DateTimeImmutable($expectedEndDay);
$this->assertEquals($expectedStart, $period->getStart());
$this->assertEquals($expectedEnd, $period->getEnd());
}
public function getDateRanges()
{
return [
'November' => [
'2017-11-11Z', '2017-11-01 00:00:00Z', '2017-11-30 23:59:59Z'
],
'Leap year' => [
'2016-02-11Z', '2016-02-01 00:00:00Z', '2016-02-29 23:59:59Z'
],
];
}
}
Date and time arithmetic pitfalls
$lastDayOfJanuary = new DateTimeImmutable('2016-01-31Z');
echo $lastDayOfJanuary
->modify('+1 month')
->format('Y-m-d') . PHP_EOL;
echo $lastDayOfJanuary
->modify('last day of next month')
->format('Y-m-d') . PHP_EOL;
// Output:
// 2016-03-02
// 2016-02-29
More: http://php.net/manual/en/datetime.examples-arithmetic.php
More: Michał Pipa, Data i czas dla programistów (https://youtu.be/ZRoEVJlxPlo)
A bad example, once again...
class SuperMegaFancyPromotionController()
{
public function showAction()
{
if (time() > 1496268000) {
$this->showForm();
} elseif (time() < 1490997600 && time() > 1488322800) {
$this->showWarning();
} else {
/* ... */
}
}
}
Let's rewrite that legacy code...
class PromotionTimelineTest extends PHPUnitFrameworkTestCase
{
public function testIsActive()
{
$now = new DateTimeImmutable('2017-05-01T00:00:00Z');
$opening = $now;
$closing = $opening->add(new DateInterval('P2M'));
$timeline = new PromotionTimeline($now, $opening, $closing);
$this->assertTrue($timeline->isActive());
}
}
...starting from a test
Testing the exceptions
class PromotionTimelineTest extends PHPUnitFrameworkTestCase
{
/* ... */
/**
* @expectedException InvalidArgumentException
*/
public function testInvalidArguments()
{
$now = new DateTimeImmutable('2017-05-01T00:00:00Z');
$opening = $now;
$closing = $opening->sub(new DateInterval('PT1S'));
new PromotionTimeline($now, $opening, $closing);
}
}
class PromotionTimeline
{
private $now;
private $openingDate;
private $closingDate;
public function __construct(
DateTimeImmutable $now,
DateTimeImmutable $openingDate,
DateTimeImmutable $closingDate
) {
if ($openingDate >= $closingDate) {
throw new InvalidArgumentException('The opening date must be
earlier than the closing date');
}
$this->now = $now;
$this->openingDate = $openingDate;
$this->closingDate = $closingDate;
}
public function isActive()
{
return
($this->now >= $this->openingDate) &&
($this->now < $this->closingDate);
}
}
What might come next?
●
Early-bird tickets
●
Reminders
●
Last-minute tickets
●
Individual coupons valid until ...
●
Black Friday
●
...
Expect the unexpected!
Remember the Strategy pattern!
●
Let's say we develop a hotel booking platform
●
Ordinary users have to check out at a fixed
hour specified by the hotel
●
But the premium users have extra time!
Old-fashioned way...
$hotel = Hotel::find(['name' => 'Ossa']);
$user = User::find(['login' => 'test']);
if ($user->isPremium()) {
$checkout = $hotel->getCheckoutTime()->add(new DateInterval('P2H'));
} else {
$checkout = $hotel->getCheckoutTime();
}
echo 'Checkout at ' . $checkout->format('H:i');
interface CheckoutStrategyInterface
{
public function getCheckoutTime();
}
class User
{
private $checkoutStrategy;
public function __construct(CheckoutStrategyInterface $checkoutStrategy)
{
$this->checkoutStrategy = $checkoutStrategy;
}
public function getCheckoutTime()
{
return $this->checkoutStrategy->getCheckoutTime();
}
}
Doing it the flexible way
class StandardCheckoutStrategy implements CheckoutStrategyInterface
{
private $checkout;
public function __construct(DateTimeImmutable $checkout)
{
$this->checkout = $checkout;
}
public function getCheckoutTime()
{
return $this->checkout;
}
}
class ExtendedCheckoutStrategy extends StandardCheckoutStrategy
{
private $extension;
public function __construct(
DateTimeImmutable $checkout,
DateInterval $extension
) {
parent::__construct($checkout);
$this->extension = $extension;
}
public function getCheckoutTime()
{
return $this->checkout->add($this->extension);
}
}
Why do we create so many
classes?
●
A class should be modified only for a single
reason
●
Single-responsibility principle
●
Flexibility: loose coupling, high cohesion
●
You never know what business
brings next
●
But don't over-engineer!
Distributed systems
●
Servers in multiple timezones? Oops...
●
Date synchronization via Network Time Protocol
●
Time events
●
A lot of fun :)
Thank you!
piotr.horzycki@gmail.com
twitter.com/phorzycki
github.com/phorzycki/datetime_examples

More Related Content

PDF
The Ring programming language version 1.9 book - Part 91 of 210
PDF
The Ring programming language version 1.5.3 book - Part 88 of 184
DOCX
DataStructures notes
DOCX
Assignment no39
PPTX
Double linked list
PPTX
javascript function & closure
PDF
C++ programs
DOCX
Travel management
The Ring programming language version 1.9 book - Part 91 of 210
The Ring programming language version 1.5.3 book - Part 88 of 184
DataStructures notes
Assignment no39
Double linked list
javascript function & closure
C++ programs
Travel management

What's hot (20)

DOCX
Pratik Bakane C++
PPTX
Double linked list
PDF
C++ TUTORIAL 3
PDF
The Ring programming language version 1.5.4 book - Part 78 of 185
TXT
How to send a mail from utl smtp or from back end
PDF
Advanced Java Practical File
DOCX
Pratik Bakane C++
PDF
C++ TUTORIAL 8
PPTX
Single linked list
DOCX
Pratik Bakane C++
PPTX
Circular linked list
PDF
C++ TUTORIAL 5
PDF
C++ TUTORIAL 10
DOCX
Programa en C++ ( escriba 3 números y diga cual es el mayor))
DOC
VLSI Sequential Circuits II
DOC
Numerical Methods in C
PPTX
Binary Search Tree
DOCX
Pratik Bakane C++
PPT
DATASTRUCTURES PPTS PREPARED BY M V BRAHMANANDA REDDY
DOCX
Ejercicios
Pratik Bakane C++
Double linked list
C++ TUTORIAL 3
The Ring programming language version 1.5.4 book - Part 78 of 185
How to send a mail from utl smtp or from back end
Advanced Java Practical File
Pratik Bakane C++
C++ TUTORIAL 8
Single linked list
Pratik Bakane C++
Circular linked list
C++ TUTORIAL 5
C++ TUTORIAL 10
Programa en C++ ( escriba 3 números y diga cual es el mayor))
VLSI Sequential Circuits II
Numerical Methods in C
Binary Search Tree
Pratik Bakane C++
DATASTRUCTURES PPTS PREPARED BY M V BRAHMANANDA REDDY
Ejercicios
Ad

Similar to Time-driven applications (20)

PDF
Advanced Date/Time Handling with PHP
PDF
Péhápkaři v Pecce: Za hranicemi DateTime – Jiří Pudil – 16. 10. 2019
PPTX
Dealing with Continuous Data Processing, ConFoo 2012
PPT
Php date &amp; time functions
PDF
PHP 5.3 Overview
PPSX
DIWE - Advanced PHP Concepts
ODP
Why should we use SIMPLE FACTORY pattern even when we have one class only?
KEY
Can't Miss Features of PHP 5.3 and 5.4
KEY
Time tested php with libtimemachine
PDF
PHP Cheat Sheet
PPTX
Creating "Secure" PHP Applications, Part 1, Explicit Code & QA
PDF
Temporal Data
PPT
Time manipulation lecture 2
PPTX
OOP Is More Then Cars and Dogs - Midwest PHP 2017
PDF
Double Loop: TDD & BDD Done Right
PPTX
Double Loop
PDF
The PHP Way Of TDD - Think First, Code Later
PPTX
Conditional Statementfinal PHP 02
PDF
Object Oriented Programming in PHP
PDF
OOPs Concept
Advanced Date/Time Handling with PHP
Péhápkaři v Pecce: Za hranicemi DateTime – Jiří Pudil – 16. 10. 2019
Dealing with Continuous Data Processing, ConFoo 2012
Php date &amp; time functions
PHP 5.3 Overview
DIWE - Advanced PHP Concepts
Why should we use SIMPLE FACTORY pattern even when we have one class only?
Can't Miss Features of PHP 5.3 and 5.4
Time tested php with libtimemachine
PHP Cheat Sheet
Creating "Secure" PHP Applications, Part 1, Explicit Code & QA
Temporal Data
Time manipulation lecture 2
OOP Is More Then Cars and Dogs - Midwest PHP 2017
Double Loop: TDD & BDD Done Right
Double Loop
The PHP Way Of TDD - Think First, Code Later
Conditional Statementfinal PHP 02
Object Oriented Programming in PHP
OOPs Concept
Ad

More from Piotr Horzycki (9)

PDF
Serial(ize) killers, czyli jak popsuliśmy API
PDF
Mity, które blokują Twoją karierę
PDF
Architecture tests: Setting a common standard
PDF
Software development myths that block your career
PDF
Software Composition Analysis in PHP
PDF
How to count money with Java and not lose it
PDF
How to count money using PHP and not lose money
PDF
New kids on the block: Conducting technical onboarding
PDF
Jak zacząć, aby nie żałować - czyli 50 twarzy PHP
Serial(ize) killers, czyli jak popsuliśmy API
Mity, które blokują Twoją karierę
Architecture tests: Setting a common standard
Software development myths that block your career
Software Composition Analysis in PHP
How to count money with Java and not lose it
How to count money using PHP and not lose money
New kids on the block: Conducting technical onboarding
Jak zacząć, aby nie żałować - czyli 50 twarzy PHP

Recently uploaded (20)

PPTX
Nature of X-rays, X- Ray Equipment, Fluoroscopy
PDF
R24 SURVEYING LAB MANUAL for civil enggi
PDF
Human-AI Collaboration: Balancing Agentic AI and Autonomy in Hybrid Systems
PDF
737-MAX_SRG.pdf student reference guides
PPTX
CURRICULAM DESIGN engineering FOR CSE 2025.pptx
PDF
Mitigating Risks through Effective Management for Enhancing Organizational Pe...
PDF
BIO-INSPIRED ARCHITECTURE FOR PARSIMONIOUS CONVERSATIONAL INTELLIGENCE : THE ...
PDF
Abrasive, erosive and cavitation wear.pdf
PDF
Soil Improvement Techniques Note - Rabbi
PDF
Unit I ESSENTIAL OF DIGITAL MARKETING.pdf
PDF
The CXO Playbook 2025 – Future-Ready Strategies for C-Suite Leaders Cerebrai...
PPT
A5_DistSysCh1.ppt_INTRODUCTION TO DISTRIBUTED SYSTEMS
PDF
COURSE DESCRIPTOR OF SURVEYING R24 SYLLABUS
PPTX
Information Storage and Retrieval Techniques Unit III
PPT
Introduction, IoT Design Methodology, Case Study on IoT System for Weather Mo...
PDF
III.4.1.2_The_Space_Environment.p pdffdf
PDF
SMART SIGNAL TIMING FOR URBAN INTERSECTIONS USING REAL-TIME VEHICLE DETECTI...
PDF
PREDICTION OF DIABETES FROM ELECTRONIC HEALTH RECORDS
PPT
Total quality management ppt for engineering students
PDF
Integrating Fractal Dimension and Time Series Analysis for Optimized Hyperspe...
Nature of X-rays, X- Ray Equipment, Fluoroscopy
R24 SURVEYING LAB MANUAL for civil enggi
Human-AI Collaboration: Balancing Agentic AI and Autonomy in Hybrid Systems
737-MAX_SRG.pdf student reference guides
CURRICULAM DESIGN engineering FOR CSE 2025.pptx
Mitigating Risks through Effective Management for Enhancing Organizational Pe...
BIO-INSPIRED ARCHITECTURE FOR PARSIMONIOUS CONVERSATIONAL INTELLIGENCE : THE ...
Abrasive, erosive and cavitation wear.pdf
Soil Improvement Techniques Note - Rabbi
Unit I ESSENTIAL OF DIGITAL MARKETING.pdf
The CXO Playbook 2025 – Future-Ready Strategies for C-Suite Leaders Cerebrai...
A5_DistSysCh1.ppt_INTRODUCTION TO DISTRIBUTED SYSTEMS
COURSE DESCRIPTOR OF SURVEYING R24 SYLLABUS
Information Storage and Retrieval Techniques Unit III
Introduction, IoT Design Methodology, Case Study on IoT System for Weather Mo...
III.4.1.2_The_Space_Environment.p pdffdf
SMART SIGNAL TIMING FOR URBAN INTERSECTIONS USING REAL-TIME VEHICLE DETECTI...
PREDICTION OF DIABETES FROM ELECTRONIC HEALTH RECORDS
Total quality management ppt for engineering students
Integrating Fractal Dimension and Time Series Analysis for Optimized Hyperspe...

Time-driven applications

  • 1. Time-driven applications Piotr Horzycki Team leader @ gwo.pl phpCE 2017, Rawa Mazowiecka, Poland
  • 2. What's time got to do with web applications? ● News, movies & other content ● Voting systems ● Subscriptions with time constraints (like "Call For Papers": https://github.com/opencfp/opencfp) ● Discount systems ● Reports, statistics
  • 3. A very bad example... class SuperMegaFancyPromotionController { public function showAction() { if (time() > 1496268000) { $this->showForm(); } elseif (time() < 1490997600 && time() > 1488322800) { $this->showWarning(); } else { /* ... */ } } } This code: - is hard to read - cannot be easily tested (see uopz extension) - is not reusable - must be modified even if somebody wants to change the dates (hardcoding)
  • 4. Date & time operations in PHP ● date(), time(), strtotime(), strftime()... ● DateTime ● DateInterval ● DatePeriod ● DateTimeZone
  • 5. UNIX timestamps ● A signed integer; a number of seconds since the "epoch" – 1970-01-01 Coordinated Universal Time (UTC) ● On 32-bit systems, the maximum supported year was 2038 ● On 64-bit systems, PHP 7 works beyond the year 2038: $ php -r "echo strtotime('1939-09-01');" -957315600 $ php -r "echo strtotime('2049-09-01');" 2514063600
  • 6. Why use functions if we have classes? function getNextMonthAsRSSDate(DateTimeImmutable $date): string { $nextMonth = $date->add(new DateInterval('P1M')); return $nextMonth->format(DateTime::RSS); } function getNextMonthAsRSSDate(int $date): string { $nextMonth = mktime( date('H', $date), date('i', $date), date('s', $date), date('n', $date) + 1 ); return date('D, d M Y H:i:s O', $nextMonth); }
  • 7. DateTimeImmutable vs DateTime PHP Manual on DateTimeImmutable: „This class behaves the same as DateTime except it never modifies itself but returns a new object instead.” Thus, DateTimeImmutable is a value object, known from DDD. $now = new DateTime(); $nextMonth = $now->add(new DateInterval('P1M')); /* $now and $nextMonth are pointers to the same object; * we need two separate value objects! */ debug_zval_dump($now); debug_zval_dump($nextMonth);
  • 8. Timezones are tricky ● Your server has its local time, your users have their own local time ● Daylight Saving Time works differently in different countries ● Timezones are not always shifted by whole hours (like +8:45 in Australia) ● Solution: store all dates in Coordinated Universal Time – and convert back to local time in the presentation layer
  • 11. Intervals $june = new DateTimeImmutable('2017-06-01Z'); $november = new DateTimeImmutable('2017-11-01Z'); echo $november ->diff($june) // returns DateInterval ->format('%r%m') . ' months' . PHP_EOL; // Output: -5 months
  • 12. Creating intervals in ISO 8601 syntax $interval = new DateInterval('P1Y2M3DT4H5M6S'); $interval = new DateInterval('P0001-02-03T04:05:06'); = 1 year, 2 months, 3 days, 4 hours, 5 minutes and 6 seconds
  • 13. Periods function printSchoolYearMondays() { $start = new DateTimeImmutable('2017-09-01Z'); $end = new DateTimeImmutable('2018-06-30T23:59:59Z'); $interval = DateInterval::createFromDateString('next monday'); $period = new DatePeriod( $start, $interval, $end, DatePeriod::EXCLUDE_START_DATE ); foreach ($period as $date) { // $date is immutable if $start is too echo $date->format('l, Y-m-d') . PHP_EOL; } }
  • 14. Date ranges in MySQL SELECT * FROM news WHERE YEAR(published_at) = 2017 AND MONTH(published_at) = 11; SELECT * FROM news WHERE published_at BETWEEN '2017-11-01' AND '2017-11-30 23:59:59'; SELECT * FROM news WHERE published_at LIKE '2017-11-%';
  • 15. class MonthPeriod { public function getStart() { return new DateTimeImmutable('first day of this month midnight'); } public function getEnd() { return new DateTimeImmutable('first day of next month midnight -1 second'); } } define('SQL_DATETIME', 'Y-m-d H:i:s'); $period = new MonthPeriod(); echo $period->getStart()->format(SQL_DATETIME) . PHP_EOL; echo $period->getEnd()->format(SQL_DATETIME) . PHP_EOL; // Output: // 2017-11-01 00:00:00 // 2017-11-30 23:59:59 These two values are ready to be bound into a BETWEEN … AND … statement.
  • 16. Remember about tests! class MonthPeriod { private $now; public function __construct(DateTimeImmutable $now) { $this->now = $now; } public function getStart() { return $this->now->modify('first day of this month midnight'); } public function getEnd() { return $this->now->modify('first day of next month midnight -1 second'); } }
  • 17. class MonthPeriodTest extends PHPUnitFrameworkTestCase { /** @dataProvider getDateRanges */ public function testMonth( string $today, string $expectedStartDay, string $expectedEndDay ) { $now = new DateTimeImmutable($today); $period = new MonthPeriod($now); $expectedStart = new DateTimeImmutable($expectedStartDay); $expectedEnd = new DateTimeImmutable($expectedEndDay); $this->assertEquals($expectedStart, $period->getStart()); $this->assertEquals($expectedEnd, $period->getEnd()); } public function getDateRanges() { return [ 'November' => [ '2017-11-11Z', '2017-11-01 00:00:00Z', '2017-11-30 23:59:59Z' ], 'Leap year' => [ '2016-02-11Z', '2016-02-01 00:00:00Z', '2016-02-29 23:59:59Z' ], ]; } }
  • 18. Date and time arithmetic pitfalls $lastDayOfJanuary = new DateTimeImmutable('2016-01-31Z'); echo $lastDayOfJanuary ->modify('+1 month') ->format('Y-m-d') . PHP_EOL; echo $lastDayOfJanuary ->modify('last day of next month') ->format('Y-m-d') . PHP_EOL; // Output: // 2016-03-02 // 2016-02-29 More: http://php.net/manual/en/datetime.examples-arithmetic.php More: Michał Pipa, Data i czas dla programistów (https://youtu.be/ZRoEVJlxPlo)
  • 19. A bad example, once again... class SuperMegaFancyPromotionController() { public function showAction() { if (time() > 1496268000) { $this->showForm(); } elseif (time() < 1490997600 && time() > 1488322800) { $this->showWarning(); } else { /* ... */ } } }
  • 20. Let's rewrite that legacy code... class PromotionTimelineTest extends PHPUnitFrameworkTestCase { public function testIsActive() { $now = new DateTimeImmutable('2017-05-01T00:00:00Z'); $opening = $now; $closing = $opening->add(new DateInterval('P2M')); $timeline = new PromotionTimeline($now, $opening, $closing); $this->assertTrue($timeline->isActive()); } } ...starting from a test
  • 21. Testing the exceptions class PromotionTimelineTest extends PHPUnitFrameworkTestCase { /* ... */ /** * @expectedException InvalidArgumentException */ public function testInvalidArguments() { $now = new DateTimeImmutable('2017-05-01T00:00:00Z'); $opening = $now; $closing = $opening->sub(new DateInterval('PT1S')); new PromotionTimeline($now, $opening, $closing); } }
  • 22. class PromotionTimeline { private $now; private $openingDate; private $closingDate; public function __construct( DateTimeImmutable $now, DateTimeImmutable $openingDate, DateTimeImmutable $closingDate ) { if ($openingDate >= $closingDate) { throw new InvalidArgumentException('The opening date must be earlier than the closing date'); } $this->now = $now; $this->openingDate = $openingDate; $this->closingDate = $closingDate; } public function isActive() { return ($this->now >= $this->openingDate) && ($this->now < $this->closingDate); } }
  • 23. What might come next? ● Early-bird tickets ● Reminders ● Last-minute tickets ● Individual coupons valid until ... ● Black Friday ● ... Expect the unexpected!
  • 24. Remember the Strategy pattern! ● Let's say we develop a hotel booking platform ● Ordinary users have to check out at a fixed hour specified by the hotel ● But the premium users have extra time!
  • 25. Old-fashioned way... $hotel = Hotel::find(['name' => 'Ossa']); $user = User::find(['login' => 'test']); if ($user->isPremium()) { $checkout = $hotel->getCheckoutTime()->add(new DateInterval('P2H')); } else { $checkout = $hotel->getCheckoutTime(); } echo 'Checkout at ' . $checkout->format('H:i');
  • 26. interface CheckoutStrategyInterface { public function getCheckoutTime(); } class User { private $checkoutStrategy; public function __construct(CheckoutStrategyInterface $checkoutStrategy) { $this->checkoutStrategy = $checkoutStrategy; } public function getCheckoutTime() { return $this->checkoutStrategy->getCheckoutTime(); } } Doing it the flexible way
  • 27. class StandardCheckoutStrategy implements CheckoutStrategyInterface { private $checkout; public function __construct(DateTimeImmutable $checkout) { $this->checkout = $checkout; } public function getCheckoutTime() { return $this->checkout; } }
  • 28. class ExtendedCheckoutStrategy extends StandardCheckoutStrategy { private $extension; public function __construct( DateTimeImmutable $checkout, DateInterval $extension ) { parent::__construct($checkout); $this->extension = $extension; } public function getCheckoutTime() { return $this->checkout->add($this->extension); } }
  • 29. Why do we create so many classes? ● A class should be modified only for a single reason ● Single-responsibility principle ● Flexibility: loose coupling, high cohesion ● You never know what business brings next ● But don't over-engineer!
  • 30. Distributed systems ● Servers in multiple timezones? Oops... ● Date synchronization via Network Time Protocol ● Time events ● A lot of fun :)