Skip to content

Commit

Permalink
NEXT-24264 - Add ADR for new PHP 8.1 & Symfony 6.1 features
Browse files Browse the repository at this point in the history
  • Loading branch information
AydinHassan committed Jun 30, 2023
1 parent d58f8b9 commit 373793c
Show file tree
Hide file tree
Showing 3 changed files with 778 additions and 0 deletions.
155 changes: 155 additions & 0 deletions adr/2023-05-16-php-enums.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
---
title: Use PHP 8.1 Enums
date: 2023-05-16
area: core
tags: [php, '8.1', enum]
---

## Context

As of Shopware 6.5 the minimum version of PHP is 8.1. We would like to promote the usage of PHP Enums.

Enums are useful where we have a predefined list of constant values. It's now not necessary to provide values as constants, and it's not necessary to create arrays of the constants to check validity.

## Decision

All new code which needs to represent a collection of constant values should now use Enums.

A few examples might be:

* Product Types (Parent, Variant)
* Product Status (Enabled, Disabled)
* Backup Type (Full, Incremental)

Where possible, we should migrate existing constant lists to use Enums. See the following Migration Strategy:

## Backwards Compatibility / Migration Strategy

To migrate a list of constant values, where an API accepts a "type" parameter which should exist in the list of constant values we can use the [Expand & Contract pattern](https://www.tim-wellhausen.de/papers/ExpandAndContract/ExpandAndContract.html) to migrate in a backwards compatible manner:

Consider the following example:

```php
class Indexer
{
public const PARTIAL = 'partial';
public const FULL = 'full';

public function product(int $id, string $method): void
{
if (!in_array($method, [self::PARTIAL, self::FULL], true)) {
throw new \InvalidArgumentException();
}

match ($method) {
self::PARTIAL => $this->partial($id),
self::FULL => $this->full($id)
};
}
}
```

Step 1: Create the ENUM & Accept in API as well as string. For this step it is necessary to maintain the allowed values in both the constants and the ENUM:

Note: In PHP 8.1 we cannot assign directly an ENUM to a constant. For the future, it is worth noting that this is supported in PHP 8.2 with backed ENUMS: `public const PARTIAL = IndexMethod::PARTIAL->value;`

```php
enum IndexMethod
{
case PARTIAL;
case FULL;
}

class Indexer
{
public function product(int $id, IndexMethod|string $method): void
{
...
}
}
```

Step 2: Create ENUM from primitive type if string value passed:

Note: If your ENUM is backed with a value you can use `BackedEnum::from` to perform automatic casting and validation. Otherwise you will need to map the values manually.

```php
class Indexer
{
public const PARTIAL = 'partial';
public const FULL = 'full';

public function product(int $id, IndexMethod|string $method): void
{
if (is_string($method)) {
$method = match ($method) {
'partial' => IndexMethod::PARTIAL,
'full' => IndexMethod::FULL,
default => throw new \InvalidArgumentException()
};
}

match ($method) {
IndexMethod::PARTIAL => $this->partial($id),
IndexMethod::FULL => $this->full($id)
};
}
}
```

Step 3: Deprecate the constants and passing primitive values in the method:

```php
class Indexer
{
// @deprecated tag:v6.6.0 - Constant will be removed, use enum IndexMethod::PARTIAL
public const PARTIAL = 'partial';
// @deprecated tag:v6.6.0 - Constant will be removed, use enum IndexMethod::FULL
public const FULL = 'full';

/**
* @deprecated tag:v6.6.0 - Parameter $method will not accept a primitive in v6.6.0
*/
public function product(int $id, IndexMethod|string $method): void
{
if (is_string($method)) {
$method = match ($method) {
'partial' => IndexMethod::PARTIAL,
'full' => IndexMethod::FULL,
default => throw new \InvalidArgumentException()
};
}

match ($method) {
IndexMethod::PARTIAL => $this->partial($id),
IndexMethod::FULL => $this->full($id)
};
}
}
```

Step 4: Remove deprecations in next major.


Which leaves us with the following, succinct code:

```php
enum IndexMethod
{
case PARTIAL;
case FULL;
}

class Indexer
{
public function product(int $id, IndexMethod $method): void
{
match ($method) {
IndexMethod::PARTIAL => $this->partial($id),
IndexMethod::FULL => $this->full($id)
};
}
}

(new Indexer())->product(1, IndexMethod::PARTIAL);
```
45 changes: 45 additions & 0 deletions adr/2023-05-16-symfony-dependency-management.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
title: Symfony Dependency Management
date: 2023-05-16
area: core
tags: [php, symfony, dependency]
---

## Context

The process of configuring dependencies has been upgraded with various new features in recent versions of Symfony.

We would like to utilise new features such as:

* [Autowiring](https://symfony.com/doc/current/service_container.html)
* [PHP configuration](https://symfony.com/doc/current/service_container/import.html)
* [Attributes for autowiring](https://symfony.com/blog/new-in-symfony-6-1-service-autowiring-attributes)

## Decision

1. Autowiring will be enabled
2. Support will be added to load service configuration from PHP files (as well as XML for backwards compatibility)
3. Where services need a particular non default service, for example a different implementation, or scalar values, we can use attributes.

Note: Attributes should only be used in framework glue code, for example in Controllers and commands. We do not want to couple our domain code too close to Symfony.

With Aurowiring enabled we can greatly reduce the amount of configuration in the XML files since most of the configuration is unnecessary. Most dependency graphs can be automatically resolved by Symfony using type hints.
There are no runtime performance implications because the container with its definitions are compiled.

Advantages:

* Less code to maintain.
* Better autocompletion with PHP.
* More modern approach

## Backwards Compatibility / Migration Strategy

To migrate our current XML dependency configurations we can follow the below steps:

Step 1: Add support for loading service definitions from PHP files as well as XML files.

Step 2: Enable autowiring. Symfony should prefer the registered configuration before trying to autowire. In other words, Symfony will only autowire classes without configured definitions.

Step 3: Delete definitions which are not required because they can be autoloaded.

Step 4: Migrate any existing definitions to the PHP configurations or Attributes.
Loading

0 comments on commit 373793c

Please sign in to comment.