forked from shopware/shopware
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
NEXT-24264 - Add ADR for new PHP 8.1 & Symfony 6.1 features
- Loading branch information
1 parent
d58f8b9
commit 373793c
Showing
3 changed files
with
778 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
Oops, something went wrong.