# Shopware Plugin Inspections

**Inspections** analyze Shopware projects in real-time and highlight potential bugs, migration issues, missing resources, invalid configuration, and platform API misuse.

---

## Use the abstract decoration contract

**Feature ID:** `ShopwareAbstractServiceDecorationInspection`  
**Feature Page:** [Use the abstract decoration contract](https://espend.de/phpstorm/plugin/shopware#shopware-abstract-service-decoration-inspection)  

Reports decorated services that extend or inject the concrete implementation where Shopware exposes an abstract decoration contract.

### Code Examples

```php
# Before:
final class CustomProductRoute extends ProductRoute
{
    public function load(string $productId, Request $request, SalesChannelContext $context): ProductRouteResponse
    {
        return parent::load($productId, $request, $context);
    }
}
```

```php
# After:
final class CustomProductRoute extends AbstractProductRoute
{
    public function __construct(private readonly AbstractProductRoute $decorated)
    {
    }

    public function getDecorated(): AbstractProductRoute
    {
        return $this->decorated;
    }

    public function load(string $productId, Request $request, SalesChannelContext $context): ProductRouteResponse
    {
        return $this->decorated->load($productId, $request, $context);
    }
}
```

---

## Avoid direct default context creation

**Feature ID:** `ShopwareDisallowDefaultContextCreationInspection`  
**Feature Page:** [Avoid direct default context creation](https://espend.de/phpstorm/plugin/shopware#shopware-disallow-default-context-creation-inspection)  

Reports direct creation of the default Shopware context when request or execution context should be passed explicitly.

### Code Examples

```php
# Before:
public function load(Criteria $criteria): EntitySearchResult
{
    $context = Context::createDefaultContext();

    return $this->productRepository->search($criteria, $context);
}
```

```php
# After:
public function load(Criteria $criteria, SalesChannelContext $context): EntitySearchResult
{
    return $this->productRepository->search($criteria, $context->getContext());
}
```

---

## Parent method becomes abstract in the next major

**Feature ID:** `ShopwareMethodBecomesAbstractInspection`  
**Feature Page:** [Parent method becomes abstract in the next major](https://espend.de/phpstorm/plugin/shopware#shopware-method-becomes-abstract-inspection)  

Reports parent methods marked with PHPDoc `@abstract` when the child class does not implement them yet.

### Code Examples

```php
# Before:
class MyRoute extends AbstractProductRoute
{
    // Parent method is still concrete but already marked @abstract.
}
```

```php
# After:
class MyRoute extends AbstractProductRoute
{
    public function getDecorated(): AbstractProductRoute
    {
        throw new DecorationPatternException(self::class);
    }
}
```

---

## Move simple id filters into Criteria

**Feature ID:** `ShopwareCriteriaIdFilterConstructor`  
**Feature Page:** [Move simple id filters into Criteria](https://espend.de/phpstorm/plugin/shopware#shopware-criteria-id-filter-constructor)  

Reports direct id filters when the id can be passed through the id-aware Criteria API instead.

### Code Examples

```php
# Before:
public function load(string $productId, Context $context): EntitySearchResult
{
    $criteria = new Criteria();
    $criteria->addFilter(new EqualsFilter('id', $productId));

    return $this->productRepository->search($criteria, $context);
}
```

```php
# After:
public function load(string $productId, Context $context): EntitySearchResult
{
    $criteria = new Criteria([$productId]);

    return $this->productRepository->search($criteria, $context);
}
```

---

## Avoid changing FOREIGN_KEY_CHECKS during updates

**Feature ID:** `ShopwareForeignKeyChecksInspection`  
**Feature Page:** [Avoid changing FOREIGN_KEY_CHECKS during updates](https://espend.de/phpstorm/plugin/shopware#shopware-foreign-key-checks-inspection)  

Reports `FOREIGN_KEY_CHECKS` changes inside Shopware migration or plugin `update()` methods. It does not report unrelated SQL strings outside update code.

### Code Examples

```php
# Before:
final class Migration1710000000Example extends MigrationStep
{
    public function update(Connection $connection): void
    {
        $connection->executeStatement('SET FOREIGN_KEY_CHECKS=0');
        $connection->executeStatement('DELETE FROM product WHERE id = :id', ['id' => $id]);
        $connection->executeStatement('SET FOREIGN_KEY_CHECKS=1');
    }
}
```

```php
# After:
final class Migration1710000000Example extends MigrationStep
{
    public function update(Connection $connection): void
    {
        $connection->delete('product_translation', ['product_id' => $id]);
        $connection->delete('product', ['id' => $id]);
    }
}
```

---

## Avoid direct admin store token reads

**Feature ID:** `ShopwareNoUserEntityStoreTokenInspection`  
**Feature Page:** [Avoid direct admin store token reads](https://espend.de/phpstorm/plugin/shopware#shopware-no-user-entity-store-token-inspection)  

Reports direct reads of the Administration user store token so integrations use explicit token handling instead of user-entity internals.

### Code Examples

```php
# Before:
public function connect(UserEntity $user): void
{
    $token = $user->getStoreToken();

    $this->client->connect($token);
}
```

```php
# After:
public function connect(string $token): void
{
    $this->client->connect($token);
}
```

---

## Avoid repository calls inside loops

**Feature ID:** `ShopwareEntityRepositoryInLoopInspection`  
**Feature Page:** [Avoid repository calls inside loops](https://espend.de/phpstorm/plugin/shopware#shopware-entity-repository-in-loop-inspection)  

Reports DAL repository searches inside loops so repeated queries can be replaced with a batched lookup.

### Code Examples

```php
# Before:
public function loadProducts(array $productIds, Context $context): array
{
    $products = [];

    foreach ($productIds as $productId) {
        $criteria = new Criteria([$productId]);
        $products[] = $this->productRepository->search($criteria, $context)->first();
    }

    return $products;
}
```

```php
# After:
public function loadProducts(array $productIds, Context $context): EntitySearchResult
{
    $criteria = new Criteria($productIds);

    return $this->productRepository->search($criteria, $context);
}
```

---

## Use Criteria ids for direct id lookups

**Feature ID:** `ShopwareNoDalFilterByIdInspection`  
**Feature Page:** [Use Criteria ids for direct id lookups](https://espend.de/phpstorm/plugin/shopware#shopware-no-dal-filter-by-id-inspection)  

Reports `EqualsFilter` and `EqualsAnyFilter` calls that filter directly on the entity id field.

### Code Examples

```php
# Before:
public function load(array $ids, Context $context): EntitySearchResult
{
    $criteria = new Criteria();
    $criteria->addFilter(new EqualsAnyFilter('id', $ids));

    return $this->productRepository->search($criteria, $context);
}
```

```php
# After:
public function load(array $ids, Context $context): EntitySearchResult
{
    $criteria = new Criteria();
    $criteria->setIds($ids);

    return $this->productRepository->search($criteria, $context);
}
```

---

## Forward the sales channel id to system config reads

**Feature ID:** `ShopwareForwardSalesChannelContextToSystemConfigInspection`  
**Feature Page:** [Forward the sales channel id to system config reads](https://espend.de/phpstorm/plugin/shopware#shopware-forward-sales-channel-context-to-system-config-inspection)  

Reports system config reads in a sales-channel scope that do not pass the active sales-channel id.

### Code Examples

```php
# Before:
public function load(SalesChannelContext $salesChannelContext): string
{
    return $this->systemConfigService->get('Acme.config.label');
}
```

```php
# After:
public function load(SalesChannelContext $salesChannelContext): string
{
    return $this->systemConfigService->get(
        'Acme.config.label',
        $salesChannelContext->getSalesChannelId()
    );
}
```

---

## Scheduled task intervals should not be too low

**Feature ID:** `ShopwareScheduledTaskIntervalInspection`  
**Feature Page:** [Scheduled task intervals should not be too low](https://espend.de/phpstorm/plugin/shopware#shopware-scheduled-task-interval-inspection)  

Reports literal scheduled task intervals below the Shopware minimum.

### Code Examples

```php
# Before:
public static function getDefaultInterval(): int
{
    return 60;
}
```

```php
# After:
public static function getDefaultInterval(): int
{
    return 300;
}
```

---

## Avoid session access in Store API and payment handlers

**Feature ID:** `ShopwareNoSessionInPaymentHandlerAndStoreApiInspection`  
**Feature Page:** [Avoid session access in Store API and payment handlers](https://espend.de/phpstorm/plugin/shopware#shopware-no-session-in-payment-handler-and-store-api-inspection)  

Reports Symfony session reads in Shopware payment handlers and Store API routes. These entrypoints should use their explicit method inputs or persisted state instead of depending on storefront session data.

### Code Examples

```php
# Reported session access:
public function pay(
    Request $request,
    PaymentTransactionStruct $transaction,
    Context $context,
    ?Struct $validateStruct
): ?RedirectResponse {
    $token = $this->session->get('checkout-token');

    return null;
}
```

```php
# Use the route or handler inputs:
public function switchContext(RequestDataBag $data, SalesChannelContext $context): ContextTokenResponse
{
    $paymentMethodId = $data->get('paymentMethodId');
    $contextToken = $context->getToken();

    // Update state through the sales-channel context services.
}
```

```php
# Payment handlers receive transaction data explicitly:
public function pay(
    Request $request,
    PaymentTransactionStruct $transaction,
    Context $context,
    ?Struct $validateStruct
): ?RedirectResponse {
    $transactionId = $transaction->getOrderTransactionId();

    // Load payment state from the transaction/order data, not from the session.
}
```

---

## DAL definition fields should match entity properties

**Feature ID:** `ShopwareDalDefinitionInspection`  
**Feature Page:** [DAL definition fields should match entity properties](https://espend.de/phpstorm/plugin/shopware#shopware-dal-definition-inspection)  

Reports DAL fields that cannot be hydrated cleanly because the linked entity is missing matching access.

### Code Examples

```php
# Definition field:
protected function defineFields(): FieldCollection
{
    return new FieldCollection([
        new StringField('technical_name', 'technicalName'),
    ]);
}
```

```php
# Entity access:
class ExampleEntity extends Entity
{
    protected ?string $technicalName = null;

    public function getTechnicalName(): ?string
    {
        return $this->technicalName;
    }
}
```

---

## activeRoute should reference an existing route

**Feature ID:** `ShopwareActiveRouteMissingInspection`  
**Feature Page:** [activeRoute should reference an existing route](https://espend.de/phpstorm/plugin/shopware#shopware-active-route-missing-inspection)  

Reports exact Twig `activeRoute` comparisons when the referenced route name cannot be found.

### Code Examples

```twig
# Checked comparison:
{% if activeRoute == 'frontend.account.home.page' %}
    active
{% endif %}
```

```twig
# Prefix checks stay valid:
{% if activeRoute starts with 'frontend.account.' %}
    active
{% endif %}
```

---

## Storefront icon should exist in the selected pack

**Feature ID:** `ShopwareSwIconMissingInspection`  
**Feature Page:** [Storefront icon should exist in the selected pack](https://espend.de/phpstorm/plugin/shopware#shopware-sw-icon-missing-inspection)  

Reports literal `sw_icon` calls when the icon pack or SVG file cannot be resolved.

### Code Examples

```twig
# Checked icon call:
{% sw_icon 'shopping-bag' style { 'pack': 'solid' } %}
```

---

## Deprecated Shopware Twig block override

**Feature ID:** `ShopwareDeprecatedTwigBlockInspection`  
**Feature Page:** [Deprecated Shopware Twig block override](https://espend.de/phpstorm/plugin/shopware#shopware-deprecated-twig-block-inspection)  

Reports Twig block overrides that target blocks already marked for removal in indexed Shopware templates.

### Code Examples

```twig
# Override checked against the indexed block metadata:
{% block page_product_detail_buy_form %}
    {{ parent() }}
{% endblock %}
```

---

## Administration snippet key is missing

**Feature ID:** `ShopwareAdministrationSnippetMissing`  
**Feature Page:** [Administration snippet key is missing](https://espend.de/phpstorm/plugin/shopware#shopware-administration-snippet-missing)  

Reports Administration translation keys that are used in code or templates but missing from nearby snippet JSON files.

### Code Examples

```javascript
# Usage:
this.$tc('swag-example.card.headline');
```

```json
# Snippet file:
{
  "swag-example": {
    "card": {
      "headline": "Example"
    }
  }
}
```

---

## Administration parent component should exist

**Feature ID:** `ShopwareAdminParentComponentMissing`  
**Feature Page:** [Administration parent component should exist](https://espend.de/phpstorm/plugin/shopware#shopware-admin-parent-component-missing)  

Reports component extensions where the static parent component name cannot be resolved.

### Code Examples

```javascript
# Checked component extension:
Shopware.Component.extend('swag-example-child', 'sw-product-detail', {
    template,
});
```

---

## Shopware Store composer metadata inspection

**Feature ID:** `ShopwareStoreComposerInspection`  
**Feature Page:** [Shopware Store composer metadata inspection](https://espend.de/phpstorm/plugin/shopware#shopware-store-composer-inspection)  

Reports missing or incomplete Shopware Store metadata in plugin composer files.

### Code Examples

```json
# composer.json:
{
  "extra": {
    "shopware-plugin-class": "Swag\\Example\\SwagExample",
    "label": {
      "en-GB": "Example extension"
    },
    "manufacturerLink": {
      "en-GB": "https://example.com"
    }
  }
}
```

---

## App script entity access needs manifest permissions

**Feature ID:** `ShopwareAppScriptMissingPermission`  
**Feature Page:** [App script entity access needs manifest permissions](https://espend.de/phpstorm/plugin/shopware#shopware-app-script-missing-permission)  

Reports repository reads and aggregations in app scripts when the app manifest does not grant the matching entity read permission, while respecting Shopware's write-permission read dependency.

### Code Examples

```twig
# Repository access that needs read permission:
{% set products = services.repository.search('product', criteria) %}
{% set total = services.repository.aggregate('product', criteria) %}
```

```xml
# Manifest permissions:
<permissions>
    <read>product</read>
    <update>order</update>
</permissions>
```

---

## App script service should be available for the active hook

**Feature ID:** `ShopwareAppScriptUnavailableService`  
**Feature Page:** [App script service should be available for the active hook](https://espend.de/phpstorm/plugin/shopware#shopware-app-script-unavailable-service)  

Reports `services.*` usages when the current script hook does not expose that facade.

### Code Examples

```twig
# Hook-specific service usage:
{% set products = services.repository.search('product', criteria) %}
{{ services.response.json({ total: products.total }) }}
```

---

