🐘 Enums in the Wild 🔎
The Good, the Bad and the Ugly …
Dana Luther
@danaluther@phpc.social
https://joind.in/talk/f13a4
https://www.linkedin.com/in/danaluther
Why an Enum?
• Constant
• Restricted value range / data conformity
• Type safety
• Readability
• Maintainability
• Organization
Key Benefits
PHP 8.1 - Enums are here!
• https://www.php.net/manual/en/language.types.enumerations.php
• https://www.php.net/manual/en/language.enumerations.examples.php
• https://stitcher.io/blog/php-enums
• https://php.watch/versions/8.1/enums
Version check…
• https://www.php.net/supported-versions.php
• Current minimum active version is 8.3
Sidebar:
The old ways
• The chaos method - string and int values sprinkled throughout the codebase
• The constant method - string and int constants assigned to classes where they are
used or referenced
• The makeshift enum - string and int constants to speci
fi
c classes that have been
created just for that purpose
The new way … Enums!
🙌
Best Practices
• Consistent case syntax
• BackedEnums only when necessary
• Limit methods
Sidebar:
When and how much do you convert?
When and how much do you convert?
• Beware of introducing breaking changes
When and how much do you convert?
• Beware of introducing breaking changes
• Add Enums and deprecate constants - don’t just go deleting things!
When and how much do you convert?
• Beware of introducing breaking changes
• Add Enums and deprecate constants - don’t just go deleting things!
• Add rector rules for conversion processes
https://getrector.com/
Sidebar:
composer require rector/rector:^2.0 —dev
composer rector
"scripts": {
"rector": [
"vendor/bin/rector"
]
}
The Good
What are we doing that’s really improving our code base?
The Bad
What are we doing that’s creating ine
ffi
ciencies?
The Ugly
What are we doing totally wrong?
Real World Examples
Example 1
f
i
nal class Events
{
/**
* Private constructor. This class cannot be instantiated.
*
/
private function
_
_
construct(){}
public const MODULE_INIT = 'module.init';
public const SUITE_INIT = 'suite.init';
public const SUITE_BEFORE = 'suite.before';
public const SUITE_AFTER = 'suite.after';
public const TEST_START = 'test.start';
public const TEST_BEFORE = 'test.before';
public const STEP_BEFORE = 'step.before';
public const STEP_AFTER = 'step.after';
public const TEST_FAIL = 'test.fail';
public const TEST_ERROR = 'test.error';
public const TEST_PARSED = 'test.parsed';
public const TEST_INCOMPLETE = 'test.incomplete';
public const TEST_SKIPPED = 'test.skipped';
public const TEST_WARNING = 'test.warning';
public const TEST_USELESS = 'test.useless';
public const TEST_SUCCESS = 'test.success';
public const TEST_AFTER = 'test.after';
public const TEST_END = 'test.end';
public const TEST_FAIL_PRINT = 'test.fail.print';
public const RESULT_PRINT_AFTER = 'result.print.after';
} https://github.com/Codeception/Codeception/blob/main/src/Codeception/Events.php
/**
* Contains all events dispatched by Codeception.
*
* @author tiger
-
seo <tiger.seo@gmail.com>
*
/
Example 1
f
i
nal class Events
{
/**
* Private constructor. This class cannot be instantiated.
*
/
private function
_
_
construct(){}
public const MODULE_INIT = 'module.init';
public const SUITE_INIT = 'suite.init';
public const SUITE_BEFORE = 'suite.before';
public const SUITE_AFTER = 'suite.after';
public const TEST_START = 'test.start';
public const TEST_BEFORE = 'test.before';
public const STEP_BEFORE = 'step.before';
public const STEP_AFTER = 'step.after';
public const TEST_FAIL = 'test.fail';
public const TEST_ERROR = 'test.error';
public const TEST_PARSED = 'test.parsed';
public const TEST_INCOMPLETE = 'test.incomplete';
public const TEST_SKIPPED = 'test.skipped';
public const TEST_WARNING = 'test.warning';
public const TEST_USELESS = 'test.useless';
public const TEST_SUCCESS = 'test.success';
public const TEST_AFTER = 'test.after';
public const TEST_END = 'test.end';
public const TEST_FAIL_PRINT = 'test.fail.print';
public const RESULT_PRINT_AFTER = 'result.print.after';
}
enum EventsEnum: string
{
case MODULE_INIT = 'module.init';
case SUITE_INIT = 'suite.init';
case SUITE_BEFORE = 'suite.before';
case SUITE_AFTER = 'suite.after';
case TEST_START = 'test.start';
case TEST_BEFORE = 'test.before';
case STEP_BEFORE = 'step.before';
case STEP_AFTER = 'step.after';
case TEST_FAIL = 'test.fail';
case TEST_ERROR = 'test.error';
case TEST_PARSED = 'test.parsed';
case TEST_INCOMPLETE = 'test.incomplete';
case TEST_SKIPPED = 'test.skipped';
case TEST_WARNING = 'test.warning';
case TEST_USELESS = 'test.useless';
case TEST_SUCCESS = 'test.success';
case TEST_AFTER = 'test.after';
case TEST_END = 'test.end';
case TEST_FAIL_PRINT = 'test.fail.print';
case RESULT_PRINT_AFTER = 'result.print.after';
}
Example 1
class Module implements EventSubscriberInterface
{
use SharedStaticEventsTrait;
/**
* @var array<string, string>
*
/
protected static array $events = [
Events
:
:
TEST_BEFORE
=
>
'before',
Events
:
:
TEST_AFTER
=
>
'after',
Events
:
:
STEP_BEFORE
=
>
'beforeStep',
Events
:
:
STEP_AFTER
=
>
'afterStep',
Events
:
:
TEST_FAIL
=
>
'failed',
Events
:
:
TEST_ERROR
=
>
'failed',
Events
:
:
SUITE_BEFORE
=
>
'beforeSuite',
Events
:
:
SUITE_AFTER
=
>
'afterSuite'
];
Example 1
class Module implements EventSubscriberInterface
{
use SharedStaticEventsTrait;
/**
* @var array<string, string>
*
/
protected static array $events = [
Events
:
:
TEST_BEFORE
=
>
'before',
Events
:
:
TEST_AFTER
=
>
'after',
Events
:
:
STEP_BEFORE
=
>
'beforeStep',
Events
:
:
STEP_AFTER
=
>
'afterStep',
Events
:
:
TEST_FAIL
=
>
'failed',
Events
:
:
TEST_ERROR
=
>
'failed',
Events
:
:
SUITE_BEFORE
=
>
'beforeSuite',
Events
:
:
SUITE_AFTER
=
>
'afterSuite'
];
class Module implements EventSubscriberInterface
{
use SharedStaticEventsTrait;
/**
* @var array<string, string>
*
/
protected static array $events = [
EventsEnum
:
:
TEST_BEFORE
-
>
value
=
>
'before',
EventsEnum
:
:
TEST_AFTER
-
>
value
=
>
'after',
EventsEnum
:
:
STEP_BEFORE
-
>
value
=
>
'beforeStep',
EventsEnum
:
:
STEP_AFTER
-
>
value
=
>
'afterStep',
EventsEnum
:
:
TEST_FAIL
-
>
value
=
>
'failed',
EventsEnum
:
:
TEST_ERROR
-
>
value
=
>
'failed',
EventsEnum
:
:
SUITE_BEFORE
-
>
value
=
>
'beforeSuite',
EventsEnum
:
:
SUITE_AFTER
-
>
value
=
>
'afterSuite'
];
class EventsClassConstToEnumRector extends AbstractRector
{
public function getRuleDef
i
nition()
:
RuleDef
i
nition
{
return new RuleDef
i
nition(
'Convert Events constants to the EventsEnum values', [
new CodeSample('Events
:
:
TEST_BEFORE', 'EventsEnum
:
:
TEST_BEFORE
-
>
value'),
]
);
}
public function getNodeTypes()
:
array
{
return [ClassConstFetch
:
:
class];
}
public function refactor(PhpParserNode $node)
{
if (!$this
-
>
isName($node
-
>
class, Events
:
:
class)) {
return null;
}
$node
-
>
class = new FullyQualif
i
ed(EventsEnum
:
:
class);
$node
-
>
name = new Identif
i
er($node
-
>
name . '
-
>
value');
return $node;
}
}
Work smarter,
not harder!!
public function getNodeTypes()
:
array
{
return [ClassConstFetch
:
:
class];
}
public function refactor(PhpParserNode $node)
{
if (!$this
-
>
isName($node
-
>
class, Events
:
:
class)) {
return null;
}
$node
-
>
class = new FullyQualif
i
ed(EventsEnum
:
:
class);
$node
-
>
name = new Identif
i
er($node
-
>
name . '
-
>
value');
return $node;
} Work smarter,
not harder!!
Example 2
/**
* …
* Class LocalServer
* @package CodeceptionCoverageSubscriber
*
/
class LocalServer extends SuiteSubscriber
{
/
/
headers
public const COVERAGE_HEADER = 'X-Codeception-CodeCoverage';
public const COVERAGE_HEADER_ERROR = 'X-Codeception-CodeCoverage-Error';
public const COVERAGE_HEADER_CONFIG = 'X-Codeception-CodeCoverage-Conf
i
g';
public const COVERAGE_HEADER_SUITE = 'X-Codeception-CodeCoverage-Suite';
/
/
cookie names
public const COVERAGE_COOKIE = 'CODECEPTION_CODECOVERAGE';
public const COVERAGE_COOKIE_ERROR = 'CODECEPTION_CODECOVERAGE_ERROR';
https://github.com/Codeception/Codeception/blob/main/src/Codeception/Coverage/Subscriber/LocalServer.php
Example 2
/
/
headers
enum CoverageHeader: string
{
case COVERAGE_HEADER = 'X-Codeception-CodeCoverage';
case COVERAGE_HEADER_ERROR = 'X-Codeception-CodeCoverage-Error';
case COVERAGE_HEADER_CONFIG = 'X-Codeception-CodeCoverage-Conf
i
g';
case COVERAGE_HEADER_SUITE = 'X-Codeception-CodeCoverage-Suite';
}
/
/
cookie names
enum CoverageCookie: string
{
case COVERAGE_COOKIE = 'CODECEPTION_CODECOVERAGE';
case COVERAGE_COOKIE_ERROR = 'CODECEPTION_CODECOVERAGE_ERROR';
}
Example 2
…
$this
-
>
addC3AccessHeader(self
:
:
COVERAGE_HEADER_CONFIG, $this
-
>
settings['remote_conf
i
g']);
…
protected function addC3AccessHeader(string $header, string $value)
:
void
{
$headerString = "{$header}
:
{$value}rn";
if (!str_contains((string) $this
-
>
c3Access['http']['header'], $headerString)) {
$this
-
>
c3Access['http']['header']
.
=
$headerString;
}
}
https://github.com/Codeception/Codeception/blob/main/src/Codeception/Coverage/Subscriber/LocalServer.php
Example 2
…
$this
-
>
addC3AccessHeader(self
:
:
COVERAGE_HEADER_CONFIG, $this
-
>
settings['remote_conf
i
g']);
…
protected function addC3AccessHeader(string $header, string $value)
:
void
{
$headerString = "{$header}
:
{$value}rn";
if (!str_contains((string) $this
-
>
c3Access['http']['header'], $headerString)) {
$this
-
>
c3Access['http']['header']
.
=
$headerString;
}
}
https://github.com/Codeception/Codeception/blob/main/src/Codeception/Coverage/Subscriber/LocalServer.php
…
$this
-
>
addC3AccessHeader(CoverageHeader
:
:
COVERAGE_HEADER_CONFIG, $this
-
>
settings['remote_conf
i
g']);
…
protected function addC3AccessHeader(CoverageHeader $header, string $value)
:
void
{
$headerString = “{$header
-
>
value}
:
{$value}rn";
if (!str_contains((string) $this
-
>
c3Access['http']['header'], $headerString)) {
$this
-
>
c3Access['http']['header']
.
=
$headerString;
}
}
Example 2
…
$this
-
>
addC3AccessHeader(self
:
:
COVERAGE_HEADER_CONFIG, $this
-
>
settings['remote_conf
i
g']);
…
protected function addC3AccessHeader(string $header, string $value)
:
void
{
$headerString = "{$header}
:
{$value}rn";
if (!str_contains((string) $this
-
>
c3Access['http']['header'], $headerString)) {
$this
-
>
c3Access['http']['header']
.
=
$headerString;
}
}
https://github.com/Codeception/Codeception/blob/main/src/Codeception/Coverage/Subscriber/LocalServer.php
protected function addC3AccessHeader(string|CoverageHeader $header, string $value)
:
void
{
if(!($header instanceof CoverageHeader)){
Logger
:
:
getLogger()
-
>
info(‘Calling addC3AccessHeader with string value’);
/
/
tombstone
$header = CoverageHeader
:
:
from($header)
}
$headerString = “{$header
-
>
value}
:
{$value}rn";
if (!str_contains((string) $this
-
>
c3Access['http']['header'], $headerString)) {
$this
-
>
c3Access['http']['header']
.
=
$headerString;
}
}
class LocalServerClassConstToEnumRector extends AbstractRector
{
/
/
Rule def
i
nition & ClassConstFetch node types
public function refactor(PhpParserNode $node)
{
if (!$this
-
>
isName($node
-
>
class, LocalServer
:
:
class)) {
return null;
}
$enumClass = match(true){
str_contains($node
-
>
name, ‘_HEADER’)
=
>
CoverageHeader
:
:
class,
str_contains($node
-
>
name, ‘_COOKIE’)
=
>
CoverageCookie
:
:
class,
default
=
>
throw new Exception(‘Unknown constant found’),
}
$node
-
>
class = new FullyQualif
i
ed($enumClass);
$node
-
>
name = new Identif
i
er($node
-
>
name . '
-
>
value');
return $node;
}
} Work smarter,
not harder!!
}
$enumClass = match(true){
str_contains($node
-
>
name, ‘_HEADER’)
=
>
C
:
:
str_contains($node
-
>
name, ‘_COOKIE’)
=
>
C
:
:
default
=
>
throw new Exception(‘Unknown c
}
$node
-
>
class = new FullyQualif
i
ed($enumClass
$node
-
>
name = new Identif
i
er($node
-
>
name . '
-
>
return $node;
}
Work smarter,
not harder!!
Example 2 - refined!
/
/
headers
enum CoverageHeader
{
case COVERAGE_HEADER;
case COVERAGE_HEADER_ERROR;
case COVERAGE_HEADER_CONFIG;
case COVERAGE_HEADER_SUITE;
}
/
/
cookie names
enum CoverageCookie
{
case COVERAGE_COOKIE;
case COVERAGE_COOKIE_ERROR;
}
Example 2 - refined!
protected function addC3AccessHeader(CoverageHeader $header, string $value)
:
void
{
$headerName = match($header){
CoverageHeader
:
:
COVERAGE_HEADER_ERROR
=
>
‘X-Codeception-CodeCoverage-Error',
CoverageHeader
:
:
COVERAGE_HEADER_CONFIG
=
>
‘X-Codeception-CodeCoverage-Conf
i
g',
CoverageHeader
:
:
COVERAGE_HEADER_SUITE
=
>
‘X-Codeception-CodeCoverage-Suite',
default
=
>
‘X-Codeception-CodeCoverage',
};
$headerString = “{$headerName}
:
{$value}rn";
if (!str_contains((string) $this
-
>
c3Access['http']['header'], $headerString)) {
$this
-
>
c3Access['http']['header'].
=
$headerString;
}
}
Switch vs Match
• https://www.php.net/manual/en/control-structures.match.php
• https://php.watch/versions/8.0/match-expression
Sidebar:
Example 2 - refined!
protected function addC3AccessHeader(CoverageHeader $header, string $value)
:
void
{
$headerName = match($header){
CoverageHeader
:
:
COVERAGE_HEADER_ERROR
=
>
‘X-Codeception-CodeCoverage-Error',
CoverageHeader
:
:
COVERAGE_HEADER_CONFIG
=
>
‘X-Codeception-CodeCoverage-Conf
i
g',
CoverageHeader
:
:
COVERAGE_HEADER_SUITE
=
>
‘X-Codeception-CodeCoverage-Suite',
default
=
>
‘X-Codeception-CodeCoverage',
};
$headerString = “{$headerName}
:
{$value}rn";
if (!str_contains((string) $this
-
>
c3Access['http']['header'], $headerString)) {
$this
-
>
c3Access['http']['header']
.
=
$headerString;
}
}
Example 2 - refined!
protected function addC3AccessHeader(CoverageHeader $header, string $value)
:
void
{
$headerName = match($header){
CoverageHeader
:
:
ERROR
=
>
‘X-Codeception-CodeCoverage-Error',
CoverageHeader
:
:
CONFIG
=
>
‘X-Codeception-CodeCoverage-Conf
i
g',
CoverageHeader
:
:
SUITE
=
>
‘X-Codeception-CodeCoverage-Suite’,
default =
>
‘X-Codeception-CodeCoverage',
};
$headerString = “{$headerName}
:
{$value}rn";
if (!str_contains((string) $this
-
>
c3Access['http']['header'], $headerString)) {
$this
-
>
c3Access['http']['header']
.
=
$headerString;
}
}
Example 2 - refined!
/
/
headers
enum CoverageHeader
{
case DEFAULT;
case ERROR;
case CONFIG;
case SUITE;
}
/
/
cookie names
enum CoverageCookie: string
{
case DEFAULT;
case ERROR;
}
class LocalServerClassConstToEnumRector extends AbstractRector
{
/
/
Rule def
i
nition & ClassConstFetch node types
public function refactor(PhpParserNode $node)
{
if (!$this
-
>
isName($node
-
>
class, LocalServer
:
:
class)) {
return null;
}
$enumClass = match(true){
str_contains($node
-
>
name, ‘_HEADER’)
=
>
CoverageHeader
:
:
class,
str_contains($node
-
>
name, ‘_COOKIE’)
=
>
CoverageCookie
:
:
class,
default
=
>
throw new Exception(‘Unknown constant found’),
}
$nodeName = trim(
str_replace([‘COVERAGE_HEADER’,’COVERAGE_COOKIE’, ‘_’], ‘’, $node
-
>
name)
) ?: ‘DEFAULT’;
$node
-
>
class = new FullyQualif
i
ed($enumClass);
$node
-
>
name = new Identif
i
er($nodeName . '
-
>
value');
return $node;
}
} Work smarter,
not harder!!
$enumClass = match(true){
str_contains($node
-
>
name, ‘_HEADER’)
=
>
Coverage
:
:
str_contains($node
-
>
name, ‘_COOKIE’)
=
>
Coverage
:
:
default
=
>
throw new Exception(‘Unknown constant
}
$nodeName = trim(
str_replace([‘COVERAGE_HEADER’,’COVERAGE_COOKIE’,
-
>
) ?: ‘DEFAULT’;
$node
-
>
class = new FullyQualif
i
ed($enumClass);
$node
-
>
name = new Identif
i
er($nodeName . '
-
>
value')
return $node;
}
Work smarter,
not harder!!
Example 3
trait EnumArrayTrait
{
public static function names()
:
array
{
if (method_exists(static
:
:
class, 'label')) {
return array_map(fn($c)
=
>
$c
-
>
label(), static
:
:
cases());
}
return array_column(static
:
:
cases(), 'name');
}
public static function values()
:
array
{
return array_column(static
:
:
cases(), 'value');
}
public static function options()
:
array
{
return array_combine(static
:
:
values(), static
:
:
names());
}
}
Example 3
enum Suit
{
use EnumArrayTrait;
case Diamonds;
case Hearts;
case Spades;
case Clubs;
public function label()
:
{
return match ($this) {
self
:
:
Diamonds
=
>
'♢',
self
:
:
Hearts
=
>
'♡',
self
:
:
Spades
=
>
'♤',
self
:
:
Clubs
=
>
'♧'
};
}
}
Example 3
enum Suit
{
case Diamonds;
case Hearts;
case Spades;
case Clubs;
}
class SuitFormatter
{
public static function asSuitIcon(Suit $suit)
:
string
{
return match ($suit) {
Suit
:
:
Diamonds
=
>
‘♢',
Suit
:
:
Hearts
=
>
‘♡',
Suit
:
:
Spades
=
>
'♤',
Suit
:
:
Clubs
=
>
‘♧'
};
}
public static function asSuitLabel(Suit $suit)
:
string
{
return match ($suit) {
Suit
:
:
Diamonds
=
>
‘Diamonds',
Suit
:
:
Hearts
=
>
‘Hearts',
Suit
:
:
Spades
=
>
'Spades',
Suit
:
:
Clubs
=
>
‘Clubs'
};
}
}
Example 4
enum AccountStatus: int
{
case Unknown = 0;
case Verif
i
ed = 1;
case Active = 2;
case Disabled = 3;
}
class StatusHelper
{
public static function asLabel(AccountStatus $status)
:
string
{
return match ($status) {
AccountStatus
:
:
Unknown
=
>
‘Unknown — Error‘,
AccountStatus
:
:
Active
=
>
‘Active User',
AccountStatus
:
:
Verif
i
ed
=
>
‘Verif
i
ed Account',
AccountStatus
:
:
Disabled
=
>
‘Account Disabled'
};
}
public static function accountStatusOptions()
:
array
{
$options = [];
foreach (AccountStatus
:
:
cases() as $status) {
$options[$status
-
>
value] = static
:
:
asLabel($status);
}
return $options;
}
}
PHP 8.4 Property Hooks
• https://www.php.net/manual/en/language.oop5.property-hooks.php
• https://stitcher.io/blog/new-in-php-84
Sidebar:
Example 4
enum AccountStatus: int
{
case Unknown = 0;
case Verif
i
ed = 1;
case Active = 2;
case Disabled = 3;
}
/**
* @property int $account_status_id
*
/
class SomeAccount extends yiidbActiveRecord
{
public AccountStatus $account_status {
get
=
>
AccountStatus
:
:
tryFrom((int)$this
-
>
account_status_id)
?
?
AccountStatus
:
:
Unknown;
set
=
>
$this
-
>
account_status_id = $value
-
>
value;
}
/
/
…
}
MySQL
• Compact data storage
• Readable queries and output
• Conformed data
Sidebar: https://dev.mysql.com/doc/refman/8.4/en/enum.html
Pros:
• String values only
• Potentially unintuitive ordering
• Empty/Null values
• Constrained at DB level (??)
Cons:
Example 4 alternate
enum AccountStatus: string
{
case Unknown = ‘unknown’;
case Verif
i
ed = ‘verif
i
ed’;
case Active = ‘active’;
case Disabled = ‘disabled’;
}
class SomeAccount extends yiidbActiveRecord
{
public AccountStatus $account_status = AccountStatus
:
:
Unknown {
get
=
>
AccountStatus
:
:
tryFrom((string)$this
-
>
status)
?
?
AccountStatus
:
:
Unknown;
set
=
>
$this
-
>
status = $value
-
>
value;
}
/
/
…
}
What other good/bad
examples have you seen/used?
Add Enum best practices to
your company style guide!
Questions
Dana Luther
@danaluther@phpc.social
https://joind.in/talk/f13a4
https://www.linkedin.com/in/danaluther
🤔
?
? ?
?

Enums In the Wild at PHP[tek] Conference 2025

  • 1.
    🐘 Enums inthe Wild 🔎 The Good, the Bad and the Ugly … Dana Luther @danaluther@phpc.social https://joind.in/talk/f13a4 https://www.linkedin.com/in/danaluther
  • 2.
    Why an Enum? •Constant • Restricted value range / data conformity • Type safety • Readability • Maintainability • Organization Key Benefits
  • 3.
    PHP 8.1 -Enums are here! • https://www.php.net/manual/en/language.types.enumerations.php • https://www.php.net/manual/en/language.enumerations.examples.php • https://stitcher.io/blog/php-enums • https://php.watch/versions/8.1/enums
  • 4.
    Version check… • https://www.php.net/supported-versions.php •Current minimum active version is 8.3 Sidebar:
  • 5.
    The old ways •The chaos method - string and int values sprinkled throughout the codebase • The constant method - string and int constants assigned to classes where they are used or referenced • The makeshift enum - string and int constants to speci fi c classes that have been created just for that purpose
  • 6.
    The new way… Enums! 🙌
  • 7.
    Best Practices • Consistentcase syntax • BackedEnums only when necessary • Limit methods Sidebar:
  • 8.
    When and howmuch do you convert?
  • 9.
    When and howmuch do you convert? • Beware of introducing breaking changes
  • 10.
    When and howmuch do you convert? • Beware of introducing breaking changes • Add Enums and deprecate constants - don’t just go deleting things!
  • 11.
    When and howmuch do you convert? • Beware of introducing breaking changes • Add Enums and deprecate constants - don’t just go deleting things! • Add rector rules for conversion processes
  • 12.
    https://getrector.com/ Sidebar: composer require rector/rector:^2.0—dev composer rector "scripts": { "rector": [ "vendor/bin/rector" ] }
  • 13.
    The Good What arewe doing that’s really improving our code base?
  • 14.
    The Bad What arewe doing that’s creating ine ffi ciencies?
  • 15.
    The Ugly What arewe doing totally wrong?
  • 16.
  • 17.
    Example 1 f i nal classEvents { /** * Private constructor. This class cannot be instantiated. * / private function _ _ construct(){} public const MODULE_INIT = 'module.init'; public const SUITE_INIT = 'suite.init'; public const SUITE_BEFORE = 'suite.before'; public const SUITE_AFTER = 'suite.after'; public const TEST_START = 'test.start'; public const TEST_BEFORE = 'test.before'; public const STEP_BEFORE = 'step.before'; public const STEP_AFTER = 'step.after'; public const TEST_FAIL = 'test.fail'; public const TEST_ERROR = 'test.error'; public const TEST_PARSED = 'test.parsed'; public const TEST_INCOMPLETE = 'test.incomplete'; public const TEST_SKIPPED = 'test.skipped'; public const TEST_WARNING = 'test.warning'; public const TEST_USELESS = 'test.useless'; public const TEST_SUCCESS = 'test.success'; public const TEST_AFTER = 'test.after'; public const TEST_END = 'test.end'; public const TEST_FAIL_PRINT = 'test.fail.print'; public const RESULT_PRINT_AFTER = 'result.print.after'; } https://github.com/Codeception/Codeception/blob/main/src/Codeception/Events.php /** * Contains all events dispatched by Codeception. * * @author tiger - seo <tiger.seo@gmail.com> * /
  • 18.
    Example 1 f i nal classEvents { /** * Private constructor. This class cannot be instantiated. * / private function _ _ construct(){} public const MODULE_INIT = 'module.init'; public const SUITE_INIT = 'suite.init'; public const SUITE_BEFORE = 'suite.before'; public const SUITE_AFTER = 'suite.after'; public const TEST_START = 'test.start'; public const TEST_BEFORE = 'test.before'; public const STEP_BEFORE = 'step.before'; public const STEP_AFTER = 'step.after'; public const TEST_FAIL = 'test.fail'; public const TEST_ERROR = 'test.error'; public const TEST_PARSED = 'test.parsed'; public const TEST_INCOMPLETE = 'test.incomplete'; public const TEST_SKIPPED = 'test.skipped'; public const TEST_WARNING = 'test.warning'; public const TEST_USELESS = 'test.useless'; public const TEST_SUCCESS = 'test.success'; public const TEST_AFTER = 'test.after'; public const TEST_END = 'test.end'; public const TEST_FAIL_PRINT = 'test.fail.print'; public const RESULT_PRINT_AFTER = 'result.print.after'; } enum EventsEnum: string { case MODULE_INIT = 'module.init'; case SUITE_INIT = 'suite.init'; case SUITE_BEFORE = 'suite.before'; case SUITE_AFTER = 'suite.after'; case TEST_START = 'test.start'; case TEST_BEFORE = 'test.before'; case STEP_BEFORE = 'step.before'; case STEP_AFTER = 'step.after'; case TEST_FAIL = 'test.fail'; case TEST_ERROR = 'test.error'; case TEST_PARSED = 'test.parsed'; case TEST_INCOMPLETE = 'test.incomplete'; case TEST_SKIPPED = 'test.skipped'; case TEST_WARNING = 'test.warning'; case TEST_USELESS = 'test.useless'; case TEST_SUCCESS = 'test.success'; case TEST_AFTER = 'test.after'; case TEST_END = 'test.end'; case TEST_FAIL_PRINT = 'test.fail.print'; case RESULT_PRINT_AFTER = 'result.print.after'; }
  • 19.
    Example 1 class Moduleimplements EventSubscriberInterface { use SharedStaticEventsTrait; /** * @var array<string, string> * / protected static array $events = [ Events : : TEST_BEFORE = > 'before', Events : : TEST_AFTER = > 'after', Events : : STEP_BEFORE = > 'beforeStep', Events : : STEP_AFTER = > 'afterStep', Events : : TEST_FAIL = > 'failed', Events : : TEST_ERROR = > 'failed', Events : : SUITE_BEFORE = > 'beforeSuite', Events : : SUITE_AFTER = > 'afterSuite' ];
  • 20.
    Example 1 class Moduleimplements EventSubscriberInterface { use SharedStaticEventsTrait; /** * @var array<string, string> * / protected static array $events = [ Events : : TEST_BEFORE = > 'before', Events : : TEST_AFTER = > 'after', Events : : STEP_BEFORE = > 'beforeStep', Events : : STEP_AFTER = > 'afterStep', Events : : TEST_FAIL = > 'failed', Events : : TEST_ERROR = > 'failed', Events : : SUITE_BEFORE = > 'beforeSuite', Events : : SUITE_AFTER = > 'afterSuite' ]; class Module implements EventSubscriberInterface { use SharedStaticEventsTrait; /** * @var array<string, string> * / protected static array $events = [ EventsEnum : : TEST_BEFORE - > value = > 'before', EventsEnum : : TEST_AFTER - > value = > 'after', EventsEnum : : STEP_BEFORE - > value = > 'beforeStep', EventsEnum : : STEP_AFTER - > value = > 'afterStep', EventsEnum : : TEST_FAIL - > value = > 'failed', EventsEnum : : TEST_ERROR - > value = > 'failed', EventsEnum : : SUITE_BEFORE - > value = > 'beforeSuite', EventsEnum : : SUITE_AFTER - > value = > 'afterSuite' ];
  • 21.
    class EventsClassConstToEnumRector extendsAbstractRector { public function getRuleDef i nition() : RuleDef i nition { return new RuleDef i nition( 'Convert Events constants to the EventsEnum values', [ new CodeSample('Events : : TEST_BEFORE', 'EventsEnum : : TEST_BEFORE - > value'), ] ); } public function getNodeTypes() : array { return [ClassConstFetch : : class]; } public function refactor(PhpParserNode $node) { if (!$this - > isName($node - > class, Events : : class)) { return null; } $node - > class = new FullyQualif i ed(EventsEnum : : class); $node - > name = new Identif i er($node - > name . ' - > value'); return $node; } } Work smarter, not harder!!
  • 22.
    public function getNodeTypes() : array { return[ClassConstFetch : : class]; } public function refactor(PhpParserNode $node) { if (!$this - > isName($node - > class, Events : : class)) { return null; } $node - > class = new FullyQualif i ed(EventsEnum : : class); $node - > name = new Identif i er($node - > name . ' - > value'); return $node; } Work smarter, not harder!!
  • 23.
    Example 2 /** * … *Class LocalServer * @package CodeceptionCoverageSubscriber * / class LocalServer extends SuiteSubscriber { / / headers public const COVERAGE_HEADER = 'X-Codeception-CodeCoverage'; public const COVERAGE_HEADER_ERROR = 'X-Codeception-CodeCoverage-Error'; public const COVERAGE_HEADER_CONFIG = 'X-Codeception-CodeCoverage-Conf i g'; public const COVERAGE_HEADER_SUITE = 'X-Codeception-CodeCoverage-Suite'; / / cookie names public const COVERAGE_COOKIE = 'CODECEPTION_CODECOVERAGE'; public const COVERAGE_COOKIE_ERROR = 'CODECEPTION_CODECOVERAGE_ERROR'; https://github.com/Codeception/Codeception/blob/main/src/Codeception/Coverage/Subscriber/LocalServer.php
  • 24.
    Example 2 / / headers enum CoverageHeader:string { case COVERAGE_HEADER = 'X-Codeception-CodeCoverage'; case COVERAGE_HEADER_ERROR = 'X-Codeception-CodeCoverage-Error'; case COVERAGE_HEADER_CONFIG = 'X-Codeception-CodeCoverage-Conf i g'; case COVERAGE_HEADER_SUITE = 'X-Codeception-CodeCoverage-Suite'; } / / cookie names enum CoverageCookie: string { case COVERAGE_COOKIE = 'CODECEPTION_CODECOVERAGE'; case COVERAGE_COOKIE_ERROR = 'CODECEPTION_CODECOVERAGE_ERROR'; }
  • 25.
    Example 2 … $this - > addC3AccessHeader(self : : COVERAGE_HEADER_CONFIG, $this - > settings['remote_conf i g']); … protectedfunction addC3AccessHeader(string $header, string $value) : void { $headerString = "{$header} : {$value}rn"; if (!str_contains((string) $this - > c3Access['http']['header'], $headerString)) { $this - > c3Access['http']['header'] . = $headerString; } } https://github.com/Codeception/Codeception/blob/main/src/Codeception/Coverage/Subscriber/LocalServer.php
  • 26.
    Example 2 … $this - > addC3AccessHeader(self : : COVERAGE_HEADER_CONFIG, $this - > settings['remote_conf i g']); … protectedfunction addC3AccessHeader(string $header, string $value) : void { $headerString = "{$header} : {$value}rn"; if (!str_contains((string) $this - > c3Access['http']['header'], $headerString)) { $this - > c3Access['http']['header'] . = $headerString; } } https://github.com/Codeception/Codeception/blob/main/src/Codeception/Coverage/Subscriber/LocalServer.php … $this - > addC3AccessHeader(CoverageHeader : : COVERAGE_HEADER_CONFIG, $this - > settings['remote_conf i g']); … protected function addC3AccessHeader(CoverageHeader $header, string $value) : void { $headerString = “{$header - > value} : {$value}rn"; if (!str_contains((string) $this - > c3Access['http']['header'], $headerString)) { $this - > c3Access['http']['header'] . = $headerString; } }
  • 27.
    Example 2 … $this - > addC3AccessHeader(self : : COVERAGE_HEADER_CONFIG, $this - > settings['remote_conf i g']); … protectedfunction addC3AccessHeader(string $header, string $value) : void { $headerString = "{$header} : {$value}rn"; if (!str_contains((string) $this - > c3Access['http']['header'], $headerString)) { $this - > c3Access['http']['header'] . = $headerString; } } https://github.com/Codeception/Codeception/blob/main/src/Codeception/Coverage/Subscriber/LocalServer.php protected function addC3AccessHeader(string|CoverageHeader $header, string $value) : void { if(!($header instanceof CoverageHeader)){ Logger : : getLogger() - > info(‘Calling addC3AccessHeader with string value’); / / tombstone $header = CoverageHeader : : from($header) } $headerString = “{$header - > value} : {$value}rn"; if (!str_contains((string) $this - > c3Access['http']['header'], $headerString)) { $this - > c3Access['http']['header'] . = $headerString; } }
  • 28.
    class LocalServerClassConstToEnumRector extendsAbstractRector { / / Rule def i nition & ClassConstFetch node types public function refactor(PhpParserNode $node) { if (!$this - > isName($node - > class, LocalServer : : class)) { return null; } $enumClass = match(true){ str_contains($node - > name, ‘_HEADER’) = > CoverageHeader : : class, str_contains($node - > name, ‘_COOKIE’) = > CoverageCookie : : class, default = > throw new Exception(‘Unknown constant found’), } $node - > class = new FullyQualif i ed($enumClass); $node - > name = new Identif i er($node - > name . ' - > value'); return $node; } } Work smarter, not harder!!
  • 29.
    } $enumClass = match(true){ str_contains($node - > name,‘_HEADER’) = > C : : str_contains($node - > name, ‘_COOKIE’) = > C : : default = > throw new Exception(‘Unknown c } $node - > class = new FullyQualif i ed($enumClass $node - > name = new Identif i er($node - > name . ' - > return $node; } Work smarter, not harder!!
  • 30.
    Example 2 -refined! / / headers enum CoverageHeader { case COVERAGE_HEADER; case COVERAGE_HEADER_ERROR; case COVERAGE_HEADER_CONFIG; case COVERAGE_HEADER_SUITE; } / / cookie names enum CoverageCookie { case COVERAGE_COOKIE; case COVERAGE_COOKIE_ERROR; }
  • 31.
    Example 2 -refined! protected function addC3AccessHeader(CoverageHeader $header, string $value) : void { $headerName = match($header){ CoverageHeader : : COVERAGE_HEADER_ERROR = > ‘X-Codeception-CodeCoverage-Error', CoverageHeader : : COVERAGE_HEADER_CONFIG = > ‘X-Codeception-CodeCoverage-Conf i g', CoverageHeader : : COVERAGE_HEADER_SUITE = > ‘X-Codeception-CodeCoverage-Suite', default = > ‘X-Codeception-CodeCoverage', }; $headerString = “{$headerName} : {$value}rn"; if (!str_contains((string) $this - > c3Access['http']['header'], $headerString)) { $this - > c3Access['http']['header']. = $headerString; } }
  • 32.
    Switch vs Match •https://www.php.net/manual/en/control-structures.match.php • https://php.watch/versions/8.0/match-expression Sidebar:
  • 33.
    Example 2 -refined! protected function addC3AccessHeader(CoverageHeader $header, string $value) : void { $headerName = match($header){ CoverageHeader : : COVERAGE_HEADER_ERROR = > ‘X-Codeception-CodeCoverage-Error', CoverageHeader : : COVERAGE_HEADER_CONFIG = > ‘X-Codeception-CodeCoverage-Conf i g', CoverageHeader : : COVERAGE_HEADER_SUITE = > ‘X-Codeception-CodeCoverage-Suite', default = > ‘X-Codeception-CodeCoverage', }; $headerString = “{$headerName} : {$value}rn"; if (!str_contains((string) $this - > c3Access['http']['header'], $headerString)) { $this - > c3Access['http']['header'] . = $headerString; } }
  • 34.
    Example 2 -refined! protected function addC3AccessHeader(CoverageHeader $header, string $value) : void { $headerName = match($header){ CoverageHeader : : ERROR = > ‘X-Codeception-CodeCoverage-Error', CoverageHeader : : CONFIG = > ‘X-Codeception-CodeCoverage-Conf i g', CoverageHeader : : SUITE = > ‘X-Codeception-CodeCoverage-Suite’, default = > ‘X-Codeception-CodeCoverage', }; $headerString = “{$headerName} : {$value}rn"; if (!str_contains((string) $this - > c3Access['http']['header'], $headerString)) { $this - > c3Access['http']['header'] . = $headerString; } }
  • 35.
    Example 2 -refined! / / headers enum CoverageHeader { case DEFAULT; case ERROR; case CONFIG; case SUITE; } / / cookie names enum CoverageCookie: string { case DEFAULT; case ERROR; }
  • 36.
    class LocalServerClassConstToEnumRector extendsAbstractRector { / / Rule def i nition & ClassConstFetch node types public function refactor(PhpParserNode $node) { if (!$this - > isName($node - > class, LocalServer : : class)) { return null; } $enumClass = match(true){ str_contains($node - > name, ‘_HEADER’) = > CoverageHeader : : class, str_contains($node - > name, ‘_COOKIE’) = > CoverageCookie : : class, default = > throw new Exception(‘Unknown constant found’), } $nodeName = trim( str_replace([‘COVERAGE_HEADER’,’COVERAGE_COOKIE’, ‘_’], ‘’, $node - > name) ) ?: ‘DEFAULT’; $node - > class = new FullyQualif i ed($enumClass); $node - > name = new Identif i er($nodeName . ' - > value'); return $node; } } Work smarter, not harder!!
  • 37.
    $enumClass = match(true){ str_contains($node - > name,‘_HEADER’) = > Coverage : : str_contains($node - > name, ‘_COOKIE’) = > Coverage : : default = > throw new Exception(‘Unknown constant } $nodeName = trim( str_replace([‘COVERAGE_HEADER’,’COVERAGE_COOKIE’, - > ) ?: ‘DEFAULT’; $node - > class = new FullyQualif i ed($enumClass); $node - > name = new Identif i er($nodeName . ' - > value') return $node; } Work smarter, not harder!!
  • 38.
    Example 3 trait EnumArrayTrait { publicstatic function names() : array { if (method_exists(static : : class, 'label')) { return array_map(fn($c) = > $c - > label(), static : : cases()); } return array_column(static : : cases(), 'name'); } public static function values() : array { return array_column(static : : cases(), 'value'); } public static function options() : array { return array_combine(static : : values(), static : : names()); } }
  • 39.
    Example 3 enum Suit { useEnumArrayTrait; case Diamonds; case Hearts; case Spades; case Clubs; public function label() : { return match ($this) { self : : Diamonds = > '♢', self : : Hearts = > '♡', self : : Spades = > '♤', self : : Clubs = > '♧' }; } }
  • 40.
    Example 3 enum Suit { caseDiamonds; case Hearts; case Spades; case Clubs; } class SuitFormatter { public static function asSuitIcon(Suit $suit) : string { return match ($suit) { Suit : : Diamonds = > ‘♢', Suit : : Hearts = > ‘♡', Suit : : Spades = > '♤', Suit : : Clubs = > ‘♧' }; } public static function asSuitLabel(Suit $suit) : string { return match ($suit) { Suit : : Diamonds = > ‘Diamonds', Suit : : Hearts = > ‘Hearts', Suit : : Spades = > 'Spades', Suit : : Clubs = > ‘Clubs' }; } }
  • 41.
    Example 4 enum AccountStatus:int { case Unknown = 0; case Verif i ed = 1; case Active = 2; case Disabled = 3; } class StatusHelper { public static function asLabel(AccountStatus $status) : string { return match ($status) { AccountStatus : : Unknown = > ‘Unknown — Error‘, AccountStatus : : Active = > ‘Active User', AccountStatus : : Verif i ed = > ‘Verif i ed Account', AccountStatus : : Disabled = > ‘Account Disabled' }; } public static function accountStatusOptions() : array { $options = []; foreach (AccountStatus : : cases() as $status) { $options[$status - > value] = static : : asLabel($status); } return $options; } }
  • 42.
    PHP 8.4 PropertyHooks • https://www.php.net/manual/en/language.oop5.property-hooks.php • https://stitcher.io/blog/new-in-php-84 Sidebar:
  • 43.
    Example 4 enum AccountStatus:int { case Unknown = 0; case Verif i ed = 1; case Active = 2; case Disabled = 3; } /** * @property int $account_status_id * / class SomeAccount extends yiidbActiveRecord { public AccountStatus $account_status { get = > AccountStatus : : tryFrom((int)$this - > account_status_id) ? ? AccountStatus : : Unknown; set = > $this - > account_status_id = $value - > value; } / / … }
  • 44.
    MySQL • Compact datastorage • Readable queries and output • Conformed data Sidebar: https://dev.mysql.com/doc/refman/8.4/en/enum.html Pros: • String values only • Potentially unintuitive ordering • Empty/Null values • Constrained at DB level (??) Cons:
  • 45.
    Example 4 alternate enumAccountStatus: string { case Unknown = ‘unknown’; case Verif i ed = ‘verif i ed’; case Active = ‘active’; case Disabled = ‘disabled’; } class SomeAccount extends yiidbActiveRecord { public AccountStatus $account_status = AccountStatus : : Unknown { get = > AccountStatus : : tryFrom((string)$this - > status) ? ? AccountStatus : : Unknown; set = > $this - > status = $value - > value; } / / … }
  • 46.
    What other good/bad exampleshave you seen/used?
  • 47.
    Add Enum bestpractices to your company style guide!
  • 49.