Reduce boilerplate code

Carsten Windler

Head of Engineering @ KW-Commerce GmbH

<?php

/**
 * A Sample DTO
 * @package ...
 */ 
class SampleDto
{
    /**
     * @var int
     */
    private $property1;

    /**
     * @return int
     */
    public function getProperty1()
    {
        return $this->property1;
    }

    /**
     * @param int $property1
     * @return SampleDto
     * @throws \Exception
     */
    public function setProperty1($property1)
    {
        if (!is_int($property1)) {
            throw new \Exception("property1 must be of type integer");
        }

        $this->property1 = $property1;

        return $this;
    }

    /**
     * SampleDto constructor.
     * @param int $property1

     * @throws \Exception
     */
    public function __construct($property1)
    {
        $this->setProperty1($property1);
    }
}

Boilerplate code?

    /**
     * @var int
     */
    private $property1;

    /**
     * @return int
     */
    public function getProperty1()
    {
        return $this->property1;
    }

    /**
     * @param int $property1
     * @return SampleDto
     * @throws Exception
     */
    public function setProperty1($property1)
    {
        if (!is_int($property1)) {
            throw new \Exception("property1 must be of type integer");
        }

        $this->property1 = $property1;

        return $this;
    }

Docblocks

  • Type hinting
  • Additional information
/**
 * @param $property1
 */
public function setProperty1($property1) { 
    $this->property1 = $property1;
}

Docblocks gone wrong

Docblocks gone wrong

/**
 * @var int
 */
private $property1; 

/**
 * Set $property2
 * 
 * @param string $property1
 */
public function setProperty1($property1) { 
    $this->property1 = $property1;
}

Useless Docblocks

class SampleDto
{
    /**
     * @var int
     */
    private int $property1; 

    /**
     * @param int $property1
     */
    public function setProperty1(int $property1): SampleDto { 
        $this->property1 = $property1;
    
        return $this;
    }
}
class SampleDto
{
    private int $property1; 

    public function setProperty1(int $property1): SampleDto { 
        $this->property1 = $property1;
    
        return $this;
    }
}

Redundant comments are just places to collect lies and misinformation.

Robert C. Martin

Avoid dockblocks, if they add no information.

Valuable information might be:

  • Thrown exceptions
  • Deprecated functions
  • ORM/Framework

Attributes

[ Link ]

/**
 * @Route(path="/archive", name="blog_archive")
 */
public function blogArchive(): Response 
{
    // ....
}
#[Route(path: '/archive', name: 'blog_archive')]
public function blogArchive(): Response 
{
    // ....
}

Attributes

#[Route(path: '/archive', name: RouteName::BLOG_ARCHIVE)]
public function blogArchive(): Response 
{
    // ....
}
    /**
     * @var int
     */
    private $property1;

    /**
     * @return int
     */
    public function getProperty1()
    {
        return $this->property1;
    }

    /**
     * @param int $property1
     * @return SampleDto
     * @throws Exception
     */
    public function setProperty1($property1)
    {
        if (!is_int($property1)) {
            throw new \Exception("property1 must be of type integer");
        }

        $this->property1 = $property1;
    }

Getters & Setters

  • "Type safety"
  • Readonly properties
class SampleDto
{
    private int $property1;

    public function getProperty1(): int
    {
        return $this->property1;
    }

    public function setProperty1(int $property1): void
    {
        $this->property1 = $property1;
    }
 }

Getters & Setters

class SampleDto
{
    public int $property1;
}

No Getters & Setters

$myDto = new SampleDto;

$myDto->property1 = 5;

echo $myDto->property1 * 10;

Could Getters & Setters still be useful?

    private int $timestamp;

    public function getTimestamp(): int
    {
        return $this->timestamp;
    }

    public function getAsDate(): string
    {
        return date(DATE_RFC2822, $this->timestamp);
    }

Casting/Formatting

of properties

class MyImmutable
{
    private int $someValue;

    public function __construct(int $someValue)
    {
        $this->someValue = $someValue;
    }

    public function getSomeValue(): int
    {
        return $this->someValue;
    }
}

Immutable objects

readonly properties

class MyImmutable
{
    public readonly int $someValue;

    public function __construct(int $someValue)
    {
        $this->someValue = $someValue;
    }
}
$immutable = new MyImmutable(10);
echo $immutable->someValue;
$immutable->someValue = 20;
Fatal error: Uncaught Error: Cannot modify 
readonly property MyImmutable::$someValue in ....
class SampleDto
{
    public int $property1;

    public function __construct(int $property1)
    {
        $this->property1 = $property1;
    }
}

Let's have another look

Constructor property promotion

class SampleDto
{
    public int $property1;

    public function __construct(int $property1)
    {
        $this->property1 = $property1;
    }
}
class SampleDto
{
    public function __construct(
        public int $property1
    ) {}
}

"new" in initializers

class MyService {
    private Logger $logger;

    public function __construct(
        ?Logger $logger = null,
    ) {
        $this->logger = $logger ?? new NullLogger;
    }
}
class MyService {
    public function __construct(
        private ?Logger $logger = new NullLogger(),
    ) {}
}

[ Link ]

...and in combination

class MyService {
    private readonly Logger $logger;

    public function __construct(
        ?Logger $logger = null,
    ) {
        $this->logger = $logger ?? new NullLogger;
    }
}
class MyService {
    public function __construct(
        private readonly Logger $logger = new NullLogger(),
    ) {}
}

Yeah 😎 

switch ($var) {
    case 'a':
        $message = "The variable was a.";
        break;
    case 'b':
        $message = "The variable was b.";
        break;
    case 'c':
        $message = "The variable was c.";
        break;
    default:
        $message = "The variable was something else.";
        break;
}
$message = match($var) {
    'a' => 'The variable was a',
    'b' => 'The variable was b',
    'c' => 'The variable was c',
    default => 'The variable was something else',
};

match() expression

[ Link ]

switch ($var) {
    case 'a':
        (function () { /* ... */ })();
        break;
    case 'b':
        someFunction();
        break;
}

match() expression

match($var) {
    'a' => function () { /* ... */ },
    'b' => someFunction(),
};
$user = get_user($id);
if (!is_null($user)) {
    $address = $user->getAddress();
    if (!is_null($address)) {
        $state = $address->state;
        If (!is_null($state)) {
            echo $state;
        }
    }
}

nullsafe operator

echo get_user($id)
    ?->getAddress()
    ?->state;
echo $result = $object1
    ?->object2
    ?->object3
    ?->property1;

[ Link ]

$url = new Url(
   'https', null, null, 'mydomain.com', 443, '/somepath', null, 'latest'
);

named arguments

[ Link ]

class Url implements UrlInterface
{
    public function __construct(
        private string $scheme = 'https',
        private ?string $authority = null,
        private ?string $userInfo = null,
        private string $host = '',
        private ?int $port = 443,
        private string $path = '',
        private ?string $query = null,
        private ?string $fragment = null,
    ) {}
}
$url = new Url(
    host: 'mydomain.com',
    path: '/somepath',
    fragment: 'latest'
);

Recap

  • You might not need Docblocks anymore
  • You might not need Getters & Setters anymore
  • Consider constructor property promotion
  • We got readonly properties now
  • New features:
    • Nullsafe operator
    • Match expression
    • Named arguments

Coding example

Now let's use the old, ugly class, and apply what we just discussed

Thank you!

carsten.windler@kw-commerce.de

Reduce boilerplate code

By Carsten Windler

Reduce boilerplate code

  • 707