PHPStan
Common PHPStan Issues in Shopware Code
EntityRepository Should Define a Generic Type
Problem: Repository returns EntityCollection without type information.
$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:
class Foo
{
/**
* @param EntityRepository<ProductCollection> $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.
$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:
$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:
$manufacturerName = $product?->getManufacturer()?->getName() ?? 'Unknown';
Missing Generic Type for EntityCollection
Problem: Custom EntityCollection does not have a generic type.
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:
/**
* @extends EntityCollection<FooEntity>
*/
class FooCollection extends EntityCollection
{
protected function getExpectedClass(): string
{
return FooEntity::class;
}
}
$foo = $fooCollection->first();
if ($foo === null) {
throw new FooNotFoundException();
}
$foo->bar(); // No error