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
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;
$url = new Url(
'https', null, null, 'mydomain.com', 443, '/somepath', null, 'latest'
);
named arguments
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