Skip to content

Commit

Permalink
Merge pull request #239 from AdobeDocs/2.4.7-develop
Browse files Browse the repository at this point in the history
March 2024 release (beta3)
  • Loading branch information
jeff-matthews authored Mar 12, 2024
2 parents c70d949 + 1c980d3 commit 1708deb
Show file tree
Hide file tree
Showing 7 changed files with 242 additions and 8 deletions.
4 changes: 4 additions & 0 deletions src/data/navigation/sections/development.js
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,10 @@ module.exports = [
title: "Component development",
path: "/development/components/",
pages: [
{
title: "Application Server compatibility",
path: "/development/components/app-server/",
},
{
title: "Asynchronous and deferred operations",
path: "/development/components/async-operations/",
Expand Down
4 changes: 2 additions & 2 deletions src/pages/best-practices/security/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ Avoid using these functions in your code.
* [`serialize`](https://www.php.net/manual/en/function.serialize.php)/[`unserialize`](https://www.php.net/manual/en/function.unserialize.php) - Attackers can create an exploit for these functions by passing a string with a serialized arbitrary object to the `unserialize` function to [run arbitrary code](https://www.owasp.org/index.php/PHP_Object_Injection).
* [`md5`](https://www.php.net/manual/en/function.md5.php) - The algorithm for this function is known to have [cryptographic weaknesses](https://www.owasp.org/index.php/Guide_to_Cryptography#Hashes).
You should never use this function for hashing passwords or any other sensitive data.
* [`srand`](https://www.php.net/manual/en/function.srand.php) - Using a predetermined number to seed the random number generator results in a [predictable sequence of numbers](http://programmers.stackexchange.com/questions/76229/predicting-the-output-of-phps-rand).
* [`mt_srand`](https://www.php.net/manual/en/function.mt-rand.php) - This function is a pseudo-random number generator (PRNG) and is [not cryptographically secure](http://phpsecurity.readthedocs.io/en/latest/Insufficient-Entropy-For-Random-Values.html).
* [`srand`](https://www.php.net/manual/en/function.srand.php) - Using a predetermined number to seed the random number generator results in a [predictable sequence of numbers](https://programmers.stackexchange.com/questions/76229/predicting-the-output-of-phps-rand).
* [`mt_srand`](https://www.php.net/manual/en/function.mt-rand.php) - This function is a pseudo-random number generator (PRNG) and is [not cryptographically secure](https://phpsecurity.readthedocs.io/en/latest/Insufficient-Entropy-For-Random-Values.html).

## Standard PHP library classes to avoid

Expand Down
4 changes: 2 additions & 2 deletions src/pages/coding-standards/jquery-widgets.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,9 +246,9 @@ When a component is initialized, it is also important to send parameters to it,
});
```
* You must use [DOM event bubbling](http://stackoverflow.com/questions/4616694/what-is-event-bubbling-and-capturing) to perform one-way communication between a child widget and its parent widget.
* You must use [DOM event bubbling](https://stackoverflow.com/questions/4616694/what-is-event-bubbling-and-capturing) to perform one-way communication between a child widget and its parent widget.
* Widgets must comply with the [Law of Demeter](http://en.wikipedia.org/wiki/Law_of_Demeter) principle.
* Widgets must comply with the [Law of Demeter](https://en.wikipedia.org/wiki/Law_of_Demeter) principle.
Do not instantiate a widget or call a widget's methods inside another widget.
Expand Down
2 changes: 0 additions & 2 deletions src/pages/coding-standards/js.md
Original file line number Diff line number Diff line change
Expand Up @@ -362,5 +362,3 @@ var foo = 'bar',
There is a set of custom Eslint rules to ensure code compatibility with the latest versions of third-party libraries.

These custom rules are included using the `rulePaths` setting in the [Eslint Grunt configuration](https://github.com/magento/magento2/blob/2.4/dev/tools/grunt/configs/eslint.json).

The source code of the rules can be found in the [Eslint custom rules folder](https://github.com/magento/magento2/tree/2.4/dev/tests/static/testsuite/Magento/Test/Js/_files/eslint).
36 changes: 36 additions & 0 deletions src/pages/development/backward-incompatible-changes/highlights.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,42 @@ keywords:

This page highlights backward-incompatible changes between Adobe Commerce and Magento Open Source releases that have a major impact and require detailed explanation and special instructions to ensure third-party modules continue working. High-level reference information for all backward-incompatible changes in each release are documented in [Backward incompatible changes reference](reference.md).

## 2.4.7-beta3

The following major backward-incompatible changes were introduced in the 2.4.7-beta3 Adobe Commerce and Magento Open Source releases:

* Fixes to resolve compatibility issues with Symfony
* New system configuration for limiting coupon generation
* New method and an optional parameter for multicoupons

### Fixes to resolve compatibility issues with Symfony

The latest Symfony 6.4 LTS is not supported, so changes were made to keep Symfony LTS 5.4 in 2.4.7-beta3. Extension developers must define strict typing for return values in classes that use the changed interface: `Magento\Framework\Console\Cli::getDefaultCommands`.

### New system configuration for limiting coupon generation

Added a new setting for the number of coupons to generate. This property has a default value of `250,000`, which is also the maximum value. Merchants can disable this feature by it setting it to `0` in the Admin by going to **Stores** > **Settings** > **Configuration** > **Customers** > **Promotions** > **Code Quantity Limit**.

The following module is affected by this change:

* [Magento_SalesRule](https://developer.adobe.com/commerce/php/module-reference/module-sales-rule/)

### New method and an optional parameter for multicoupons

The following changes were introduced to implement the multicoupon functionality in the [SalesRule](https://developer.adobe.com/commerce/php/module-reference/module-sales-rule/) module:

* Optional parameter added to `Magento\SalesRule\Model\ResourceModel\Rule\Collection::setValidationFilter`
* New method introduced: `Magento\SalesRule\Model\Validator::initFromQuote`

All changes have been done in a way to minimize any impact to extensions and customizations. However, there are risks of conflict if an extension or customization extends the following:

* `Magento\SalesRule\Model\ResourceModel\Rule\Collection::setValidationFilter` and adds a parameter to this method.
* `Magento\SalesRule\Model\Validator` and introduces a method with the same name `initFromQuote`.

The following module is affected by this change:

* [Magento_SalesRule](https://developer.adobe.com/commerce/php/module-reference/module-sales-rule/)

## 2.4.7-beta2

The following major backward-incompatible changes were introduced in the 2.4.7-beta2 Adobe Commerce and Magento Open Source releases:
Expand Down
196 changes: 196 additions & 0 deletions src/pages/development/components/app-server.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
---
title: Application Server | Commerce PHP Extensions
description: Learn about the Application Server architecture and how to ensure compatibility with custom extensions.
keywords:
- Extensions
edition: ee
---

<InlineAlert variant="info" slots="text" />

Available in [2.4.7-beta](https://experienceleague.adobe.com/docs/commerce-operations/release/notes/adobe-commerce/2-4-7.html) only.

# Application Server for GraphQL APIs

The [Application Server for GraphQL APIs](https://experienceleague.adobe.com/docs/commerce-operations/performance-best-practices/performance-best-practices/application-server.html) enables Adobe Commerce to maintain state among GraphQL API requests. The Application Server, which is built on the Open Swoole extension, operates as a process with worker threads that handle request processing. By preserving a bootstrapped application state among GraphQL API requests, the Application Server enhances request handling and overall product performance. As a result, GraphQL request response time can be reduced by up to 30%.

The Application Server is supported on [Cloud Starter](https://experienceleague.adobe.com/docs/commerce-cloud-service/user-guide/architecture/starter-architecture.html) deployments only. It is not available for Cloud Pro projects during Beta. It is not available for deployments of the Magento Open Source code base.

## Challenges to consider

Ensuring compatibility between your extension and the Application Server can be challenging without the right information.

This page provides all the details that you need to make sure that your code is compatible with the Application Server, including recommendations and testing instructions. Key concepts include:

- Resource management
- Memory and resource leaks
- Cookies, sessions, and superglobals
- Testing and debugging

### Resources Management

In environments using `php-fpm`, SQL connections, file handles, and other resources are automatically closed or reset at the conclusion of each HTTP request, ensuring that each new request starts with a clean slate. This mechanism helps prevent memory leaks and resource exhaustion, which can lead to degraded performance or application failure. The Application Server contains a cleanup process. This process is triggered after a response is dispatched, guaranteeing that all resources are properly released or reset, thereby maintaining the application's performance and reliability over time.

### Memory and resource leaks

Memory and resource management in PHP can often be overlooked due to the stateless nature of HTTP request handling, where resources are automatically released at the end of each request. However, in long-running scripts or applications, such as those using Swoole, proper management becomes crucial to prevent leaks and ensure efficient operation. Leaks occur when the application fails to release memory or resources that are no longer needed, leading to increased memory consumption and potential degradation of performance or even application failure over time.

### Cookies, sessions, and superglobals

Using cookies, sessions, and superglobals can be a challenge due to Swoole's asynchronous, long-running server environment, which differs significantly from the traditional, stateless PHP-FPM model. Since Swoole keeps the PHP application in memory across requests, traditional PHP superglobals like `$_GET`, `$_POST`, and `$_SESSION` do not automatically reset between requests, potentially leading to data leakage or incorrect data being served to users.

Additionally, implementing session management and cookie handling requires careful consideration to ensure thread safety and data isolation among concurrent requests, posing a significant challenge in preserving the state and security of a Swoole application.

### Testing and debugging

Traditional debugging tools and techniques designed for synchronous PHP scripts may not work as expected, as they might not properly handle the concurrency and parallel execution of tasks within Swoole. Developers must adopt or develop tools that are capable of understanding and interacting with asynchronous operations, as well as establish testing environments that can simulate the concurrent execution of multiple requests. This requires a shift in approach to both debugging and testing, with an emphasis on asynchronous logic, co-routine lifecycle management, and the potential for shared-state issues, making the process more challenging than in traditional PHP environments.

## Common reasons for incompatibility

1. [Not following technical guidelines](../../coding-standards/technical-guidelines.md)

- 2.9. Service classes (ones that provide behavior but not data, like EventManager) SHOULD NOT have a mutable state. To make extensions codebase compliant with technical guidelines mutable states should be removed from Service classes. For example, a property cache introduced for better performance when dealing with data-intensive operations, such as database queries, API calls, or complex calculations, that does not need to be executed repeatedly within the same request. However, this can cause issues with the Application Server, since the property cache would be used in consequent requests and it should be reset after each request.

- 2.14. Temporal coupling MUST be avoided. Changed state at one point in time can inadvertently affect the behavior of subsequent operations, requiring a specific order of execution for the application to function correctly. This coupling makes the code harder to understand and maintain, as the correct operation of the system becomes dependent on the sequence in which state-modifying actions are performed.

1. Superglobals and native PHP functions usage for header, session, and cookie.

We recommend using PHP Superglobals, like `$_GET`, `$_POST`, and `$_SESSION`, and native PHP functions for header, session, and cookie, instead of utilizing interfaces and service contracts through dependency injection.

## Integration testing

This section describes integration tests that you can use when developing your extension to ensure compatibility with the Application Server.

### GraphQlStateTests

This test finds the state in shared objects that should not be reused for multiple requests. It currently runs against 67 different GraphQL requests, which could be extended.

GraphQlStateTest is a test that looks for state changes in service objects created by ObjectManager. It does this by running a GraphQL query twice, and comparing the state of the service objects before and after the second query.

There are two files used to filter and skip classes that are safe to be reused in a stateful application:

- `dev/tests/integration/testsuite/Magento/GraphQl/_files/state-skip-list.php`—Skips the comparison of objects by their class name or virtual name (as defined in the `di.xml` file).
- `dev/tests/integration/testsuite/Magento/GraphQl/_files/state-filter-list.php`—Filters out the properties that should be compared.

<InlineAlert variant="info" slots="text" />

There are similar files in the `magento/magento2ee` GitHub repository for classes that are specific to `magento2ee`. Similarly, other repositories can use their own files.

In the `state-filter-list.php` file, there are three different types of filtering:

- The `all` section filters these property names from all of the service objects.
- The `parents` section first compares the service object to the specified class or interface using the `instanceof` operator. If it returns true, then those filters are applied.
- The `services` section checks for direct matches of the `serviceName`. The `serviceName` can either be the class name, or virtual type of preferences (as defined in `di.xml` files) before applying the properties filter.

If you are working on a failure and it does not look like it is safe to add to the skip or filter list, then consider if you can refactor the class in a [backwards-compatible](https://developer.adobe.com/commerce/contributor/guides/code-contributions/backward-compatibility-policy/) way to use Factories of the service classes that have mutable state. If it is the class itself that has mutable state, then try to rewrite it in a way to avoid mutable state. If the mutable state is required for performance reasons, then implement `ResetAfterRequestInterface` and use `_resetState()` to reset the object back to its initial constructed state.

If the class is failing because of a `Typed property $x must not be accessed before initialization` error, then the property is not initialized by the constructor. This is a form of temporal coupling, because the object cannot be used after it is initially constructed. This happens even if the property is private because the Collector gets the data from the properties using PHP's reflection feature. In this case, refactor the class to avoid temporal coupling, and also to avoid mutable state. If that does not work, the property can change its type to a nullable type and be initialized to null. If the property is an array, it may be okay to initialize the property as an empty array.

Command to run test:

```bash
vendor/bin/phpunit -c $(pwd)/dev/tests/integration/phpunit.xml dev/tests/integration/testsuite/Magento/GraphQl/App/GraphQlStateTest.php
```

### ResetAfterRequestTest

`ResetAfterRequestTest` finds all classes that implement `ResetAfterRequestInterface` and verifies that the `_resetState()` method returns the state of the object to the same state as it was after being constructed by ObjectManager. It does this by creating a service object with ObjectManager, cloning that object, then calling `_resetState()` and comparing it. It does not call any methods in between object instantiation and `_resetState()`, so it does not confirm resetting any mutable state. If it finds bugs or typos in `_resetState()`, it may set the state to something different than the original state.

If this test fails, then you should check to see if you have changed a class in a way where the object has different values in properties after construction comparted to after the `_resetState()` method is called. If the class that you are working on does not have the `_resetState()` method itself, then check the class hierarchy for a superclass that is implementing it.

If the class is failing because of a `Typed property $x must not be accessed before initialization` error, see the discussion about this problem in the [GraphQlStateTest](#graphqlstatetests) section.

Command to run test:

```bash
vendor/bin/phpunit -c $(pwd)/dev/tests/integration/phpunit.xml dev/tests/integration/testsuite/Magento/Framework/ObjectManager/ResetAfterRequestTest.php
```

## How to fix test failures

To set up local testing and debugging:

1. Install the Swoole extension:

```bash
pecl install swoole
```

1. Route all GraphQL requests to the application server (nginx example):

```php
location /graphql {
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

proxy_pass http://127.0.0.1:9501/graphql;
}
```

1. Run the application server with the CLI command:

```bash
bin/magento server:run
```

1. Execute GraphQL mutations against the application server and debug with xDebug if needed.

## Example of mutable state in code

Example with the `Magento\Catalog\Model\Product\Image\Cache` class:

```php
/**
* @var array
*/
protected $data = [];
```

Review the following example to see how the `$data` property is used:

```php
protected function getData()
{
if (!$this->data) {
foreach ($this->themeCollection->loadRegisteredThemes() as $theme) {
$config = $this->viewConfig->getViewConfig([
'area' => Area::AREA_FRONTEND,
'themeModel' => $theme,
]);
$images = $config->getMediaEntities('Magento_Catalog', ImageHelper::MEDIA_TYPE_CONFIG_NODE);
foreach ($images as $imageId => $imageData) {
$this->data[$theme->getCode() . $imageId] = array_merge(['id' => $imageId], $imageData);
}
}
}
return $this->data;
}
```

### How to fix mutable state

There are several challenges you may face for refactoring the code with mutable state, including, but not limited to:

- Backwards compatibility
- Performance efficiency
- Lack of time

In the example with the [`Magento\Catalog\Model\Product\Image\Cache`](#example-of-mutable-state-in-code) class, we can see performance benefits of reusing the `$data` property in the context of a single request, but we should reset the state after the request.

For this purpose, we should implement the following:

```php
class Cache implements ResetAfterRequestInterface
```

Add the implementation of the `_resetState()` method with overriding `$data` property to its initial state - empty array:

```php
/**
* @inheritDoc
*/
public function _resetState(): void
{
$this->data = [];
}
```
Loading

0 comments on commit 1708deb

Please sign in to comment.