The Myth Of Reusability And Other OOP Lies

phpbnl18
2018-01-26

About Me

Benjamin Cremer

Benjamin Cremer
@benjamincremer

PHP Usergroup Münster

Once a month. Speakers wanted!
meetup.com/phpugms
@phpugms

Questions?

Feel free to ask at any time

Example of good Re-Use


// booking controller
throw new \InvalidArgumentException(
    sprintf('Booking by uuid "%s" not found', $request->getUuid()),
    4321
);

// a few lines later...
throw new \InvalidArgumentException(
    sprintf('Booking by uuid "%s" not found', $request->getUuid()),
    4321
);

// a few lines later...
throw new \InvalidArgumentException(
    sprintf('Booking by uuid "%s" not found', $request->getUuid()),
    4321
);
    

class BookingNotFoundException extends \InvalidArgumentException
{
    public const MESSAGE_TEMPLATE = 'Booking by uuid "%s" not found'
    private $code = 4321;
}
    

throw new \BookingNotFoundException(
    sprintf(
        BookingNotFoundException::MESSAGE_TEMPLATE,
        $request->getUuid()
    )
);
    

final class BookingNotFoundException extends \InvalidArgumentException
{
    private $code = 4321;

    public static function createFromUuid($uuid)
    {
        return new self(sprintf(
            'Booking by uuid "%s" not found', $uuid
        ));
    }
}
                    

throw \BookingNotFoundException::createFromUuid(
    $request->getUuid()
);
                    

Lessons Learned

  • Make it easy for the consumer of your API/object

Template method pattern


abstract class AbstractClass
{
    // Public API
    public function doSomething()
    {
        $this->step1();
        $this->step2();
    }

    // Template methods
    abstract protected function step1();
    abstract protected function step2();
}

class ConcreteClass extends AbstractClass
{
    protected function step1() { […] }
    protected function step2() { […] }
}
    

$baz = new ConcreteClass();
$baz->doSomething();
    

Let's add dependencies


abstract class AbstractClass
{
    private $logger;

    public function __construct(Logger $logger)
    {
        $this->logger = $logger;
    }

    public function doSomething()
    {
        $this->step1();
        $this->step2();
        $this->logger->log('Did Something');
    }
}

class ConcreteClass extends AbstractClass
{
    private $db;
    public function __construct(Connection $db)
    {
        $this->db = $db;
    }

    protected function step1() { $this->db->exec() }
    protected function step2() { […] }
}
    

Spotted The Error?


class ConcreteClass extends AbstractClass
{
    public function __construct(Connection $db)
    {
        $this->db = $db;
    }

    protected function step1() { […] }
    protected function step2() { […] }
}
    

class ConcreteClass extends AbstractClass
{
    public function __construct(
        Connection $db
        Logger $logger,
    ) {
        $this->db = $db;
        parent::__construct($logger);
    }

    protected function step1() { […] }
    protected function step2() { […] }
}
    

Even worse!

This is a runtime error, not a compile time error

Side effects of changing something in a base class

https://codeburst.io/inheritance-is-evil-stop-using-it-6c4f1caf5117

Favor Composition Over Inheritance


final class MyFacade
{
    public function __construct(ConcreteClass $myImplementation, Logger $logger)
    {
        $this->myImplementation = $myImplementation;
        $this->logger = $logger;
    }

    public function doSomething()
    {
        $this->myImplementation->step1();
        $this->myImplementation->step2();
        $this->logger->log(..);
    }
}

class ConcreteClass
{
    public function __construct(Connection $db)
    {
        $this->db = $db;
    }

    public function step1() { […] }
    public function step2() { […] }
}
    

$baz = new MyFacade(
    new ConcreteClass(new DB())
    new Logger();
);
$baz->doSomething();
    

Another Bad Example


Class BookingCache extends RedisCacheAdapter
{
    public function addBooking(Bookinging $booking): void
    {
        $this->put($booking->getUuid(), $booking);
    }

    public function getBookingByUuid(string $uuid): Bookinging
    {
        return $this->get($uuid);
    }
}
    

Class BookingCache
{
    private $cache;
    public function __construct(CacheAdapterInterface $cache)  {}

    public function addBooking(Bookinging $booking): void
    {
        $this->cache->put($booking->getUuid(), $booking);
    }

    public function getBookingByUuid(string $uuid): Bookinging
    {
        return $this->cache->get($uuid);
    }
}
    

// return true to win
function foo($x) {
    return $x === $x();
}

foo(    ?    );
    

__invoke


function foo($x) {
    return $x === $x();
}
    

class MyClass
{
    function __invoke()
    {
        return $this;
    }
}

foo(
    new MyClass()
);
    

Anonymous Class


function foo($x) {
    return $x === $x();
}
    

foo(
    new class { function __invoke() { return $this; }}
);
    

Closure?


function foo($x) {
    return $x === $x();
}
    

foo(
    $x = function () use ($x) { return $x; }
);
    

        Notice: Undefined variable: x in ...
    

Closure with bound reference


function foo($x) {
    return $x === $x();
}
    

foo(
    $x = function () use (&$x) { return $x; }
);
    

Bonus: dynamic function name


function foo($x) {
    return $x === $x();
}
    




function a() {return 'a';}
foo('a');
    
twitter.com/matthieunapoli/status/870267655701311488

PHP Visibility


class SomeClass
{
    private $foo;
}
    

PHP has class visibility


class SomeClass
{
    private $foo;

    public function poke(self $other)
    {
        $other->foo = $this->foo; // access to private property
    }
}
                    

Named Constructors


final class BookingNotFoundException
{
    private $uuid;

    private function __construct()
    {
       // empty
    }

    public static function createFromUuid($uuid): self
    {
        $object = new self();
        $object->uuid = $uuid;

        return $object;
    }
}
                    

Closure Hack


class Kitchen
{
    private $cake = 'yummy';
}
               

$kitchen = new Kitchen();
               

$thief = function($kitchen) {
    return $kitchen->cake;
};
               

$cake = $thief($kitchen);
               

         PHP Fatal error: Cannot access private property Kitchen::$yummy in […]
               

$thief = function($kitchen) {
    return $kitchen->cake;
};
$thief->bindTo(null, $kitchen);
               

$cake = $thief($kitchen);
               

Values Objects


class Color
{
    private $red;
    private $green;
    private $blue;

    public function __construct(int $red, int $green, int $blue)
    {
        foreach ([$red, $green, $blue] as $color) {
            if ($color < 0 || $color > 255) {
                throw new InvalidArgumentException(
                    'Color values must be between 0 and 255'
                );
            }
        }

        $this->red = $red;
        $this->green = $green;
        $this->blue = $blue;
    }
}
                    

final class Color
{
    private $red;
    private $green;
    private $blue;

    private function __construct() {}

    public static function createFromHexstring(string $hex): self
    {
        $parts = str_split($hex, 2);
        return self::createFromRGB(
            hexdec($parts[0]),
            hexdec($parts[1]),
            hexdec($parts[2]),
        );
    }

    public static function createFromRGB(int $red, int $green, int $blue): self
    {
        // validation […]
        $color = new self();
        $color->red = $red;
        $color->green = $green;
        $color->blue = $blue;
        return $color;
    }
}
                    

$red = Color::createFromRGB(255, 0, 0);
$blue = Color::createFromHexstring('0000FF')
    

final class Color
{
    private $red;
    private $green;
    private $blue;
    […]

    public function isEqualTo(Color $color): bool
    {
        return $this->red === $color->red
            && $this->green === $color->green
            && $this->blue === $color->blue;
    }
}
    

$red = Color::createFromRGB(255, 0, 0);
$blue = Color::createFromHexstring('0000FF');
$anotherRed = Color::createFromHexstring('FF0000');

$red->isEqualTo($blue); // false
$red->isEqualTo($anotherRed); // true
    

final class Color
{
    private $red;
    private $green;
    private $blue;
    […]

    public function mix(Color $color): self
    {
        $mixedColor = new self();

        $mixedColor->red = min([$this->red + $color->red, 255]);
        $mixedColor->green = min([$this->green + $color->green, 255]);
        $mixedColor->blue = min([$this->blue + $color->blue, 255]);

        return $mixedColor; // return new instance
    }
}
    

$red = Color::createFromRGB(255, 0, 0);
$green = Color::createFromRGB(0, 255, 0);

$yellow = $red->mix($green);
    

Re-Usability

Duplication is better than wrong abstraction

Duplication is far cheaper than the wrong abstraction — Sandi Metz, RailsConf 2014
Sandi Metz, "The Wrong Abstraction"

Do not duplicate your Domain knowledge

YAGNI

You aren't gonna need it

Always implement things when you actually need them, never when you just foresee that you need them. — Ron Jeffries
Ron Jeffries (April 4, 1998). "You're NOT gonna need it!"
  • Re-Usable Code means generic
  • Your Domain is not generic
  • Code is harder to read, than to write
https://twitter.com/asgrim/status/871709231333986305

DRY vs. KISS

FizzBuzz

FizzBuzz

$ php fizzbuzz.php
1
2
Fizz // divisible by three
4
Buzz // divisible by five
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz // divisible by three AND five
16

                   

foreach(range(1,100) as $i) {
    if ($i % 3 == 0 && $i % 5 ==0) {
        echo "FizzBuzz\n";
    } else if($i % 3 == 0){
        echo "Fizz\n";
    } else if($i % 5 == 0){
        echo "Buzz\n";
    } else {
        echo $i."\n";
    }
}

Let's get rid of duplication!


foreach(range(1,100) as $i) {
  $val = ($i % 3 == 0 ? "Fizz" : "").($i % 5 == 0 ? "Buzz" : "");
  echo (empty($val) ? $i : $val)."\n";
}

FizzBuzz without Conditionals in PHP?


$fizzers = [1, 0, 0, 0];
$buzzers = [2, 0, 0, 0, 0];
$words = [null, 'Fizz', 'Buzz', 'FizzBuzz'];
foreach (range(1, 100) as $i) {
    $words[0] = $i;
    echo $words[$fizzers[$i % 3] + $buzzers[$i % 5]]."\n";
}

Code must be readable

Code is harder to read, than to write!

Thank you!

Please leave feedback

@benjamincremer
talks.benjamin-cremer.de/phpbnl18

PHP Usergroup Münster

meetup.com/phpugms
@phpugms