From 9b84951eb0dda3d64e16c2a22af74cf200bcd8b9 Mon Sep 17 00:00:00 2001 From: Michael Telgmann Date: Fri, 10 Oct 2025 10:48:10 +0200 Subject: [PATCH 1/4] feat: Add troubleshooting section for PHPStan --- .../index.md} | 0 .../guidelines/troubleshooting/phpstan.md | 125 ++++++++++++++++++ 2 files changed, 125 insertions(+) rename resources/guidelines/{troubleshooting.md => troubleshooting/index.md} (100%) create mode 100644 resources/guidelines/troubleshooting/phpstan.md diff --git a/resources/guidelines/troubleshooting.md b/resources/guidelines/troubleshooting/index.md similarity index 100% rename from resources/guidelines/troubleshooting.md rename to resources/guidelines/troubleshooting/index.md diff --git a/resources/guidelines/troubleshooting/phpstan.md b/resources/guidelines/troubleshooting/phpstan.md new file mode 100644 index 000000000..69467d107 --- /dev/null +++ b/resources/guidelines/troubleshooting/phpstan.md @@ -0,0 +1,125 @@ +--- +nav: + title: PHPStan + position: 10 +--- + +# PHPStan + +## Common PHPStan Issues in Shopware Code + +### EntityRepositorys Should Define a Generic Type + +**Problem**: Repository returns EntityCollection without type information. + +```php +$products = $this->productRepository->search($criteria, $context)->getEntities(); +foreach ($products as $product) { + // PHPStan doesn't know $product is ProductEntity + $name = $product->getName(); // Call to an undefined method Shopware\Core\Framework\DataAbstractionLayer\Entity::getName() +} +``` + +**Solution**: Add PHPDoc with generics to EntityRepository: + +```php +class Foo +{ + /** + * @param EntityRepository $productRepository + */ + public function __construct( + private readonly EntityRepository $productRepository, + ) { + } + + public function doSomething(): void + { + // ... + $products = $this->productRepository->search($criteria, $context)->getEntities(); + foreach ($products as $product) { + $name = $product->getName(); // PHPStan correctly identifies this as ProductEntity + } + } +} +``` + +Be aware that the `EntityRepository` class is a generic class, which gets an EntityCollection as type. +This might sound counterintuitive and different to other well-known repository classes, which take the Entity class as the generic type. +But it was the easiest technical solution to get PHPStan to understand the type of the collection returned by the search method. + +### Null Safety with First method and Associations + +**Problem**: Calling `first` could return `null`, also entity associations can be `null` if not loaded. + +```php +$product = $this->productRepository->search($criteria, $context)->first(); +$manufacturer = $product->getManufacturer(); // Cannot call method getManufacturer() on Shopware\Core\Content\Product\ProductEntity|null. +$manufacturerName = $manufacturer->getName(); // Cannot call method getName() on Shopware\Core\Content\Product\Aggregate\ProductManufacturer\ProductManufacturerEntity|null. +``` + +**Solution**: Ensure associations are added before in the criteria and always check for possible `null` returns: + +```php +$criteria = new Criteria(); +$criteria->addAssociation('manufacturer'); + +$product = $this->productRepository->search($criteria, $context)->first(); +if ($product === null) { + throw new ProductNotFoundException(); +} + +$manufacturer = $product->getManufacturer(); +if ($manufacturer === null) { + throw new ManufacturerNotLoadedException(); +} + +$manufacturerName = $manufacturer->getName(); // No error +``` + +Or use the null-safe operators: + +```php +$manufacturerName = $product?->getManufacturer()?->getName() ?? 'Unknown'; +``` + +### Missing Generic Type for EntityCollection + +**Problem**: Custom EntityCollection does not have a generic type. + +```php +class FooCollection extends EntityCollection +{ + protected function getExpectedClass(): string + { + return FooEntity::class; + } +} + +$foo = $fooCollection->first(); +if ($foo === null) { + throw new FooNotFoundException(); +} +$foo->bar(); // Cannot call method bar() on Shopware\Core\Framework\DataAbstractionLayer\Entity. +``` + +**Solution**: Add a generic type to EntityCollection: + +```php +/** + * @extends EntityCollection + */ +class FooCollection extends EntityCollection +{ + protected function getExpectedClass(): string + { + return FooEntity::class; + } +} + +$foo = $fooCollection->first(); +if ($foo === null) { + throw new FooNotFoundException(); +} +$foo->bar(); // No error +``` From 60ed4d3f22639b8163f8939b15ccc64ae4a35834 Mon Sep 17 00:00:00 2001 From: Michael Telgmann Date: Fri, 10 Oct 2025 10:54:32 +0200 Subject: [PATCH 2/4] spell check --- resources/guidelines/troubleshooting/phpstan.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/guidelines/troubleshooting/phpstan.md b/resources/guidelines/troubleshooting/phpstan.md index 69467d107..3ad04e7f8 100644 --- a/resources/guidelines/troubleshooting/phpstan.md +++ b/resources/guidelines/troubleshooting/phpstan.md @@ -8,7 +8,7 @@ nav: ## Common PHPStan Issues in Shopware Code -### EntityRepositorys Should Define a Generic Type +### EntityRepository Should Define a Generic Type **Problem**: Repository returns EntityCollection without type information. @@ -45,7 +45,7 @@ class Foo ``` Be aware that the `EntityRepository` class is a generic class, which gets an EntityCollection as type. -This might sound counterintuitive and different to other well-known repository classes, which take the Entity class as the generic type. +This might sound counter-intuitive and different to other well-known repository classes, which take the Entity class as the generic type. But it was the easiest technical solution to get PHPStan to understand the type of the collection returned by the search method. ### Null Safety with First method and Associations From 945387ae7bc22a80c42dfd53ff635c2957087f1d Mon Sep 17 00:00:00 2001 From: Michael Telgmann Date: Fri, 10 Oct 2025 10:59:50 +0200 Subject: [PATCH 3/4] another spell check fix --- resources/guidelines/troubleshooting/phpstan.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/guidelines/troubleshooting/phpstan.md b/resources/guidelines/troubleshooting/phpstan.md index 3ad04e7f8..805e78eb5 100644 --- a/resources/guidelines/troubleshooting/phpstan.md +++ b/resources/guidelines/troubleshooting/phpstan.md @@ -20,7 +20,7 @@ foreach ($products as $product) { } ``` -**Solution**: Add PHPDoc with generics to EntityRepository: +**Solution**: Add a PHP doc with a generic type to EntityRepository: ```php class Foo From 147bd41bcde56c819d57eb94ce47dcfff5b87879 Mon Sep 17 00:00:00 2001 From: Micha Date: Tue, 14 Oct 2025 14:12:33 +0200 Subject: [PATCH 4/4] feat/add-new-subsection --- resources/guidelines/troubleshooting/index.md | 34 +--------------- .../guidelines/troubleshooting/performance.md | 40 +++++++++++++++++++ .../guidelines/troubleshooting/phpstan.md | 2 +- 3 files changed, 43 insertions(+), 33 deletions(-) create mode 100644 resources/guidelines/troubleshooting/performance.md diff --git a/resources/guidelines/troubleshooting/index.md b/resources/guidelines/troubleshooting/index.md index 2beb5d14f..2abc7f783 100644 --- a/resources/guidelines/troubleshooting/index.md +++ b/resources/guidelines/troubleshooting/index.md @@ -1,40 +1,10 @@ --- nav: title: Troubleshooting - position: 80 + position: 10 --- # Troubleshooting -## Performance - -### Dynamic product groups are slow to load - -When you use a `contains` filter in dynamic product groups (especially when you use that on a custom field), the loading of that dynamic product group might get slow. -The reason is that the underlying SQL query is not and cannot be optimized for this kind of filter. -When you use OpenSearch instead of relying on the DB for searching, this issue should be resolved. -Alternatively, for using `contains` on custom fields, it should be preferred to create individual bool custom fields for the different values and check those instead. -When contains on usual fields is used and slow, it should help to add a [custom field](../../guides/plugins/plugins/framework/custom-field/) and manually manage that. -Alternatively, [tags](https://docs.shopware.com/en/shopware-6-en/settings/tags) can be used for this purpose. - -### Cache is invalided too often - -It might be that your caching is not effective because the cache is invalidated too often. -You should look for the reason why the cache is invalidated that frequently. -In general, it means that probably there is a background process running that leads to the cache invalidation. -This could be more obvious cases like cron jobs manually clearing the cache or more subtle cases like your ERP system syncing products frequently, -which will lead to cache invalidations of all pages where those products are referenced. -For cases like the latter, there is the option to only clear the cache delayed and not immediately ([this will be the new default starting with shopware 6.7.0.0](https://github.com/shopware/shopware/blob/trunk/UPGRADE-6.7.md#delayed-cache-invalidation)). -You might consider [activating this feature](../../guides/hosting/performance/performance-tweaks.md#delayed-invalidation) in older versions. - -### High Memory Usage - -While using certain APIs or e.g. the `EntityRepository` it might happen that the memory usage is increasing constantly. -First, you should make sure that you have set the `APP_ENV` variable to `prod` in your `.env` file. -If the `APP_ENV` is set to `dev` Shopware keeps many objects for debugging purposes, which will lead to high memory usage. -If the memory usage issue persists after setting `APP_ENV` to `prod`, check if you are using the [sync API](https://shopware.stoplight.io/docs/admin-api/faf8f8e4e13a0-bulk-payloads). -Also consider changing the `indexing-behavior` to your needs if you need to sync many entities. -Another reason for high memory usage might be the logging within the application. -See the logging section in the [performance guide](../../guides/hosting/performance/performance-tweaks.md#logging) for more information. -After all, you still can make use of tools like blackfire.io to find the root cause of the memory usage. +Use this section to diagnose and resolve common issues you might encounter while working with Shopware projects. diff --git a/resources/guidelines/troubleshooting/performance.md b/resources/guidelines/troubleshooting/performance.md new file mode 100644 index 000000000..9cc32cdb0 --- /dev/null +++ b/resources/guidelines/troubleshooting/performance.md @@ -0,0 +1,40 @@ +--- +nav: + title: Performance + position: 20 + +--- + +# Performance + +## Common Performance Considerations + +### Dynamic product groups are slow to load + +When you use a `contains` filter in dynamic product groups (especially when you use that on a custom field), the loading of that dynamic product group might get slow. +The reason is that the underlying SQL query is not and cannot be optimized for this kind of filter. +When you use OpenSearch instead of relying on the DB for searching, this issue should be resolved. +Alternatively, for using `contains` on custom fields, it should be preferred to create individual bool custom fields for the different values and check those instead. +When contains on usual fields is used and slow, it should help to add a [custom field](../../guides/plugins/plugins/framework/custom-field/) and manually manage that. +Alternatively, [tags](https://docs.shopware.com/en/shopware-6-en/settings/tags) can be used for this purpose. + +### Cache is invalided too often + +It might be that your caching is not effective because the cache is invalidated too often. +You should look for the reason why the cache is invalidated that frequently. +In general, it means that probably there is a background process running that leads to the cache invalidation. +This could be more obvious cases like cron jobs manually clearing the cache or more subtle cases like your ERP system syncing products frequently, +which will lead to cache invalidations of all pages where those products are referenced. +For cases like the latter, there is the option to only clear the cache delayed and not immediately ([this will be the new default starting with shopware 6.7.0.0](https://github.com/shopware/shopware/blob/trunk/UPGRADE-6.7.md#delayed-cache-invalidation)). +You might consider [activating this feature](../../guides/hosting/performance/performance-tweaks.md#delayed-invalidation) in older versions. + +### High Memory Usage + +While using certain APIs or e.g. the `EntityRepository` it might happen that the memory usage is increasing constantly. +First, you should make sure that you have set the `APP_ENV` variable to `prod` in your `.env` file. +If the `APP_ENV` is set to `dev` Shopware keeps many objects for debugging purposes, which will lead to high memory usage. +If the memory usage issue persists after setting `APP_ENV` to `prod`, check if you are using the [sync API](https://shopware.stoplight.io/docs/admin-api/faf8f8e4e13a0-bulk-payloads). +Also consider changing the `indexing-behavior` to your needs if you need to sync many entities. +Another reason for high memory usage might be the logging within the application. +See the logging section in the [performance guide](../../guides/hosting/performance/performance-tweaks.md#logging) for more information. +After all, you still can make use of tools like blackfire.io to find the root cause of the memory usage. diff --git a/resources/guidelines/troubleshooting/phpstan.md b/resources/guidelines/troubleshooting/phpstan.md index 805e78eb5..a2805e736 100644 --- a/resources/guidelines/troubleshooting/phpstan.md +++ b/resources/guidelines/troubleshooting/phpstan.md @@ -1,7 +1,7 @@ --- nav: title: PHPStan - position: 10 + position: 30 --- # PHPStan