$30 off During Our Annual Pro Sale. View Details »

The true value of objects

The true value of objects

Although the latest PHP versions provide us with enough types and keywords to enable object oriented development, the language itself is not truly object oriented. In pure object oriented languages, like Java, almost everything is an object. Even primitives have their object equivalent. Concepts that first look like simple values, can in fact be modelled as objects. This enables us to add a lot of behavior to them. These so called Value Objects make our code more readable, elegant, maintainable and dry. We will explore the possibilities and advantages of these Value Objects together, guided by some real world code samples.

Stijn Vannieuwenhuyse

January 28, 2017
Tweet

More Decks by Stijn Vannieuwenhuyse

Other Decks in Programming

Transcript

  1. The true value of
    objects

    View Slide

  2. Stijn Vannieuwenhuyse
    @stijnvnh

    View Slide

  3. Teamleader
    CRM - invoicing - project management
    head of engineering

    View Slide

  4. The true value of
    objects

    View Slide

  5. what are
    Objects?

    View Slide

  6. represent domain concepts
    encapsulate data
    expose behaviour

    View Slide

  7. SOLID

    View Slide

  8. Domain
    Driven
    Design

    View Slide

  9. Service
    Objects

    View Slide

  10. Entities

    View Slide

  11. Value
    Objects

    View Slide

  12. 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;
    }
    }

    View Slide

  13. $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

    View Slide

  14. but ...
    Why?

    View Slide

  15. Model domain concepts

    View Slide

  16. 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);

    View Slide

  17. 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
    }

    View Slide

  18. 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)
    );

    View Slide

  19. Avoid bugs

    View Slide

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

    View Slide

  21. $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
    }

    View Slide

  22. Achieve single
    responsibility

    View Slide

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

    View Slide

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

    View Slide

  25. PHP
    BUILT IN

    View Slide

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

    View Slide

  27. 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

    View Slide

  28. CAUTION

    View Slide

  29. Value objects should be
    immutable

    View Slide

  30. 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

    View Slide

  31. 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);
    }
    }

    View Slide

  32. ENDLESS POSSIBILITIES

    View Slide

  33. 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?

    View Slide

  34. 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

    View Slide

  35. 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])
    )
    }
    }

    View Slide

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

    View Slide

  37. 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;
    }
    }

    View Slide

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

    View Slide

  39. 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

    View Slide

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

    View Slide

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

    View Slide

  42. 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
    }

    View Slide

  43. 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) { /* ... */ }
    }

    View Slide

  44. 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();
    }

    View Slide

  45. 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();
    }

    View Slide

  46. But ...

    View Slide

  47. what about database storage?

    View Slide

  48. 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);
    }
    }

    View Slide

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

    View Slide

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

    View Slide

  51. what about memory usage?

    View Slide

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

    View Slide

  53. $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];
    }
    }

    View Slide

  54. Value object
    or entity?

    View Slide

  55. User

    View Slide

  56. Username

    View Slide

  57. Product-id

    View Slide

  58. Date

    View Slide

  59. Money

    View Slide

  60. Blog Post

    View Slide

  61. Blog Post
    Category

    View Slide

  62. VALUE OBJECTS
    represent domain values
    encapsulate data
    expose behaviour

    View Slide

  63. VALUE OBJECTS
    dry
    readability
    immutable

    View Slide

  64. QUESTIONS?

    View Slide

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

    View Slide