Slide 1

Slide 1 text

The true value of objects

Slide 2

Slide 2 text

Stijn Vannieuwenhuyse @stijnvnh

Slide 3

Slide 3 text

Teamleader CRM - invoicing - project management head of engineering

Slide 4

Slide 4 text

The true value of objects

Slide 5

Slide 5 text

what are Objects?

Slide 6

Slide 6 text

represent domain concepts encapsulate data expose behaviour

Slide 7

Slide 7 text

SOLID

Slide 8

Slide 8 text

Domain Driven Design

Slide 9

Slide 9 text

Service Objects

Slide 10

Slide 10 text

Entities

Slide 11

Slide 11 text

Value Objects

Slide 12

Slide 12 text

class Money { private $amount; private $currency; public function __construct($amount, $currency) { $this->amount = $amount; $this->currency = $currency; } public function getAmount() { return $this->amount; } public function getCurrency() { return $this->currency; } }

Slide 13

Slide 13 text

$a = new Money(5.00, "EUR"); $b = new Money(5.00, "EUR"); $c = new Money(4.23, "EUR"); $d = new Money(5.00, "USD"); $a === $b; //false $a == $b; //true $a == $c; //false $a == $d; //false

Slide 14

Slide 14 text

but ... Why?

Slide 15

Slide 15 text

Model domain concepts

Slide 16

Slide 16 text

class InvoiceItem { private $invoiceId; private $productId; private $amount; private $price; private $vatrate; public function __construct($invoiceId, $productId, $amount, $price, $vatrate) { //... } public function getLineTotalExclusiveVat() { return $this->amount * $this->price; } public function getVatTotal() { return $this->getLineTotalExclusiveVat() * $this->vatrate; } public function getLineTotalInclusiveVat() { return $this->getLineTotalExclusiveVat() + $this->getVatAmount(); } //other getters } $invoiceItem = new InvoiceItem(1, 2, 100, 1000.00, 21.0);

Slide 17

Slide 17 text

class PriceExclusiveVat { private $money; private $vat; public function __construct( $money, $vat ) { $this->money = $money; $this->vat = $vat; } public function getVatAmount() { return $this->money * $this->vat; } //other getters }

Slide 18

Slide 18 text

class InvoiceItem { private $invoiceId; private $productId; private $amount; private $price; private $vatrate; public function __construct($invoiceId, $productId, $amount, PriceExclusiveVat $price) { //... } public function getLineTotalExclusiveVat() { return $this->amount * $this->price->getAmountExclusiveVat(); } public function getVatTotal() { return $this->amount * $this->price->getVatAmount(); } public function getLineTotalInclusiveVat() { return $this->amount * $this->price->getAmountInclusiveVat(); } //other getters } $invoiceItem = new InvoiceItem( 1, 2, 100, new PriceExclusiveVat(new Money(1000.00, "EUR"), 21.0) );

Slide 19

Slide 19 text

Avoid bugs

Slide 20

Slide 20 text

$invoiceItem = new InvoiceItem( 1, 2, 100, new PriceExclusiveVat(new Money(1000.00, "EUR"), 21.0) ); /// public function getVatAmount() { return $this->money * $this->vat }

Slide 21

Slide 21 text

$invoiceItem = new InvoiceItem( 1, 2, 100, new PriceExclusiveVat(new Money(1000.00, "EUR"), new Percentage(21.0)) ); class Percentage { private $percentage; public function __construct( $percentage ) { $this->percentage = $percentage } public function asFloat() { return $this->percentage / 100; } //other getters }

Slide 22

Slide 22 text

Achieve single responsibility

Slide 23

Slide 23 text

class HexColor { private $hexColor; public function __construct($hexColor) { if(!preg_match('/^#?[a-f0-9]{6}$/i', $hexColor)) { throw new \InvalidArgumentException(); } $this->hexColor = $hexColor; } }

Slide 24

Slide 24 text

class HexColor { private $hexColor; public function __construct($hexColor) { //... } public function blend(HexColor $otherHexColor) { $newHexColor = // color blending...; return new HexColor($newHexColor); } }

Slide 25

Slide 25 text

PHP BUILT IN

Slide 26

Slide 26 text

$a = new DateTime("2017-01-28"); $b = $a->modify("+1 day"); $a == new DateTime("2017-01-28"); //false

Slide 27

Slide 27 text

class User { /** * @var DateTime */ private $registrationDate; public function __construct(DateTime $registrationDate) { $this->registrationDate = $registrationDate; } public function getRegistrationDate() { return $this->registrationDate; } } $user = new User(new DateTime("2017-01-28")); if($user->getRegistrationDate()->modify("+1 year") == new DateTime("today")) { //send anniversary gift } $user->getRegistrationDate() == new DateTime("2017-01-28"); //false $user->getRegistrationDate() == new DateTime("2018-01-28"); //true

Slide 28

Slide 28 text

CAUTION

Slide 29

Slide 29 text

Value objects should be immutable

Slide 30

Slide 30 text

class User { /** * @var DateTimeImmutable */ private $registrationDate; public function __construct(DateTimeImmutable $registrationDate) { $this->registrationDate = $registrationDate; } public function getRegistrationDate() { return $this->registrationDate; } } $user = new User(new DateTimeImmutable("2017-01-28")); if($user->getRegistrationDate()->modify("+1 year") == new DateTimeImmutable("today")) { //send anniversary gift } $user->getRegistrationDate() == new DateTimeImmutable("2017-01-28"); //true

Slide 31

Slide 31 text

class Money { //fields //constructor //getters public function add(Money $other) { if($this->currency != $other->currency) { throw new CurrenciesDontMatch(); } return new Money($this->amount + $other->amount, $this->currency); } }

Slide 32

Slide 32 text

ENDLESS POSSIBILITIES

Slide 33

Slide 33 text

class DateRange { private $start; private $end; public function __construct( DateTimeImmutable $start, DateTimeImmutable $end ) { if($start > $end) { throw new InvalidArgumentException(); } $this->start = $start; $this->end = $end; } //getters } $a = new DateRange(new DateTimeImmutable("today"), new DateTimeImmutable("tomorrow")); $b = new DateRange(new DateTimeImmutable("today"), new DateTimeImmutable("tomorrow")); $c = new DateRange(new DateTimeImmutable("yesterday"), new DateTimeImmutable("today")); $a == $b; //still true $a < $c; //will it work?

Slide 34

Slide 34 text

class DateRange { private $start; private $end; //constructor //getters /** * @return bool */ public function isLessThan(DateRange $other) { return $this->start < $other->start; } } $a = new DateRange(new DateTimeImmutable("today"), new DateTimeImmutable("tomorrow")); $b = new DateRange(new DateTimeImmutable("today"), new DateTimeImmutable("tomorrow")); $c = new DateRange(new DateTimeImmutable("yesterday"), new DateTimeImmutable("today")); $a->isLessThan($b); //false $a->isLessThan($c); //true

Slide 35

Slide 35 text

class DateRange { private $start; private $end; //constructor //getters //comparation public function __toString() { return $this->start->format("U") . " " . $this->end->format("U"); } public static function fromString($string) { if(!preg_match("/^(\S+) (\S+)$/", string, $matches)) { throw new InvalidArgumentException(); } return new DateRange( new DateTimeImmutable($matches[1]), new DateTimeImmutable($matches[2]) ) } }

Slide 36

Slide 36 text

class DateRange { //... /** * @return DateRange[] */ public static function summarize(array $dateRanges) { //some magic to combine overlapping dateranges return $dateRanges; } }

Slide 37

Slide 37 text

class PersonName { private $firstName; private $nickName; private $lastName; public function __construct($firstName, $lastName, $nickName = '') { $this->firstName = $firstName; $this->lastName = $lastName; $this->nickName = $nickName; } //getters public function getDisplayShortName() { return $this->nickName ?: $this->firstName; } public function getDisplayFullName() { return $this->getDisplayShortName() . " " . $this->lastName; } }

Slide 38

Slide 38 text

$string = " FooBar "; trim($string); //FooBar substr($string, 0, 2); //Fo strlen($string); // 2 strtolower($string); //fo //...

Slide 39

Slide 39 text

class String { public function __construct($string) { /*...*/ } public function length() { /*...*/ } public function substring($start, $length) { /*...*/ } public function toLowercase() { /*...*/ } public function trim() { /*...*/ } } $string = new String("foobar"); $string ->trim() ->substring(0, 2) ->toLowercase(); //fo

Slide 40

Slide 40 text

$workingHours = new WeeklyTimeSlot( WeekDay::friday(), new TimeSlot( new Time(08, 30) new Time(17, 00) ) ); /** @var duration Duration */ $duration = $weeklyTimeSlot->getDuration();

Slide 41

Slide 41 text

$workingHours = new WeeklyTimeSlot( WeekDay::friday(), new TimeSlot( new Time(10, 30) new Time(20, 00) ) ); /** @var duration Duration */ $duration = $weeklyTimeSlot->getDuration();

Slide 42

Slide 42 text

class SchoolYear { private $startYear; public function __construct($startYear) { if(!is_int($startYear)) { throw new InvalidArgumentException(); } $this->startYear = $startYear; } public function getStartYear() { /* ... */ } public function getEndYear() { /* ... */ } public function __toString() { /* ... */ } //2014-2015 }

Slide 43

Slide 43 text

class SchoolYear { private $startYear; public function __construct($startYear) { /* ... */ } public static function current() { /* ... */ } public static function containingDate(DateTimeImmutable $date) { /* ... */ } public static function fromString($string) { /* ... */ } public function next() { /* ... */ } public function previous() { /* ... */ } public function getStartYear() { /* ... */ } public function getEndYear() { /* ... */ } public function __toString() { /* ... */ } public function equals(SchoolYear $schoolYear) { /* ... */ } public function getStartDate() { /* ... */ } public function getEndDate() { /* ... */ } public function getDateRange() { /* ... */ } public function getCalendarWeeks() { /* ... */ } public function startsInWeekend() { /* ... */ } public function isActive() { /* ... */ } public function isPassed() { /* ... */ } public function containsCalendarWeek(CalendarWeek $calendarWeek) { /* ... */ } public function containsDate(DateTimeImmutable $date) { /* ... */ } public function containsDateRange(DateRange $date) { /* ... */ } }

Slide 44

Slide 44 text

interface PriceExclusiveVat { public function getInclusiveAmount(); public function getExclusiveAmount(); public function getVatAmount(); public function toPriceInclusiveVat(); } interface PriceInclusiveVat { public function getInclusiveAmount(); public function getExclusiveAmount(); public function getVatAmount(); public function toPriceExclusiveVat(); }

Slide 45

Slide 45 text

interface Invoice { public function getInclusiveAmount(); public function getTotalVat(); public function getAppliedVat(); //AppliedVat[] } interface AppliedVat { public function getVatRate(); public function getExclusiveAmount(); public function getVatAmount(); public function getInclusiveAmount(); }

Slide 46

Slide 46 text

But ...

Slide 47

Slide 47 text

what about database storage?

Slide 48

Slide 48 text

class Talk { //... /** * @ORM\Column(type="datetime") */ private $startTime; /** * @ORM\Column(type="datetime") */ private $endTime; public function __construct(/*...*/, TimeSlot $slot) { $this->startTime = $slot->getStartTime(); $this->endTime = $slot->getEndTime(); } /** * @return TimeSlot */ public function getTimeSlot() { return new TimeSlot($this->startTime, $this->endTime); } }

Slide 49

Slide 49 text

/** * @ORM\Embeddable */ class TimeSlot { /** * @ORM\Column(type="datetime") */ private $startTime; /** * @ORM\Column(type="datetime") */ private $endTime; //... }

Slide 50

Slide 50 text

class Talk { //... /** * @ORM\Embedded(class="TimeSlot") */ private $timeSlot public function __construct(/*...*/, TimeSlot $slot) { $this->timeSlot = $timeSlot; } /** * @return TimeSlot */ public function getTimeSlot() { return $this->timeSlot; } //... }

Slide 51

Slide 51 text

what about memory usage?

Slide 52

Slide 52 text

$cars = []; for($i = 0; $i <= 1000000; $i++) { $cars[] = new Car("Audi A6", "VIN-NR-2015" . $i, new HexColor("FF0000")); }

Slide 53

Slide 53 text

$cars = []; for($i = 0; $i <= 1000000; $i++) { $cars[] = new Car("Audi A6", "VIN-NR-2015" . $i, HexColor::define("FF0000")); } class HexColor { private $hexColor; private static $hexColors = []; private function __construct($hexColor) { /*...*/ } public static function define($hexColor) { if(!array_key_exists($hexColor, self::$hexColors)) { self::$hexColors[$hexColor] = new self($hexColor); } return self::$hexColors[$hexColor]; } }

Slide 54

Slide 54 text

Value object or entity?

Slide 55

Slide 55 text

User

Slide 56

Slide 56 text

Username

Slide 57

Slide 57 text

Product-id

Slide 58

Slide 58 text

Date

Slide 59

Slide 59 text

Money

Slide 60

Slide 60 text

Blog Post

Slide 61

Slide 61 text

Blog Post Category

Slide 62

Slide 62 text

VALUE OBJECTS represent domain values encapsulate data expose behaviour

Slide 63

Slide 63 text

VALUE OBJECTS dry readability immutable

Slide 64

Slide 64 text

QUESTIONS?

Slide 65

Slide 65 text

Thank you Stijn Vannieuwenhuyse - @stijnvnh https://joind.in/talk/a0ccb