diff --git a/resources/guidelines/troubleshooting/index.md b/resources/guidelines/troubleshooting/index.md new file mode 100644 index 000000000..2abc7f783 --- /dev/null +++ b/resources/guidelines/troubleshooting/index.md @@ -0,0 +1,10 @@ +--- +nav: + title: Troubleshooting + position: 10 + +--- + +# Troubleshooting + +Use this section to diagnose and resolve common issues you might encounter while working with Shopware projects. diff --git a/resources/guidelines/troubleshooting.md b/resources/guidelines/troubleshooting/performance.md similarity index 96% rename from resources/guidelines/troubleshooting.md rename to resources/guidelines/troubleshooting/performance.md index 2beb5d14f..9cc32cdb0 100644 --- a/resources/guidelines/troubleshooting.md +++ b/resources/guidelines/troubleshooting/performance.md @@ -1,13 +1,13 @@ --- nav: - title: Troubleshooting - position: 80 + title: Performance + position: 20 --- -# Troubleshooting +# Performance -## Performance +## Common Performance Considerations ### Dynamic product groups are slow to load diff --git a/resources/guidelines/troubleshooting/phpstan.md b/resources/guidelines/troubleshooting/phpstan.md new file mode 100644 index 000000000..a2805e736 --- /dev/null +++ b/resources/guidelines/troubleshooting/phpstan.md @@ -0,0 +1,125 @@ +--- +nav: + title: PHPStan + position: 30 +--- + +# PHPStan + +## Common PHPStan Issues in Shopware Code + +### EntityRepository 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 a PHP doc with a generic type 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 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 + +**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 +```