--- url: >- /docs/resources/references/adr/2024-12-19-offer-html-alternative-to-our-pdf-standard-document.md --- # "\[A11y] Offer HTML alternative to our pdf standard documents" ::: info This document represents an architecture decision record (ADR) and has been mirrored from the ADR section in our Shopware 6 repository. You can find the original version [here](https://github.com/shopware/shopware/blob/trunk/adr/2024-12-19-offer-html-alternative-to-our-pdf-standard-document.md) ::: ## Context To comply with Web Content Accessibility Guidelines (WCAG), we aim to make Shopware 6's document solution accessible (A11y-compliant). This ensures that our solution remains usable while meeting legal requirements and making documents accessible to customers with disabilities. Currently, our PDF generation library, DomPDF, does not meet accessibility standards, posing a significant challenge. ## Decision We have decided to make HTML documents available in addition to PDF documents, as these are more accessible. * **Better for Accessibility**: HTML is naturally organized, making it easier for accessibility tools to read and present content to people with disabilities. * **Lack of Support**: As our current PDFs lack support for accessibility. Few tools, especially in PHP, can create accessible tagged PDFs, making it difficult to maintain PDF accessibility. * **Industry Trends**: Many organizations are already moving from PDFs to HTML for accessibility. For example, government websites have been required to meet accessibility standards since the early 2000s. Most of them now use HTML for most of their content because it meets these standards better. Providing HTML documents aligns with these trends and ensures we are using best practices for accessibility. ### Affected Areas We will integrate HTML A11y document support in the following areas: 1. **Document Type Support**: * Support includes all document types Shopware provides by default, `invoice`, `delivery note`, `credit note`, and `cancellation invoice`. Extensions must adapt themselves. 2. **Administration**: * **Order Detail Page**: Option to download HTML alongside PDF for each document type. * **Document Settings**: Toggle to generate HTML documents. 3. **Storefront**: * **Order History**: Customers can access HTML and PDF versions of documents. 4. **Flow Builder**: * This setup requires no additional special actions, and merchants can customize file generation for "Generate documents" in the `Document Settings` 5. **Email Delivery**: * Enhance the original email by including a link to the HTML document. Customers will need to log in to access the document, and additional guidance will be provided. * We can’t attach the HTML file directly due to issues with "virus scanners", as many email providers do not allow HTML file attachments. Instead we will provide a link inside the Email. * A lot of the major platforms (Microsoft, Google, Amazon, etc.) will also email a summary with a link to the customer account for things like Azure/Google Cloud/etc. ### Core concept #### Document Template 1. **Adjust Twig Template for A11y**: * Modify the `html.twig` templates to support accessibility (A11y) by adding elements like `tabindex` and appropriate CSS styles. `src/Core/Framework/Resources/views/documents/invoice.html.twig`: ```twig {% block document_headline %}

{% endblock %} ``` `src/Core/Framework/Resources/views/documents/style_base_html.css.twig`: ```twig {% block document_style_html %} body { max-width: 1200px; margin: auto; font-size: 14px; line-height: 18px; } ... {% endblock %} ``` 2. **Metadata and Security**: * The generation date of the HTML will be "fingerprinted" by adding a metadata header. This allows users to track the creation date of the document. * Implement a Content-Security-Policy meta-tag to minimize XSS attack risks, such as disallowing JavaScript and Restricts to the same domain, protecting against base URL manipulation. Added new Twig block for metadata`src/Core/Framework/Resources/views/documents/base.html.twig`: ```twig {% block document_head_meta_protection %} {% endblock %} ``` #### Core 1. **Abstract Class for Multi-Format Rendering** * We will introduce an abstract class, `src/Core/Checkout/Document/Service/AbstractDocumentTypeRenderer`, to support rendering multiple document types, including `PDF` and `HTML`. ```php abstract class AbstractDocumentTypeRenderer { abstract public function render(RenderedDocument $document): string; } class HtmlRenderer extends AbstractDocumentTypeRenderer { public function render(RenderedDocument $document): string { $content = $this->documentTemplateRenderer->render( ...$options ); $document->setContentType(self::FILE_CONTENT_TYPE); $document->setFileExtension(self::FILE_EXTENSION); $document->setContent($content); return $content; } } class PdfRenderer extends AbstractDocumentTypeRenderer {} ``` 2. **Service Registration**: * We need to use the service tag `document_type.renderer` for the `Shopware\Core\Checkout\Document\Service\DocumentFileRendererRegistry` to recognize this service. This is essential for the proper registration and functioning of the `HtmlRenderer`. ```xml ``` 3. **Database Schema**: * We will add a new column `document_a11y_media_file_id` to the `document` table to store the media file ID for HTML A11y documents. ```sql ALTER TABLE `document` ADD COLUMN `document_a11y_media_file_id` BINARY(16); ``` * The column is intended to link each document entry with its corresponding A11y media file `src/Core/Checkout/Document/DocumentDefinition.php` ```php (new FkField('document_a11y_media_file_id', 'documentA11yMediaFileId', MediaDefinition::class)) ->addFlags(new ApiAware()); ``` ### Email Migration * For templates that have been customized, new content must be migrated same as code below: `src/Core/Migration/Fixtures/mails/invoice_mail/de-plain.html.twig` ```twig {% if a11yDocuments %} For better accessibility, we also provide an HTML version of the documents here: {% for a11y in a11yDocuments %} {% set documentLink = rawUrl( 'frontend.account.order.single.document.a11y', { documentId: a11y.documentId, deepLinkCode: a11y.deepLinkCode, fileType: a11y.fileExtension, }, salesChannel.domains|first.url ) %} - {{ documentLink }} {% endfor %} {% endif %} ``` ## Consequences With this implementation, Shopware 6 will support HTML A11y documents alongside PDFs for standard document types. This change will have the following consequences: * **Renderer Updates**: Document renderers need changes to handle HTML output, using the `AbstractDocumentTypeRenderer` [here](#core). * **Email Integration**: For templates that have been customized, new content must be migrated as detailed in [here](#email-migration). * **Improved Accessibility**: HTML documents make content easier to access for users with disabilities, aligning with WCAG standards. * **Customizability**: Options in Document settings to enable or disable HTML documents should be added, giving merchants choice in document format. --- --- url: >- /docs/v6.6/resources/references/adr/2024-12-19-offer-html-alternative-to-our-pdf-standard-document.md --- # "\[A11y] Offer HTML alternative to our pdf standard documents" ::: info This document represents an architecture decision record (ADR) and has been mirrored from the ADR section in our Shopware 6 repository. You can find the original version [here](https://github.com/shopware/shopware/blob/trunk/adr/2024-12-19-offer-html-alternative-to-our-pdf-standard-document.md) ::: ## Context To comply with Web Content Accessibility Guidelines (WCAG), we aim to make Shopware 6's document solution accessible (A11y-compliant). This ensures that our solution remains usable while meeting legal requirements and making documents accessible to customers with disabilities. Currently, our PDF generation library, DomPDF, does not meet accessibility standards, posing a significant challenge. ## Decision We have decided to make HTML documents available in addition to PDF documents, as these are more accessible. * **Better for Accessibility**: HTML is naturally organized, making it easier for accessibility tools to read and present content to people with disabilities. * **Lack of Support**: As our current PDFs lack support for accessibility. Few tools, especially in PHP, can create accessible tagged PDFs, making it difficult to maintain PDF accessibility. * **Industry Trends**: Many organizations are already moving from PDFs to HTML for accessibility. For example, government websites have been required to meet accessibility standards since the early 2000s. Most of them now use HTML for most of their content because it meets these standards better. Providing HTML documents aligns with these trends and ensures we are using best practices for accessibility. ### Affected Areas We will integrate HTML A11y document support in the following areas: 1. **Document Type Support**: * Support includes all document types Shopware provides by default, `invoice`, `delivery note`, `credit note`, and `cancellation invoice`. Extensions must adapt themselves. 2. **Administration**: * **Order Detail Page**: Option to download HTML alongside PDF for each document type. * **Document Settings**: Toggle to generate HTML documents. 3. **Storefront**: * **Order History**: Customers can access HTML and PDF versions of documents. 4. **Flow Builder**: * This setup requires no additional special actions, and merchants can customize file generation for "Generate documents" in the `Document Settings` 5. **Email Delivery**: * Enhance the original email by including a link to the HTML document. Customers will need to log in to access the document, and additional guidance will be provided. * We can’t attach the HTML file directly due to issues with "virus scanners", as many email providers do not allow HTML file attachments. Instead we will provide a link inside the Email. * A lot of the major platforms (Microsoft, Google, Amazon, etc.) will also email a summary with a link to the customer account for things like Azure/Google Cloud/etc. ### Core concept #### Document Template 1. **Adjust Twig Template for A11y**: * Modify the `html.twig` templates to support accessibility (A11y) by adding elements like `tabindex` and appropriate CSS styles. `src/Core/Framework/Resources/views/documents/invoice.html.twig`: ```twig {% block document_headline %}

{% endblock %} ``` `src/Core/Framework/Resources/views/documents/style_base_html.css.twig`: ```twig {% block document_style_html %} body { max-width: 1200px; margin: auto; font-size: 14px; line-height: 18px; } ... {% endblock %} ``` 2. **Metadata and Security**: * The generation date of the HTML will be "fingerprinted" by adding a metadata header. This allows users to track the creation date of the document. * Implement a Content-Security-Policy meta-tag to minimize XSS attack risks, such as disallowing JavaScript and Restricts to the same domain, protecting against base URL manipulation. Added new Twig block for metadata`src/Core/Framework/Resources/views/documents/base.html.twig`: ```twig {% block document_head_meta_protection %} {% endblock %} ``` #### Core 1. **Abstract Class for Multi-Format Rendering** * We will introduce an abstract class, `src/Core/Checkout/Document/Service/AbstractDocumentTypeRenderer`, to support rendering multiple document types, including `PDF` and `HTML`. ```php abstract class AbstractDocumentTypeRenderer { abstract public function render(RenderedDocument $document): string; } class HtmlRenderer extends AbstractDocumentTypeRenderer { public function render(RenderedDocument $document): string { $content = $this->documentTemplateRenderer->render( ...$options ); $document->setContentType(self::FILE_CONTENT_TYPE); $document->setFileExtension(self::FILE_EXTENSION); $document->setContent($content); return $content; } } class PdfRenderer extends AbstractDocumentTypeRenderer {} ``` 2. **Service Registration**: * We need to use the service tag `document_type.renderer` for the `Shopware\Core\Checkout\Document\Service\DocumentFileRendererRegistry` to recognize this service. This is essential for the proper registration and functioning of the `HtmlRenderer`. ```xml ``` 3. **Database Schema**: * We will add a new column `document_a11y_media_file_id` to the `document` table to store the media file ID for HTML A11y documents. ```sql ALTER TABLE `document` ADD COLUMN `document_a11y_media_file_id` BINARY(16); ``` * The column is intended to link each document entry with its corresponding A11y media file `src/Core/Checkout/Document/DocumentDefinition.php` ```php (new FkField('document_a11y_media_file_id', 'documentA11yMediaFileId', MediaDefinition::class)) ->addFlags(new ApiAware()); ``` ### Email Migration * For templates that have been customized, new content must be migrated same as code below: `src/Core/Migration/Fixtures/mails/invoice_mail/de-plain.html.twig` ```twig {% if a11yDocuments %} For better accessibility, we also provide an HTML version of the documents here: {% for a11y in a11yDocuments %} {% set documentLink = rawUrl( 'frontend.account.order.single.document.a11y', { documentId: a11y.documentId, deepLinkCode: a11y.deepLinkCode, fileType: a11y.fileExtension, }, salesChannel.domains|first.url ) %} - {{ documentLink }} {% endfor %} {% endif %} ``` ## Consequences With this implementation, Shopware 6 will support HTML A11y documents alongside PDFs for standard document types. This change will have the following consequences: * **Renderer Updates**: Document renderers need changes to handle HTML output, using the `AbstractDocumentTypeRenderer` [here](#core). * **Email Integration**: For templates that have been customized, new content must be migrated as detailed in [here](#email-migration). * **Improved Accessibility**: HTML documents make content easier to access for users with disabilities, aligning with WCAG standards. * **Customizability**: Options in Document settings to enable or disable HTML documents should be added, giving merchants choice in document format. --- --- url: /frontends/packages/composables/useAddToCart.md --- # {{NAME}} {{DESCRIPTION}} {{ADDITIONAL\_README}} {{DEMO\_BLOCK}} ## Types {{INTERFACE\_CONTENT}} {{RETURN\_TYPES\_CONTENT}} --- --- url: /frontends/packages/composables/useAddress.md --- # {{NAME}} {{DESCRIPTION}} {{ADDITIONAL\_README}} {{DEMO\_BLOCK}} ## Types {{INTERFACE\_CONTENT}} {{RETURN\_TYPES\_CONTENT}} --- --- url: /frontends/packages/composables/useB2bQuoteManagement.md --- # {{NAME}} {{DESCRIPTION}} {{ADDITIONAL\_README}} {{DEMO\_BLOCK}} ## Types {{INTERFACE\_CONTENT}} {{RETURN\_TYPES\_CONTENT}} --- --- url: /frontends/packages/composables/useBreadcrumbs.md --- # {{NAME}} {{DESCRIPTION}} {{ADDITIONAL\_README}} {{DEMO\_BLOCK}} ## Types {{INTERFACE\_CONTENT}} {{RETURN\_TYPES\_CONTENT}} --- --- url: /frontends/packages/composables/useCart.md --- # {{NAME}} {{DESCRIPTION}} {{ADDITIONAL\_README}} {{DEMO\_BLOCK}} ## Types {{INTERFACE\_CONTENT}} {{RETURN\_TYPES\_CONTENT}} --- --- url: /frontends/packages/composables/useCartErrorParamsResolver.md --- # {{NAME}} {{DESCRIPTION}} {{ADDITIONAL\_README}} {{DEMO\_BLOCK}} ## Types {{INTERFACE\_CONTENT}} {{RETURN\_TYPES\_CONTENT}} --- --- url: /frontends/packages/composables/useCartItem.md --- # {{NAME}} {{DESCRIPTION}} {{ADDITIONAL\_README}} {{DEMO\_BLOCK}} ## Types {{INTERFACE\_CONTENT}} {{RETURN\_TYPES\_CONTENT}} --- --- url: /frontends/packages/composables/useCartNotification.md --- # {{NAME}} {{DESCRIPTION}} {{ADDITIONAL\_README}} {{DEMO\_BLOCK}} ## Types {{INTERFACE\_CONTENT}} {{RETURN\_TYPES\_CONTENT}} --- --- url: /frontends/packages/composables/useCategory.md --- # {{NAME}} {{DESCRIPTION}} {{ADDITIONAL\_README}} {{DEMO\_BLOCK}} ## Types {{INTERFACE\_CONTENT}} {{RETURN\_TYPES\_CONTENT}} --- --- url: /frontends/packages/composables/useCategorySearch.md --- # {{NAME}} {{DESCRIPTION}} {{ADDITIONAL\_README}} {{DEMO\_BLOCK}} ## Types {{INTERFACE\_CONTENT}} {{RETURN\_TYPES\_CONTENT}} --- --- url: /frontends/packages/composables/useCheckout.md --- # {{NAME}} {{DESCRIPTION}} {{ADDITIONAL\_README}} {{DEMO\_BLOCK}} ## Types {{INTERFACE\_CONTENT}} {{RETURN\_TYPES\_CONTENT}} --- --- url: /frontends/packages/composables/useCmsBlock.md --- # {{NAME}} {{DESCRIPTION}} {{ADDITIONAL\_README}} {{DEMO\_BLOCK}} ## Types {{INTERFACE\_CONTENT}} {{RETURN\_TYPES\_CONTENT}} --- --- url: /frontends/packages/composables/useCmsMeta.md --- # {{NAME}} {{DESCRIPTION}} {{ADDITIONAL\_README}} {{DEMO\_BLOCK}} ## Types {{INTERFACE\_CONTENT}} {{RETURN\_TYPES\_CONTENT}} --- --- url: /frontends/packages/composables/useCmsSection.md --- # {{NAME}} {{DESCRIPTION}} {{ADDITIONAL\_README}} {{DEMO\_BLOCK}} ## Types {{INTERFACE\_CONTENT}} {{RETURN\_TYPES\_CONTENT}} --- --- url: /frontends/packages/composables/useCmsTranslations.md --- # {{NAME}} {{DESCRIPTION}} {{ADDITIONAL\_README}} {{DEMO\_BLOCK}} ## Types {{INTERFACE\_CONTENT}} {{RETURN\_TYPES\_CONTENT}} --- --- url: /frontends/packages/composables/useContext.md --- # {{NAME}} {{DESCRIPTION}} {{ADDITIONAL\_README}} {{DEMO\_BLOCK}} ## Types {{INTERFACE\_CONTENT}} {{RETURN\_TYPES\_CONTENT}} --- --- url: /frontends/packages/composables/useCountries.md --- # {{NAME}} {{DESCRIPTION}} {{ADDITIONAL\_README}} {{DEMO\_BLOCK}} ## Types {{INTERFACE\_CONTENT}} {{RETURN\_TYPES\_CONTENT}} --- --- url: /frontends/packages/composables/useCustomerOrders.md --- # {{NAME}} {{DESCRIPTION}} {{ADDITIONAL\_README}} {{DEMO\_BLOCK}} ## Types {{INTERFACE\_CONTENT}} {{RETURN\_TYPES\_CONTENT}} --- --- url: /frontends/packages/composables/useCustomerPassword.md --- # {{NAME}} {{DESCRIPTION}} {{ADDITIONAL\_README}} {{DEMO\_BLOCK}} ## Types {{INTERFACE\_CONTENT}} {{RETURN\_TYPES\_CONTENT}} --- --- url: /frontends/packages/composables/useDefaultOrderAssociations.md --- # {{NAME}} {{DESCRIPTION}} {{ADDITIONAL\_README}} {{DEMO\_BLOCK}} ## Types {{INTERFACE\_CONTENT}} {{RETURN\_TYPES\_CONTENT}} --- --- url: /frontends/packages/composables/useInternationalization.md --- # {{NAME}} {{DESCRIPTION}} {{ADDITIONAL\_README}} {{DEMO\_BLOCK}} ## Types {{INTERFACE\_CONTENT}} {{RETURN\_TYPES\_CONTENT}} --- --- url: /frontends/packages/composables/useLandingSearch.md --- # {{NAME}} {{DESCRIPTION}} {{ADDITIONAL\_README}} {{DEMO\_BLOCK}} ## Types {{INTERFACE\_CONTENT}} {{RETURN\_TYPES\_CONTENT}} --- --- url: /frontends/packages/composables/useListing.md --- # {{NAME}} {{DESCRIPTION}} {{ADDITIONAL\_README}} {{DEMO\_BLOCK}} ## Types {{INTERFACE\_CONTENT}} {{RETURN\_TYPES\_CONTENT}} --- --- url: /frontends/packages/composables/useLocalWishlist.md --- # {{NAME}} {{DESCRIPTION}} {{ADDITIONAL\_README}} {{DEMO\_BLOCK}} ## Types {{INTERFACE\_CONTENT}} {{RETURN\_TYPES\_CONTENT}} --- --- url: /frontends/packages/composables/useNavigation.md --- # {{NAME}} {{DESCRIPTION}} {{ADDITIONAL\_README}} {{DEMO\_BLOCK}} ## Types {{INTERFACE\_CONTENT}} {{RETURN\_TYPES\_CONTENT}} --- --- url: /frontends/packages/composables/useNavigationContext.md --- # {{NAME}} {{DESCRIPTION}} {{ADDITIONAL\_README}} {{DEMO\_BLOCK}} ## Types {{INTERFACE\_CONTENT}} {{RETURN\_TYPES\_CONTENT}} --- --- url: /frontends/packages/composables/useNavigationSearch.md --- # {{NAME}} {{DESCRIPTION}} {{ADDITIONAL\_README}} {{DEMO\_BLOCK}} ## Types {{INTERFACE\_CONTENT}} {{RETURN\_TYPES\_CONTENT}} --- --- url: /frontends/packages/composables/useNewsletter.md --- # {{NAME}} {{DESCRIPTION}} {{ADDITIONAL\_README}} {{DEMO\_BLOCK}} ## Types {{INTERFACE\_CONTENT}} {{RETURN\_TYPES\_CONTENT}} --- --- url: /frontends/packages/composables/useNotifications.md --- # {{NAME}} {{DESCRIPTION}} {{ADDITIONAL\_README}} {{DEMO\_BLOCK}} ## Types {{INTERFACE\_CONTENT}} {{RETURN\_TYPES\_CONTENT}} --- --- url: /frontends/packages/composables/useOrderDetails.md --- # {{NAME}} {{DESCRIPTION}} {{ADDITIONAL\_README}} {{DEMO\_BLOCK}} ## Types {{INTERFACE\_CONTENT}} {{RETURN\_TYPES\_CONTENT}} --- --- url: /frontends/packages/composables/useOrderPayment.md --- # {{NAME}} {{DESCRIPTION}} {{ADDITIONAL\_README}} {{DEMO\_BLOCK}} ## Types {{INTERFACE\_CONTENT}} {{RETURN\_TYPES\_CONTENT}} --- --- url: /frontends/packages/composables/usePrice.md --- # {{NAME}} {{DESCRIPTION}} {{ADDITIONAL\_README}} {{DEMO\_BLOCK}} ## Types {{INTERFACE\_CONTENT}} {{RETURN\_TYPES\_CONTENT}} --- --- url: /frontends/packages/composables/useProduct.md --- # {{NAME}} {{DESCRIPTION}} {{ADDITIONAL\_README}} {{DEMO\_BLOCK}} ## Types {{INTERFACE\_CONTENT}} {{RETURN\_TYPES\_CONTENT}} --- --- url: /frontends/packages/composables/useProductAssociations.md --- # {{NAME}} {{DESCRIPTION}} {{ADDITIONAL\_README}} {{DEMO\_BLOCK}} ## Types {{INTERFACE\_CONTENT}} {{RETURN\_TYPES\_CONTENT}} --- --- url: /frontends/packages/composables/useProductConfigurator.md --- # {{NAME}} {{DESCRIPTION}} {{ADDITIONAL\_README}} {{DEMO\_BLOCK}} ## Types {{INTERFACE\_CONTENT}} {{RETURN\_TYPES\_CONTENT}} --- --- url: /frontends/packages/composables/useProductPrice.md --- # {{NAME}} {{DESCRIPTION}} {{ADDITIONAL\_README}} {{DEMO\_BLOCK}} ## Types {{INTERFACE\_CONTENT}} {{RETURN\_TYPES\_CONTENT}} --- --- url: /frontends/packages/composables/useProductReviews.md --- # {{NAME}} {{DESCRIPTION}} {{ADDITIONAL\_README}} {{DEMO\_BLOCK}} ## Types {{INTERFACE\_CONTENT}} {{RETURN\_TYPES\_CONTENT}} --- --- url: /frontends/packages/composables/useProductSearch.md --- # {{NAME}} {{DESCRIPTION}} {{ADDITIONAL\_README}} {{DEMO\_BLOCK}} ## Types {{INTERFACE\_CONTENT}} {{RETURN\_TYPES\_CONTENT}} --- --- url: /frontends/packages/composables/useProductSearchSuggest.md --- # {{NAME}} {{DESCRIPTION}} {{ADDITIONAL\_README}} {{DEMO\_BLOCK}} ## Types {{INTERFACE\_CONTENT}} {{RETURN\_TYPES\_CONTENT}} --- --- url: /frontends/packages/composables/useProductWishlist.md --- # {{NAME}} {{DESCRIPTION}} {{ADDITIONAL\_README}} {{DEMO\_BLOCK}} ## Types {{INTERFACE\_CONTENT}} {{RETURN\_TYPES\_CONTENT}} --- --- url: /frontends/packages/composables/useSalutations.md --- # {{NAME}} {{DESCRIPTION}} {{ADDITIONAL\_README}} {{DEMO\_BLOCK}} ## Types {{INTERFACE\_CONTENT}} {{RETURN\_TYPES\_CONTENT}} --- --- url: /frontends/packages/composables/useSessionContext.md --- # {{NAME}} {{DESCRIPTION}} {{ADDITIONAL\_README}} {{DEMO\_BLOCK}} ## Types {{INTERFACE\_CONTENT}} {{RETURN\_TYPES\_CONTENT}} --- --- url: /frontends/packages/composables/useShopwareContext.md --- # {{NAME}} {{DESCRIPTION}} {{ADDITIONAL\_README}} {{DEMO\_BLOCK}} ## Types {{INTERFACE\_CONTENT}} {{RETURN\_TYPES\_CONTENT}} --- --- url: /frontends/packages/composables/useSyncWishlist.md --- # {{NAME}} {{DESCRIPTION}} {{ADDITIONAL\_README}} {{DEMO\_BLOCK}} ## Types {{INTERFACE\_CONTENT}} {{RETURN\_TYPES\_CONTENT}} --- --- url: /frontends/packages/composables/useUrlResolver.md --- # {{NAME}} {{DESCRIPTION}} {{ADDITIONAL\_README}} {{DEMO\_BLOCK}} ## Types {{INTERFACE\_CONTENT}} {{RETURN\_TYPES\_CONTENT}} --- --- url: /frontends/packages/composables/useUser.md --- # {{NAME}} {{DESCRIPTION}} {{ADDITIONAL\_README}} {{DEMO\_BLOCK}} ## Types {{INTERFACE\_CONTENT}} {{RETURN\_TYPES\_CONTENT}} --- --- url: /frontends/packages/composables/useWishlist.md --- # {{NAME}} {{DESCRIPTION}} {{ADDITIONAL\_README}} {{DEMO\_BLOCK}} ## Types {{INTERFACE\_CONTENT}} {{RETURN\_TYPES\_CONTENT}} --- --- url: /frontends/resources/community-modules.md --- # πŸ€— Community Modules :::warning The modules listed here are not officially supported or maintained by Shopware. Please use them at your own risk. ::: The following section contains modules, plugins and other resources that are created and maintaned by the community. If you want to contribute to this list, please create a [pull request](https://github.com/shopware/frontends/pulls) or submit a new [idea](https://github.com/shopware/frontends/discussions/categories/ideas). --- --- url: /frontends/resources/troubleshooting.md --- # 😱 Troubleshooting Collection of common issues you may run into while working with Shopware Composable Frontends. If you need help or have other questions, feel free to join the [frontends Discord channel](https://discord.com/channels/1308047705309708348/1405501315160739951/archives/C050L6NCMGQ). ## Which SalesChannel type to use for Composable Frontends? Currently you should use the default **Storefront SalesChannel type**. This sounds wrong, but if you using the Headless SalesChannel type you will not have nice speaking seo urls at the moment. Because the generation of seo urls will only be executed for SalesChannels with the type Storefront. We working on a more flexible solution with the core team to not have this confusion in the future. ## The access token for the store API is public visible? In general, the store API should only output content that would also be visible on a standard storefront. Therefore, do not output any sensitive data to the store API. For our vue-starter-template, we decided to use a public access token, also to have a simple configuration. However, this does not mean that you should do the same in a production environment. To secure your access token, you can use [proxy api requests](#proxy-api-requests) also have a look at our [community modules](../resources/community-modules/) how others are doing this. ## How to use https for your localhost with Composable Frontends? ### Option 1: Manual with mkcert * Make sure you have `mkcert` installed on your system. Otherwise, follow [here](https://github.com/FiloSottile/mkcert) to set it up. * Create a valid certificate in your project folder by running `mkcert localhost`. * Update the `nuxt dev` command in your `package.json`.\ It should look like this: `NODE_TLS_REJECT_UNAUTHORIZED=0 nuxt dev --https --ssl-cert localhost.pem --ssl-key localhost-key.pem` * Now run your project with `npm run dev` or `pnpm run dev` from your project root. * Your browser may ask you to accept the risk when you visit `https://localhost:3000`. This is because it is a self-signed certificate. ### Option 2: Vite plugin * Execute `pnpm add -D @vitejs/plugin-basic-ssl` in your project folder * Edit your `nuxt.config.ts` file and add: ```ts import basicSsl from '@vitejs/plugin-basic-ssl' // https://v3.nuxtjs.org/docs/directory-structure/nuxt.config export default defineNuxtConfig({ // ... devServer: { https: true, }, vite: { plugins: [ basicSsl(), ], }, // ... ``` * Start your dev server with `pnpm run dev` * Your browser may ask you to accept the risk when you visit `https://localhost:3000`. This is because it is a self-signed certificate. ## SSR throws error in local environment with DDEV? If you are using DDEV as a local environment with SSR = true (Nuxt config for routes) and you always get a 500 error message that the context is not provided for category, you may have a problem with the SSL certificate. Try to use `NODE_TLS_REJECT_UNAUTHORIZED = 0` in [.env file](https://nuxt.com/docs/guide/directory-structure/env) (this is a issue with self-signed certificates). To validate if this is your problem: Connect the local Frontend with a valid SSL from a cloud instance and check it against this instance. Also check if you can reach any local store API endpoint with some API client. ## 412 error page during local development? The HTTP status code 412 (Precondition Failed) usually means in the Shopware `store API` context that the specified `accessToken` is incorrect or not correct for the specified `endpoint`. Check your `nuxt.config.ts` file, if you do not see an error, please try connecting directly to your `store API` endpoint using an API client. ```ts // a part of nuxt.config.ts shopware: { accessToken: "SWSCBHFSNTVMAWNZDNFKSHLAYW", // access token for corresponding sales channel endpoint: "https://demo-frontends.shopware.store/store-api/", // endpoint where store-api is available devStorefrontUrl: "https://demo-frontends.shopware.store", // see section below }, ``` ## What is `devStorefrontUrl` and when to use it? The `devStorefrontUrl` configuration option is primarily used for **customer registration** functionality. The Shopware registration endpoint requires a `storefrontUrl` parameter in its payload to identify which sales channel domain the customer is registering from. ### Why is it needed? By default, the application uses `window.location.origin` (e.g., `https://your-store.com`) to determine the storefront URL. However, this fails in certain scenarios: * **Local development** - Your browser origin is `http://localhost:3000`, which doesn't match any configured sales channel domain * **Separate frontend/API hosting** - When your frontend runs on a different domain than what's configured in Shopware ### How to configure it Set `devStorefrontUrl` to a domain that is configured in your Shopware admin under **Sales Channel β†’ Domains**: ```ts // nuxt.config.ts export default defineNuxtConfig({ runtimeConfig: { public: { shopware: { endpoint: "https://your-shop.shopware.store/store-api", accessToken: "your-access-token", devStorefrontUrl: "https://your-shop.shopware.store", // must match a domain in Sales Channel settings }, }, }, }); ``` Or use an environment variable: ```bash NUXT_PUBLIC_SHOPWARE_DEV_STOREFRONT_URL=https://your-shop.shopware.store ``` :::tip If customer registration works in production but fails locally, `devStorefrontUrl` is likely the solution. Set it to your production storefront domain during local development. ::: ## Access from origin 127.0.0.1:3000 has been blocked by CORS policy Depending on your server, you may need to set the `Access-Control-Allow-Origin` header to access your server from an external origin. And yes, your local development server is also an external origin in this case. Also, have a look at this [documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors/CORSMissingAllowOrigin) from MDN. ## Proxy API requests If you're encountering issues related to Cross-Origin Resource Sharing (CORS) or if you wish to conceal the backend API URL, you can use Vite's proxy mechanism ### Nuxt example Edit your `nuxt.config.ts` file and add: ``` vite: { server: { proxy: { "/store-api": { target: "", changeOrigin: true, secure: false, }, }, }, }, ``` Modify the Shopware API endpoint to match your local frontend URL. ``` { ... shopware: { endpoint: "store-api/", ... } } ``` ## Broadcasting and BFCache Compatibility ### Issue When Broadcasting is enabled, the BFCache (Back-Forward Cache) functionality is not operational. This incompatibility can lead to suboptimal performance and user experience when navigating back and forth between pages. ### Resolution (vue-demo template) To leverage the benefits of BFCache, we have decided to disable Broadcasting. By turning off Broadcasting, we ensure that the BFCache can function correctly, providing a smoother and faster navigation experience for users. ``` ... runtimeConfig: { broadcasting: true, }, ... ``` ### Additional Information BFCache is a browser optimization that allows pages to be stored in memory, enabling instant loading when users navigate back or forward. While Broadcasting is useful for real-time updates, its current implementation conflicts with BFCache. Disabling Broadcasting allows us to prioritize the performance improvements offered by BFCache. For more details on BFCache, refer to the [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/Performance/Navigation_and_resource_timing#bfcache), [WHATWG](https://github.com/whatwg/html/issues/7253) ## CORS (Cross-Origin Resource Sharing) Issues See the [CORS](./troubleshooting/CORS) page for more information on how to handle CORS issues in your project. ## \[unimport] failed to find "createShopwareContext" imported from "#imports" ### Problem This error occurs when `@shopware/nuxt-module` is added to your project, but `@shopware/composables/nuxt-layer` is not extended in your Nuxt configuration. ### Why it happens The `@shopware/nuxt-module` plugin imports `createShopwareContext` from the `#imports` alias. The `@shopware/composables/nuxt-layer` is responsible for configuring Nuxt's auto-import system and TypeScript paths to make composables exports available via `#imports`. When you use Nuxt layers, the layer system merges TypeScript configuration files from both the composables layer and your project. This merge adds the composables exports to the `#imports` alias scope. Without extending the composables layer, these exports are not available, causing the import error. ### Solution Extend `@shopware/composables/nuxt-layer` in your `nuxt.config.ts`: ```ts // nuxt.config.ts export default defineNuxtConfig({ extends: ["@shopware/composables/nuxt-layer"], modules: ["@shopware/nuxt-module"], // ... rest of your configuration }); ``` :::tip If you're using `@shopware/cms-base-layer`, you can extend both layers together: ```ts extends: [ "@shopware/composables/nuxt-layer", "@shopware/cms-base-layer", "@shopware/unocss-design-tokens-layer" ], ``` ::: ### Additional Information * The `@shopware/composables/nuxt-layer` sets up auto-imports for all composables from the `src` directory * It also configures TypeScript path aliases (`#imports` and `#shopware`) that are required by the nuxt-module * Always extend the composables layer when using `@shopware/nuxt-module` in your project --- --- url: /frontends/resources/links.md --- # πŸš€ Links ::: tip Do we miss some Link? πŸ˜Άβ€πŸŒ«οΈ Please tell us via [Community Discord](https://discord.com/channels/1308047705309708348/1405501315160739951/archives/C050L6NCMGQ), so we can add it. ::: ## Blog posts Sorted by date, newest first * [Unofficial API aware guidelines for Shopware 6](https://www.brocksi.net/blog/unofficial-api-aware-guidelines-shopware-6/) * [How to work with Shopware Frontends: Experience creating POC](https://itdelight.io/how-to-work-with-shopware-frontends-experience-creating-poc/) * [Multi-Page or Single-Page Variants Selection](https://www.brocksi.net/blog/variants-selection-multi-page-or-single-page/) * [Create a CI/CD Pipeline for Shopware Frontends](https://kiplingi.de/create-a-ci-cd-pipeline-for-shopware-frontends/) * [Gross and Net-Switch for B2B and B2C Shops built with Composable Frontends](https://dev.to/shopware/gross-and-net-switch-for-b2b-and-b2c-shops-built-with-composable-frontends-2b24) * [Komponentenbasierte Entwicklung mit dem Shopware Frontends Framework](https://sitegeist.de/blog/e-commerce/komponentenbasierte-entwicklung-mit-dem-shopware-frontends-framework.html) * [Frontend API with Nuxt + Nitro = Flexibility πŸ™](https://www.brocksi.net/blog/frontend-api-with-nuxt-and-nitro-will-lead-to-flexibility/) * [Remixing the Shopware Checkout (Part 1)](https://elkmod.dev/blogs/remixing-shopware-checkout) * [Create a vue.js composable and call any API within Shopware Frontends](https://www.brocksi.net/blog/vue-js-composable-call-api-shopware-frontends/) * [The future of Shopware PWA](https://www.shopware.com/de/news/the-future-of-shopware-pwa/) * [Frontends - yet another storefront?](https://www.shopware.com/en/news/frontends-yet-another-storefront/) ## Presentations * [Quick-Start Composable Frontends - Shopware Boostday 2023](https://ecommerce.shopware.com/hubfs/Boost%20Days/Quick%20Start%20-%20Shopware%20Composable%20Frontends.pdf) (PDF) ## Videos Sorted by date, newest first * [Developer Brunch April 2024 - "Composable frontends"](https://www.youtube.com/watch?v=Tz-86f72cDk) * [Going Headless - One Page Shop mit Shopware & Nuxt πŸš€ | shopware x synaigy Meetup](https://www.youtube.com/watch?v=RXaNWRMuea8) * [Performance Improvements Headless Shopware Frontends - Niklas Wolf, Mothership GmbH](https://www.youtube.com/watch?v=GhniPTMtIt8) * [Ein Microstore mit Shopware Frontends | Shopware Meetup der Mothership GmbH in MΓΌnchen](https://www.youtube.com/watch?v=Dal-z94WLCk) * [Shopware Composable Frontends in Action - A real-life example with Miriam MΓΌller](https://www.youtube.com/watch?v=AClnII3-GhQ) * [Quick-Start Composable Frontends - Shopware Boostday 2023](https://www.youtube.com/watch?v=2AwLWvPOffw) * [Shopware Composable Frontends: An interview. What is it, when to use it?](https://www.youtube.com/watch?v=A_O2nke4yoo) * [Your new tool: Composable Frontends | #SCD23](https://www.youtube.com/watch?v=hN3t96zVfpw) * [Shopware’s Vue.js framework for building custom storefronts](https://www.youtube.com/watch?v=0W_3xWIpYho) * [Ramona Schwering - Ecommerce as easy as an UI component - Vuejs Amsterdam 2023](https://www.youtube.com/watch?v=VivLHGGds6c) * [shopcast.fm Folge 38 - "Frontends" Revisited](https://www.youtube.com/watch?v=eW9-jrXx4wA) * [shopcast.fm Folge 37 - Projekt Shopware "Frontends"](https://www.youtube.com/watch?v=vupiRTNoePU) ## Code examples * [Multi sales channel support Nuxt plugin](https://github.com/shopware/frontends/tree/main/examples/multi-sales-channel) * [Multi-Instances Repo Example for Composable Frontends](https://github.com/patzick/frontends-multiinstances-example) * Language/Translation Switch [StackBlitz](https://stackblitz.com/github/mkucmus/language-translations?file=app.vue) / [GitHub](https://github.com/mkucmus/language-translations) ::: tip More Code examples Check the [examples folder](https://github.com/shopware/frontends/tree/main/examples) in our Frontends Repository. ::: --- --- url: /frontends/integrations.md --- # πŸ› οΈ Integrations This is your go-to resource for seamlessly incorporating various platforms into Shopware Composable Frontends. Explore the following sub-pages to find detailed instructions on integrating different systems, ensuring a harmonious and efficient synergy between your Shopware store and diverse external services. ## Overview --- --- url: /docs/resources/guidelines/code/core/6.5-new-php-language-features.md --- ::: info This document represents core guidelines and has been mirrored from the core in our Shopware 6 repository. You can find the original version [here](https://github.com/shopware/shopware/blob/trunk/coding-guidelines/core/6.5-new-php-language-features.md) ::: # 2023-05-16 - PHP 8.1 & Symfony 6.1 new features ## Context As of Shopware 6.5 the minimum version of PHP is 8.1 and the minimum version of Symfony is 6.1. We would like to *promote* the usage of the newly available features. Many of the new features allow us to reduce boilerplate, make it easier to prevent common mistakes, improve refactoring support, increase legibility, perform faster and so on. By using the latest features we allow the reader and writer of code to focus on the domain rather than the language. ## PHP 8.0 & 8.1 new features ### Promoted Properties * [PHP Docs](https://www.php.net/manual/en/language.oop5.decon.php#language.oop5.decon.constructor.promotion) * [PHP Watch](https://php.watch/versions/8.0/constructor-property-promotion) *We have automatically refactored all existing code to use Promoted Properties using Rector.* Promoted properties allow us to reduce the boilerplate when defining classes, by removing the need to type the property name four times and the type twice. Class properties, with their visibility and flags can now be specified entirely in the constructor. From: ```php class Point { private int $x; private int $y; public function __construct(int $x, int $y) { $this->x = $x; $this->y = $y; } } ``` To: ```php class Point { public function __construct(private int $x, private int $y) { } } ``` Note: It is still possible to use normal property definitions/assignments with promoted properties. For example, if you need to manipulate some dependencies. Advantages: * Less code, less duplication. * Better refactoring. #### Backwards Compatibility / Migration Strategy Migrating to promoted properties does not represent a breaking change. ### New in initializers * [PHP Docs](https://www.php.net/manual/en/language.oop5.decon.php#language.oop5.decon.constructor.new) It is now possible to specify an object as a default parameter value in a function/method. Previously it was only possible to specify scalar values. From: ```php class PasswordHasher { private Hasher $hasher; public function __construct(private Hasher $hasher = null) { $this->hasher = $hasher ?? new Bcrypt(); } } ``` To: ```php class PasswordHasher { public function __construct(private Hasher $hasher = new Bcrypt()) {} } ``` Advantages: * Less code * More consistent #### Backwards Compatibility / Migration Strategy Migrating to inline object default parameters does not represent a breaking change. ### Match * [PHP Docs](https://www.php.net/manual/en/control-structures.match.php) * [PHP Watch](https://php.watch/versions/8.0/match-expression) *We have automatically refactored all existing code to use match instead of switch using Rector.* In most cases, `switch` statements can be replaced with `match` statements: * Match uses strict equality unlike switch which uses weak comparison and can lead to subtle bugs. * Each match arm does not fall through without a break statement, unlike switch. * Match expressions must be exhaustive, if there is no default arm specified, and no arm matches the given value, an `UnhandledMatchError` is thrown. * Match is an expression and thus returns a value, reducing unnecessary variables and reducing the risk of accessing undefined variables. From: ```php switch ($statusCode) { case 200: case 300: $message = null; break; case 400: $message = 'not found'; break; case 500: $message = 'server error'; break; default: $message = 'unknown status code'; break; } ``` To: ```php $message = match ($statusCode) { 200, 300 => null, 400 => 'not found', 500 => 'server error', default => 'unknown status code', }; ``` Note: Conditions can be combined in a much simpler fashion. #### Backwards Compatibility / Migration Strategy There are cases where migrating from a switch to a match could case a BC break. For example switch performs lose type checks and throws an exception for unhandled values. When migrating code, be sure to check that values are the correct types and that all cases are handled. ### New string functions * `str_contains` * `str_starts_with` * `str_ends_with` Advantages: * Simpler and more concise. * Saner return types. * It is harder to get their usage wrong, for example checking for 0 vs false with `strpos`. * The functions are faster, being implemented in C. * The operations require less function calls, for example no usages of strlen are required. ### Named arguments * [PHP Docs](https://www.php.net/manual/en/functions.arguments.php) * [PHP Watch](https://php.watch/versions/8.0/named-parameters) Named arguments are useful when calling code with bad and/or large API's. For example, many of PHP's global functions. In terms of calling bad PHP API's, the following advantages apply: * It is possible to skip defaults in between the arguments you want to change. * The code is better documented since the argument label is specified with the value, very useful for boolean flags. From: ```php htmlspecialchars($string, ENT_COMPAT | ENT_HTML, 'UTF-8', false); ``` To: ```php htmlspecialchars($string, double_encode: false); ``` Note: The second argument is not changed, but in the first example we must provide the default value, in order to change the double encode flag. #### Backwards Compatibility / Migration Strategy We do not want to use named parameters when calling Shopware API's as parameter names are not a part of the Backwards compatability promise. Named parameters should only be used when calling PHP API's. ### Type improvements * [PHP Docs](https://www.php.net/manual/en/language.types.type-system.php) * [PHP Watch - Union Types](https://php.watch/versions/8.0/union-types) * [PHP Watch - Mixed Type](https://php.watch/versions/8.0/mixed-type) * [PHP Watch - Intersection Types](https://php.watch/versions/8.1/intersection-types) **It will now only be necessary to reach for @var & @param annotations when defining array shapes, generics and more specific types such as `class-string`, `positive-int` etc. Everything else should be natively typed.** When a type can really be any value, this can now be expressed as `mixed`. When a type can be multiple, but not all, this can now be expressed as a union type, eg: `int|string`. When a type must be an intersection of multiple types, this can now be expressed as an intersection type, eg: `MyService&MockObject`. These improvements come with various advantages: * The types are enforced by PHP, so TypeError's will be thrown when attempting to pass non-valid types. * It allows us to move more type information from phpdoc into function signatures. * It prevents incorrect function information. phpdocs can often become stale when they are not updated with the function itself. ### Enums * [PHP Docs](https://www.php.net/manual/en/language.types.enumerations.php) * [PHP Watch](https://php.watch/versions/8.1/enums) PHP finally has native support for enumerations, with various advantages over common userland packages and using const's. Enums are useful where we have a predefined list of constant values. It's now not necessary to provide values as constants, and it's not necessary to create arrays of the constants to check validity. From: ```php class Indexer { public const PARTIAL = 'partial'; public const FULL = 'full'; public function product(int $id, string $method): void { if (!in_array($method, [self::PARTIAL, self::FULL], true)) { throw new \InvalidArgumentException(); } match ($method) { self::PARTIAL => $this->partial($id), self::FULL => $this->full($id) }; } } ``` To: ```php enum IndexMethod { case PARTIAL; case FULL; } class Indexer { public function product(int $id, IndexMethod $method): void { match ($method) { IndexMethod::PARTIAL => $this->partial($id), IndexMethod::FULL => $this->full($id) }; } } ``` Advantages: * Works great with `match` - an `UnhandledMatchError` exception will be thrown if there is no match arm for a given enum case. * Can type hint on an enum. * No need to validate a case. * Can provide backed values and serialize/unserialize with `MyEnum::from()` && `MyEnum::tryFrom()`. * Enums can provide methods and implement interfaces. * Better comparison features, e.g. Enums are singletons. #### Backwards Compatibility / Migration Strategy See the [Use PHP 8.1 Enums](../../../references/adr/2023-05-16-php-enums) ADR for the decision and migration strategy. ### Readonly properties * [PHP Docs](https://www.php.net/manual/en/language.oop5.properties.php#language.oop5.properties.readonly-properties) * [PHP Watch](https://php.watch/versions/8.1/readonly) Readonly properties are very useful when building DTOs. When you want to communicate a payload to a system or service, `readonly` properties allow us to create immutable data structures with a lot less code. In conjunction with promoted properties, we can reduce the boilerplate of a class significantly. Consider a product reindex command: From: ```php class ProductReindexCommand { private int $productId; private bool $includeStock: public function __construct(int $productId, bool $includeStock) { $this->productId = $productId; $this->includeStock = $includeStock; } public function getProductId(): int { return $this->productId; } public function includeStock(): bool { return $this->includeStock; } } ``` To: ```php class ProductReindexCommand { public function __construct(public readonly int $productId, public readonly bool $includeStock) { } } ``` In the first example, we use private properties to prohibit updates and public getters to allow access to the data. In the second we change the properties to `public` to allow access to the data without getters, but use `readonly` to prohibit updates. We also use promoted properties to make it even more succinct. Advantages: * Reduced boilerplate. * Make the intent of code clearer. #### Backwards Compatibility / Migration Strategy All private properties, which are not written to after instantiation can successfully be migrated to `readonly` without BC breaks. New code can use `readonly` on public and protected properties, but for existing code, that would be a BC break. ### First-class callable syntax * [PHP Docs](https://www.php.net/manual/en/functions.first_class_callable_syntax.php) * [PHP Watch](https://php.watch/versions/8.1/first-class-callable-syntax) This is a new method of referencing callables with strings and arrays. It allows for improved refactoring support, better static analysis and fixes some subtle bugs with scope. Consider an operation to find the longest string in an array, you might use `strlen` within an `array_map`: From: ```php $longest = max(array_map('strlen', $strings)); ``` To: ```php $longest = max(array_map(strlen(...), $strings)); ``` Instead of using an arbitrary string as a reference to a function, we can now use the `(...)` syntax to create a callable. It can also be used with object methods, instance or static, e.g.: ```php $callable = $object->doCoolStuff(...); $callable = \My\Object::doCoolStuff(...); ``` Advantages: * Refactoring support. * Better static analyses. * Fixes scope issues. ### Attributes * [PHP Docs](https://www.php.net/manual/en/language.attributes.overview.php) * [PHP Watch](https://php.watch/versions/8.0/attributes) *We have automatically refactored all existing code to use attributes instead of annotations using Rector.* It is now possible to use native PHP attributes to store structured metadata, rather than using the error-prone PHP docblock. For us Shopware developers, this will be most useful in conjunction with Symfony bundled attributes, which allow us to configure services and routes directly in controllers and services. From: ```php namespace App\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Routing\Annotation\Route; class BlogController extends AbstractController { /** * @Route("/blog", name="blog_list") */ public function list(): Response { // ... } } ``` To: ```php namespace App\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Routing\Annotation\Route; class BlogController extends AbstractController { #[Route('/blog', name: 'blog_list')] public function list(): Response { // ... } } ``` Advantages: * Add Metadata to classes, methods, properties, arguments and so on. * They can replace PHP doc blocks, each with custom parsers and rules to a unified standard supported by PHP. * Type safety & autocompletion. * The data can be introspected using PHP's Reflection API's. ### Nullsafe operator * [PHP Docs](https://www.php.net/manual/en/language.oop5.basic.php#language.oop5.basic.nullsafe) * [PHP Watch](https://php.watch/versions/8.0/null-safe-operator) The nullsafe operator works the same as property or method accesses, except that if the object being dereferenced is null then null will be returned rather than an exception thrown. If the dereference is part of a chain, the rest of the chain is skipped. Put another way; it allows chaining multiple property or method accesses on an object, without first checking if each returned value is null before proceeding. Consider the following code: ```php class User { public string $firstName; public string $lastName; public ?int $age = null; public ?Address $address = null; } class Address { public int $number; public string $addressLine1; public ?string $addressLine2 = null; } ``` Pre PHP 8.0, in order to access `addressLine2` for an address of a user, it would be necessary to write the following code: ```php $user = new User(/** */); $address = $user->address; if ($address !== null) { $addressLine2 = $address->addressLine2; if ($addressLine2 !== null) { //do something } } ``` Instead, we can now write: ```php $user = new User(/** */); $addressLine2 = $user?->address?->addressLine2; if ($addressLine2 !== null) { //do something } ``` Advantages: * Much less code for simple operations where null is a valid value. * If the operator is part of a chain, anything to the right of the null will not be executed, the statements will be short-circuited. * Can be used on methods where null coalescing cannot `$user->getCreatedAt()->format('d-m-Y') ?? null` where `getCreatedAt()` could return `null` or a `\DateTime` instance. ### Other * `never` return type: https://www.php.net/manual/en/language.types.never.php * `array_is_list` function: https://www.php.net/manual/en/function.array-is-list.php * `final const X` final for class constants: https://www.php.net/manual/en/language.oop5.final.php * `$object::class` instead of `get_class($object)`: https://wiki.php.net/rfc/class\_name\_literal\_on\_object * Array unpacking with string keys is now supported: https://www.php.net/manual/en/language.types.array.php#language.types.array.unpacking There are many more changes, including deprecations and backwards compatibility breaks. Please read the official announcement pages for both PHP 8.0 & PHP 8.1 for a deeper understanding: * [PHP 8.0](https://www.php.net/releases/8.0/en.php) * [PHP 8.0 - PHP Watch](https://php.watch/versions/8.0) * [PHP 8.1](https://www.php.net/releases/8.1/en.php) * [PHP 8.1 - PHP Watch](https://php.watch/versions/8.1) ## Symfony 6.1 new features ### Enums in route definitions [Symfony Blog](https://symfony.com/blog/new-in-symfony-6-1-improved-routing-requirements-and-utf-8-parameters) We can specify route parameters which will be validated against a given enums cases: ```php use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Requirement\EnumRequirement; #[Route('/foo/{bar}', requirements: ['bar' => new EnumRequirement(SomeEnum::class)])] ``` ### Service autowiring attributes [Symfony Blog](https://symfony.com/blog/new-in-symfony-6-1-service-autowiring-attributes) We can now wire up dependencies without touching XML. It is possible to define the required services directly in the class: ```php use Symfony\Component\DependencyInjection\Attribute\Autowire; class Mailer { public function __construct( #[Autowire(service: 'email_adapter')] private Adapter $adapter, #[Autowire('%kernel.debug_mode%')] private bool $debugMode, ) {} } ``` Further to that, we can decorate services with attributes: https://symfony.com/blog/new-in-symfony-6-1-service-decoration-attributes #### Backwards Compatibility / Migration Strategy See the [Symfony Dependency Management](../../../references/adr/_superseded/2023-05-16-symfony-dependency-management) ADR for the decision and migration strategy. ### Improved console autocompletion [Symfony Blog](https://symfony.com/blog/new-in-symfony-6-1-improved-console-autocompletion) Autocompletion values can now be defined directly in the command input definition, as the 5th parameter, for both arguments and inputs: ```php public function configure(): void { $this->addArgument( 'features', InputArgument::REQUIRED | InputArgument::IS_ARRAY, 'The features to enable', null, fn () => self::availableFeatures() ); } ``` --- --- url: /docs/v6.5/resources/guidelines/code/core/6.5-new-php-language-features.md --- ::: info This document represents core guidelines and has been mirrored from the core in our Shopware 6 repository. You can find the original version [here](https://github.com/shopware/shopware/blob/trunk/coding-guidelines/core/6.5-new-php-language-features.md) ::: # 2023-05-16 - PHP 8.1 & Symfony 6.1 new features ## Context As of Shopware 6.5 the minimum version of PHP is 8.1 and the minimum version of Symfony is 6.1. We would like to *promote* the usage of the newly available features. Many of the new features allow us to reduce boilerplate, make it easier to prevent common mistakes, improve refactoring support, increase legibility, perform faster and so on. By using the latest features we allow the reader and writer of code to focus on the domain rather than the language. ## PHP 8.0 & 8.1 new features ### Promoted Properties * [PHP Docs](https://www.php.net/manual/en/language.oop5.decon.php#language.oop5.decon.constructor.promotion) * [PHP Watch](https://php.watch/versions/8.0/constructor-property-promotion) *We have automatically refactored all existing code to use Promoted Properties using Rector.* Promoted properties allow us to reduce the boilerplate when defining classes, by removing the need to type the property name four times and the type twice. Class properties, with their visibility and flags can now be specified entirely in the constructor. From: ```php class Point { private int $x; private int $y; public function __construct(int $x, int $y) { $this->x = $x; $this->y = $y; } } ``` To: ```php class Point { public function __construct(private int $x, private int $y) { } } ``` Note: It is still possible to use normal property definitions/assignments with promoted properties. For example, if you need to manipulate some dependencies. Advantages: * Less code, less duplication. * Better refactoring. #### Backwards Compatibility / Migration Strategy Migrating to promoted properties does not represent a breaking change. ### New in initializers * [PHP Docs](https://www.php.net/manual/en/language.oop5.decon.php#language.oop5.decon.constructor.new) It is now possible to specify an object as a default parameter value in a function/method. Previously it was only possible to specify scalar values. From: ```php class PasswordHasher { private Hasher $hasher; public function __construct(private Hasher $hasher = null) { $this->hasher = $hasher ?? new Bcrypt(); } } ``` To: ```php class PasswordHasher { public function __construct(private Hasher $hasher = new Bcrypt()) {} } ``` Advantages: * Less code * More consistent #### Backwards Compatibility / Migration Strategy Migrating to inline object default parameters does not represent a breaking change. ### Match * [PHP Docs](https://www.php.net/manual/en/control-structures.match.php) * [PHP Watch](https://php.watch/versions/8.0/match-expression) *We have automatically refactored all existing code to use match instead of switch using Rector.* In most cases, `switch` statements can be replaced with `match` statements: * Match uses strict equality unlike switch which uses weak comparison and can lead to subtle bugs. * Each match arm does not fall through without a break statement, unlike switch. * Match expressions must be exhaustive, if there is no default arm specified, and no arm matches the given value, an `UnhandledMatchError` is thrown. * Match is an expression and thus returns a value, reducing unnecessary variables and reducing the risk of accessing undefined variables. From: ```php switch ($statusCode) { case 200: case 300: $message = null; break; case 400: $message = 'not found'; break; case 500: $message = 'server error'; break; default: $message = 'unknown status code'; break; } ``` To: ```php $message = match ($statusCode) { 200, 300 => null, 400 => 'not found', 500 => 'server error', default => 'unknown status code', }; ``` Note: Conditions can be combined in a much simpler fashion. #### Backwards Compatibility / Migration Strategy There are cases where migrating from a switch to a match could case a BC break. For example switch performs lose type checks and throws an exception for unhandled values. When migrating code, be sure to check that values are the correct types and that all cases are handled. ### New string functions * `str_contains` * `str_starts_with` * `str_ends_with` Advantages: * Simpler and more concise. * Saner return types. * It is harder to get their usage wrong, for example checking for 0 vs false with `strpos`. * The functions are faster, being implemented in C. * The operations require less function calls, for example no usages of strlen are required. ### Named arguments * [PHP Docs](https://www.php.net/manual/en/functions.arguments.php) * [PHP Watch](https://php.watch/versions/8.0/named-parameters) Named arguments are useful when calling code with bad and/or large API's. For example, many of PHP's global functions. In terms of calling bad PHP API's, the following advantages apply: * It is possible to skip defaults in between the arguments you want to change. * The code is better documented since the argument label is specified with the value, very useful for boolean flags. From: ```php htmlspecialchars($string, ENT_COMPAT | ENT_HTML, 'UTF-8', false); ``` To: ```php htmlspecialchars($string, double_encode: false); ``` Note: The second argument is not changed, but in the first example we must provide the default value, in order to change the double encode flag. #### Backwards Compatibility / Migration Strategy We do not want to use named parameters when calling Shopware API's as parameter names are not a part of the Backwards compatability promise. Named parameters should only be used when calling PHP API's. ### Type improvements * [PHP Docs](https://www.php.net/manual/en/language.types.type-system.php) * [PHP Watch - Union Types](https://php.watch/versions/8.0/union-types) * [PHP Watch - Mixed Type](https://php.watch/versions/8.0/mixed-type) * [PHP Watch - Intersection Types](https://php.watch/versions/8.1/intersection-types) **It will now only be necessary to reach for @var & @param annotations when defining array shapes, generics and more specific types such as `class-string`, `positive-int` etc. Everything else should be natively typed.** When a type can really be any value, this can now be expressed as `mixed`. When a type can be multiple, but not all, this can now be expressed as a union type, eg: `int|string`. When a type must be an intersection of multiple types, this can now be expressed as an intersection type, eg: `MyService&MockObject`. These improvements come with various advantages: * The types are enforced by PHP, so TypeError's will be thrown when attempting to pass non-valid types. * It allows us to move more type information from phpdoc into function signatures. * It prevents incorrect function information. phpdocs can often become stale when they are not updated with the function itself. ### Enums * [PHP Docs](https://www.php.net/manual/en/language.types.enumerations.php) * [PHP Watch](https://php.watch/versions/8.1/enums) PHP finally has native support for enumerations, with various advantages over common userland packages and using const's. Enums are useful where we have a predefined list of constant values. It's now not necessary to provide values as constants, and it's not necessary to create arrays of the constants to check validity. From: ```php class Indexer { public const PARTIAL = 'partial'; public const FULL = 'full'; public function product(int $id, string $method): void { if (!in_array($method, [self::PARTIAL, self::FULL], true)) { throw new \InvalidArgumentException(); } match ($method) { self::PARTIAL => $this->partial($id), self::FULL => $this->full($id) }; } } ``` To: ```php enum IndexMethod { case PARTIAL; case FULL; } class Indexer { public function product(int $id, IndexMethod $method): void { match ($method) { IndexMethod::PARTIAL => $this->partial($id), IndexMethod::FULL => $this->full($id) }; } } ``` Advantages: * Works great with `match` - an `UnhandledMatchError` exception will be thrown if there is no match arm for a given enum case. * Can type hint on an enum. * No need to validate a case. * Can provide backed values and serialize/unserialize with `MyEnum::from()` && `MyEnum::tryFrom()`. * Enums can provide methods and implement interfaces. * Better comparison features, e.g. Enums are singletons. #### Backwards Compatibility / Migration Strategy See the [Use PHP 8.1 Enums](../../../references/adr/2023-05-16-php-enums) ADR for the decision and migration strategy. ### Readonly properties * [PHP Docs](https://www.php.net/manual/en/language.oop5.properties.php#language.oop5.properties.readonly-properties) * [PHP Watch](https://php.watch/versions/8.1/readonly) Readonly properties are very useful when building DTOs. When you want to communicate a payload to a system or service, `readonly` properties allow us to create immutable data structures with a lot less code. In conjunction with promoted properties, we can reduce the boilerplate of a class significantly. Consider a product reindex command: From: ```php class ProductReindexCommand { private int $productId; private bool $includeStock: public function __construct(int $productId, bool $includeStock) { $this->productId = $productId; $this->includeStock = $includeStock; } public function getProductId(): int { return $this->productId; } public function includeStock(): bool { return $this->includeStock; } } ``` To: ```php class ProductReindexCommand { public function __construct(public readonly int $productId, public readonly bool $includeStock) { } } ``` In the first example, we use private properties to prohibit updates and public getters to allow access to the data. In the second we change the properties to `public` to allow access to the data without getters, but use `readonly` to prohibit updates. We also use promoted properties to make it even more succinct. Advantages: * Reduced boilerplate. * Make the intent of code clearer. #### Backwards Compatibility / Migration Strategy All private properties, which are not written to after instantiation can successfully be migrated to `readonly` without BC breaks. New code can use `readonly` on public and protected properties, but for existing code, that would be a BC break. ### First-class callable syntax * [PHP Docs](https://www.php.net/manual/en/functions.first_class_callable_syntax.php) * [PHP Watch](https://php.watch/versions/8.1/first-class-callable-syntax) This is a new method of referencing callables with strings and arrays. It allows for improved refactoring support, better static analysis and fixes some subtle bugs with scope. Consider an operation to find the longest string in an array, you might use `strlen` within an `array_map`: From: ```php $longest = max(array_map('strlen', $strings)); ``` To: ```php $longest = max(array_map(strlen(...), $strings)); ``` Instead of using an arbitrary string as a reference to a function, we can now use the `(...)` syntax to create a callable. It can also be used with object methods, instance or static, e.g.: ```php $callable = $object->doCoolStuff(...); $callable = \My\Object::doCoolStuff(...); ``` Advantages: * Refactoring support. * Better static analyses. * Fixes scope issues. ### Attributes * [PHP Docs](https://www.php.net/manual/en/language.attributes.overview.php) * [PHP Watch](https://php.watch/versions/8.0/attributes) *We have automatically refactored all existing code to use attributes instead of annotations using Rector.* It is now possible to use native PHP attributes to store structured metadata, rather than using the error-prone PHP docblock. For us Shopware developers, this will be most useful in conjunction with Symfony bundled attributes, which allow us to configure services and routes directly in controllers and services. From: ```php namespace App\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Routing\Annotation\Route; class BlogController extends AbstractController { /** * @Route("/blog", name="blog_list") */ public function list(): Response { // ... } } ``` To: ```php namespace App\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Routing\Annotation\Route; class BlogController extends AbstractController { #[Route('/blog', name: 'blog_list')] public function list(): Response { // ... } } ``` Advantages: * Add Metadata to classes, methods, properties, arguments and so on. * They can replace PHP doc blocks, each with custom parsers and rules to a unified standard supported by PHP. * Type safety & autocompletion. * The data can be introspected using PHP's Reflection API's. ### Nullsafe operator * [PHP Docs](https://www.php.net/manual/en/language.oop5.basic.php#language.oop5.basic.nullsafe) * [PHP Watch](https://php.watch/versions/8.0/null-safe-operator) The nullsafe operator works the same as property or method accesses, except that if the object being dereferenced is null then null will be returned rather than an exception thrown. If the dereference is part of a chain, the rest of the chain is skipped. Put another way; it allows chaining multiple property or method accesses on an object, without first checking if each returned value is null before proceeding. Consider the following code: ```php class User { public string $firstName; public string $lastName; public ?int $age = null; public ?Address $address = null; } class Address { public int $number; public string $addressLine1; public ?string $addressLine2 = null; } ``` Pre PHP 8.0, in order to access `addressLine2` for an address of a user, it would be necessary to write the following code: ```php $user = new User(/** */); $address = $user->address; if ($address !== null) { $addressLine2 = $address->addressLine2; if ($addressLine2 !== null) { //do something } } ``` Instead, we can now write: ```php $user = new User(/** */); $addressLine2 = $user?->address?->addressLine2; if ($addressLine2 !== null) { //do something } ``` Advantages: * Much less code for simple operations where null is a valid value. * If the operator is part of a chain, anything to the right of the null will not be executed, the statements will be short-circuited. * Can be used on methods where null coalescing cannot `$user->getCreatedAt()->format('d-m-Y') ?? null` where `getCreatedAt()` could return `null` or a `\DateTime` instance. ### Other * `never` return type: https://www.php.net/manual/en/language.types.never.php * `array_is_list` function: https://www.php.net/manual/en/function.array-is-list.php * `final const X` final for class constants: https://www.php.net/manual/en/language.oop5.final.php * `$object::class` instead of `get_class($object)`: https://wiki.php.net/rfc/class\_name\_literal\_on\_object * Array unpacking with string keys is now supported: https://www.php.net/manual/en/language.types.array.php#language.types.array.unpacking There are many more changes, including deprecations and backwards compatibility breaks. Please read the official announcement pages for both PHP 8.0 & PHP 8.1 for a deeper understanding: * [PHP 8.0](https://www.php.net/releases/8.0/en.php) * [PHP 8.0 - PHP Watch](https://php.watch/versions/8.0) * [PHP 8.1](https://www.php.net/releases/8.1/en.php) * [PHP 8.1 - PHP Watch](https://php.watch/versions/8.1) ## Symfony 6.1 new features ### Enums in route definitions [Symfony Blog](https://symfony.com/blog/new-in-symfony-6-1-improved-routing-requirements-and-utf-8-parameters) We can specify route parameters which will be validated against a given enums cases: ```php use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Requirement\EnumRequirement; #[Route('/foo/{bar}', requirements: ['bar' => new EnumRequirement(SomeEnum::class)])] ``` ### Service autowiring attributes [Symfony Blog](https://symfony.com/blog/new-in-symfony-6-1-service-autowiring-attributes) We can now wire up dependencies without touching XML. It is possible to define the required services directly in the class: ```php use Symfony\Component\DependencyInjection\Attribute\Autowire; class Mailer { public function __construct( #[Autowire(service: 'email_adapter')] private Adapter $adapter, #[Autowire('%kernel.debug_mode%')] private bool $debugMode, ) {} } ``` Further to that, we can decorate services with attributes: https://symfony.com/blog/new-in-symfony-6-1-service-decoration-attributes #### Backwards Compatibility / Migration Strategy See the [Symfony Dependency Management](../../../references/adr/2023-05-16-symfony-dependency-management) ADR for the decision and migration strategy. ### Improved console autocompletion [Symfony Blog](https://symfony.com/blog/new-in-symfony-6-1-improved-console-autocompletion) Autocompletion values can now be defined directly in the command input definition, as the 5th parameter, for both arguments and inputs: ```php public function configure(): void { $this->addArgument( 'features', InputArgument::REQUIRED | InputArgument::IS_ARRAY, 'The features to enable', null, fn () => self::availableFeatures() ); } ``` --- --- url: /docs/v6.6/resources/guidelines/code/core/6.5-new-php-language-features.md --- ::: info This document represents core guidelines and has been mirrored from the core in our Shopware 6 repository. You can find the original version [here](https://github.com/shopware/shopware/blob/trunk/coding-guidelines/core/6.5-new-php-language-features.md) ::: # 2023-05-16 - PHP 8.1 & Symfony 6.1 new features ## Context As of Shopware 6.5 the minimum version of PHP is 8.1 and the minimum version of Symfony is 6.1. We would like to *promote* the usage of the newly available features. Many of the new features allow us to reduce boilerplate, make it easier to prevent common mistakes, improve refactoring support, increase legibility, perform faster and so on. By using the latest features we allow the reader and writer of code to focus on the domain rather than the language. ## PHP 8.0 & 8.1 new features ### Promoted Properties * [PHP Docs](https://www.php.net/manual/en/language.oop5.decon.php#language.oop5.decon.constructor.promotion) * [PHP Watch](https://php.watch/versions/8.0/constructor-property-promotion) *We have automatically refactored all existing code to use Promoted Properties using Rector.* Promoted properties allow us to reduce the boilerplate when defining classes, by removing the need to type the property name four times and the type twice. Class properties, with their visibility and flags can now be specified entirely in the constructor. From: ```php class Point { private int $x; private int $y; public function __construct(int $x, int $y) { $this->x = $x; $this->y = $y; } } ``` To: ```php class Point { public function __construct(private int $x, private int $y) { } } ``` Note: It is still possible to use normal property definitions/assignments with promoted properties. For example, if you need to manipulate some dependencies. Advantages: * Less code, less duplication. * Better refactoring. #### Backwards Compatibility / Migration Strategy Migrating to promoted properties does not represent a breaking change. ### New in initializers * [PHP Docs](https://www.php.net/manual/en/language.oop5.decon.php#language.oop5.decon.constructor.new) It is now possible to specify an object as a default parameter value in a function/method. Previously it was only possible to specify scalar values. From: ```php class PasswordHasher { private Hasher $hasher; public function __construct(private Hasher $hasher = null) { $this->hasher = $hasher ?? new Bcrypt(); } } ``` To: ```php class PasswordHasher { public function __construct(private Hasher $hasher = new Bcrypt()) {} } ``` Advantages: * Less code * More consistent #### Backwards Compatibility / Migration Strategy Migrating to inline object default parameters does not represent a breaking change. ### Match * [PHP Docs](https://www.php.net/manual/en/control-structures.match.php) * [PHP Watch](https://php.watch/versions/8.0/match-expression) *We have automatically refactored all existing code to use match instead of switch using Rector.* In most cases, `switch` statements can be replaced with `match` statements: * Match uses strict equality unlike switch which uses weak comparison and can lead to subtle bugs. * Each match arm does not fall through without a break statement, unlike switch. * Match expressions must be exhaustive, if there is no default arm specified, and no arm matches the given value, an `UnhandledMatchError` is thrown. * Match is an expression and thus returns a value, reducing unnecessary variables and reducing the risk of accessing undefined variables. From: ```php switch ($statusCode) { case 200: case 300: $message = null; break; case 400: $message = 'not found'; break; case 500: $message = 'server error'; break; default: $message = 'unknown status code'; break; } ``` To: ```php $message = match ($statusCode) { 200, 300 => null, 400 => 'not found', 500 => 'server error', default => 'unknown status code', }; ``` Note: Conditions can be combined in a much simpler fashion. #### Backwards Compatibility / Migration Strategy There are cases where migrating from a switch to a match could case a BC break. For example switch performs lose type checks and throws an exception for unhandled values. When migrating code, be sure to check that values are the correct types and that all cases are handled. ### New string functions * `str_contains` * `str_starts_with` * `str_ends_with` Advantages: * Simpler and more concise. * Saner return types. * It is harder to get their usage wrong, for example checking for 0 vs false with `strpos`. * The functions are faster, being implemented in C. * The operations require less function calls, for example no usages of strlen are required. ### Named arguments * [PHP Docs](https://www.php.net/manual/en/functions.arguments.php) * [PHP Watch](https://php.watch/versions/8.0/named-parameters) Named arguments are useful when calling code with bad and/or large API's. For example, many of PHP's global functions. In terms of calling bad PHP API's, the following advantages apply: * It is possible to skip defaults in between the arguments you want to change. * The code is better documented since the argument label is specified with the value, very useful for boolean flags. From: ```php htmlspecialchars($string, ENT_COMPAT | ENT_HTML, 'UTF-8', false); ``` To: ```php htmlspecialchars($string, double_encode: false); ``` Note: The second argument is not changed, but in the first example we must provide the default value, in order to change the double encode flag. #### Backwards Compatibility / Migration Strategy We do not want to use named parameters when calling Shopware API's as parameter names are not a part of the Backwards compatability promise. Named parameters should only be used when calling PHP API's. ### Type improvements * [PHP Docs](https://www.php.net/manual/en/language.types.type-system.php) * [PHP Watch - Union Types](https://php.watch/versions/8.0/union-types) * [PHP Watch - Mixed Type](https://php.watch/versions/8.0/mixed-type) * [PHP Watch - Intersection Types](https://php.watch/versions/8.1/intersection-types) **It will now only be necessary to reach for @var & @param annotations when defining array shapes, generics and more specific types such as `class-string`, `positive-int` etc. Everything else should be natively typed.** When a type can really be any value, this can now be expressed as `mixed`. When a type can be multiple, but not all, this can now be expressed as a union type, eg: `int|string`. When a type must be an intersection of multiple types, this can now be expressed as an intersection type, eg: `MyService&MockObject`. These improvements come with various advantages: * The types are enforced by PHP, so TypeError's will be thrown when attempting to pass non-valid types. * It allows us to move more type information from phpdoc into function signatures. * It prevents incorrect function information. phpdocs can often become stale when they are not updated with the function itself. ### Enums * [PHP Docs](https://www.php.net/manual/en/language.types.enumerations.php) * [PHP Watch](https://php.watch/versions/8.1/enums) PHP finally has native support for enumerations, with various advantages over common userland packages and using const's. Enums are useful where we have a predefined list of constant values. It's now not necessary to provide values as constants, and it's not necessary to create arrays of the constants to check validity. From: ```php class Indexer { public const PARTIAL = 'partial'; public const FULL = 'full'; public function product(int $id, string $method): void { if (!in_array($method, [self::PARTIAL, self::FULL], true)) { throw new \InvalidArgumentException(); } match ($method) { self::PARTIAL => $this->partial($id), self::FULL => $this->full($id) }; } } ``` To: ```php enum IndexMethod { case PARTIAL; case FULL; } class Indexer { public function product(int $id, IndexMethod $method): void { match ($method) { IndexMethod::PARTIAL => $this->partial($id), IndexMethod::FULL => $this->full($id) }; } } ``` Advantages: * Works great with `match` - an `UnhandledMatchError` exception will be thrown if there is no match arm for a given enum case. * Can type hint on an enum. * No need to validate a case. * Can provide backed values and serialize/unserialize with `MyEnum::from()` && `MyEnum::tryFrom()`. * Enums can provide methods and implement interfaces. * Better comparison features, e.g. Enums are singletons. #### Backwards Compatibility / Migration Strategy See the [Use PHP 8.1 Enums](../../../references/adr/2023-05-16-php-enums) ADR for the decision and migration strategy. ### Readonly properties * [PHP Docs](https://www.php.net/manual/en/language.oop5.properties.php#language.oop5.properties.readonly-properties) * [PHP Watch](https://php.watch/versions/8.1/readonly) Readonly properties are very useful when building DTOs. When you want to communicate a payload to a system or service, `readonly` properties allow us to create immutable data structures with a lot less code. In conjunction with promoted properties, we can reduce the boilerplate of a class significantly. Consider a product reindex command: From: ```php class ProductReindexCommand { private int $productId; private bool $includeStock: public function __construct(int $productId, bool $includeStock) { $this->productId = $productId; $this->includeStock = $includeStock; } public function getProductId(): int { return $this->productId; } public function includeStock(): bool { return $this->includeStock; } } ``` To: ```php class ProductReindexCommand { public function __construct(public readonly int $productId, public readonly bool $includeStock) { } } ``` In the first example, we use private properties to prohibit updates and public getters to allow access to the data. In the second we change the properties to `public` to allow access to the data without getters, but use `readonly` to prohibit updates. We also use promoted properties to make it even more succinct. Advantages: * Reduced boilerplate. * Make the intent of code clearer. #### Backwards Compatibility / Migration Strategy All private properties, which are not written to after instantiation can successfully be migrated to `readonly` without BC breaks. New code can use `readonly` on public and protected properties, but for existing code, that would be a BC break. ### First-class callable syntax * [PHP Docs](https://www.php.net/manual/en/functions.first_class_callable_syntax.php) * [PHP Watch](https://php.watch/versions/8.1/first-class-callable-syntax) This is a new method of referencing callables with strings and arrays. It allows for improved refactoring support, better static analysis and fixes some subtle bugs with scope. Consider an operation to find the longest string in an array, you might use `strlen` within an `array_map`: From: ```php $longest = max(array_map('strlen', $strings)); ``` To: ```php $longest = max(array_map(strlen(...), $strings)); ``` Instead of using an arbitrary string as a reference to a function, we can now use the `(...)` syntax to create a callable. It can also be used with object methods, instance or static, e.g.: ```php $callable = $object->doCoolStuff(...); $callable = \My\Object::doCoolStuff(...); ``` Advantages: * Refactoring support. * Better static analyses. * Fixes scope issues. ### Attributes * [PHP Docs](https://www.php.net/manual/en/language.attributes.overview.php) * [PHP Watch](https://php.watch/versions/8.0/attributes) *We have automatically refactored all existing code to use attributes instead of annotations using Rector.* It is now possible to use native PHP attributes to store structured metadata, rather than using the error-prone PHP docblock. For us Shopware developers, this will be most useful in conjunction with Symfony bundled attributes, which allow us to configure services and routes directly in controllers and services. From: ```php namespace App\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Routing\Annotation\Route; class BlogController extends AbstractController { /** * @Route("/blog", name="blog_list") */ public function list(): Response { // ... } } ``` To: ```php namespace App\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Routing\Annotation\Route; class BlogController extends AbstractController { #[Route('/blog', name: 'blog_list')] public function list(): Response { // ... } } ``` Advantages: * Add Metadata to classes, methods, properties, arguments and so on. * They can replace PHP doc blocks, each with custom parsers and rules to a unified standard supported by PHP. * Type safety & autocompletion. * The data can be introspected using PHP's Reflection API's. ### Nullsafe operator * [PHP Docs](https://www.php.net/manual/en/language.oop5.basic.php#language.oop5.basic.nullsafe) * [PHP Watch](https://php.watch/versions/8.0/null-safe-operator) The nullsafe operator works the same as property or method accesses, except that if the object being dereferenced is null then null will be returned rather than an exception thrown. If the dereference is part of a chain, the rest of the chain is skipped. Put another way; it allows chaining multiple property or method accesses on an object, without first checking if each returned value is null before proceeding. Consider the following code: ```php class User { public string $firstName; public string $lastName; public ?int $age = null; public ?Address $address = null; } class Address { public int $number; public string $addressLine1; public ?string $addressLine2 = null; } ``` Pre PHP 8.0, in order to access `addressLine2` for an address of a user, it would be necessary to write the following code: ```php $user = new User(/** */); $address = $user->address; if ($address !== null) { $addressLine2 = $address->addressLine2; if ($addressLine2 !== null) { //do something } } ``` Instead, we can now write: ```php $user = new User(/** */); $addressLine2 = $user?->address?->addressLine2; if ($addressLine2 !== null) { //do something } ``` Advantages: * Much less code for simple operations where null is a valid value. * If the operator is part of a chain, anything to the right of the null will not be executed, the statements will be short-circuited. * Can be used on methods where null coalescing cannot `$user->getCreatedAt()->format('d-m-Y') ?? null` where `getCreatedAt()` could return `null` or a `\DateTime` instance. ### Other * `never` return type: https://www.php.net/manual/en/language.types.never.php * `array_is_list` function: https://www.php.net/manual/en/function.array-is-list.php * `final const X` final for class constants: https://www.php.net/manual/en/language.oop5.final.php * `$object::class` instead of `get_class($object)`: https://wiki.php.net/rfc/class\_name\_literal\_on\_object * Array unpacking with string keys is now supported: https://www.php.net/manual/en/language.types.array.php#language.types.array.unpacking There are many more changes, including deprecations and backwards compatibility breaks. Please read the official announcement pages for both PHP 8.0 & PHP 8.1 for a deeper understanding: * [PHP 8.0](https://www.php.net/releases/8.0/en.php) * [PHP 8.0 - PHP Watch](https://php.watch/versions/8.0) * [PHP 8.1](https://www.php.net/releases/8.1/en.php) * [PHP 8.1 - PHP Watch](https://php.watch/versions/8.1) ## Symfony 6.1 new features ### Enums in route definitions [Symfony Blog](https://symfony.com/blog/new-in-symfony-6-1-improved-routing-requirements-and-utf-8-parameters) We can specify route parameters which will be validated against a given enums cases: ```php use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Requirement\EnumRequirement; #[Route('/foo/{bar}', requirements: ['bar' => new EnumRequirement(SomeEnum::class)])] ``` ### Service autowiring attributes [Symfony Blog](https://symfony.com/blog/new-in-symfony-6-1-service-autowiring-attributes) We can now wire up dependencies without touching XML. It is possible to define the required services directly in the class: ```php use Symfony\Component\DependencyInjection\Attribute\Autowire; class Mailer { public function __construct( #[Autowire(service: 'email_adapter')] private Adapter $adapter, #[Autowire('%kernel.debug_mode%')] private bool $debugMode, ) {} } ``` Further to that, we can decorate services with attributes: https://symfony.com/blog/new-in-symfony-6-1-service-decoration-attributes #### Backwards Compatibility / Migration Strategy See the [Symfony Dependency Management](../../../references/adr/2023-05-16-symfony-dependency-management) ADR for the decision and migration strategy. ### Improved console autocompletion [Symfony Blog](https://symfony.com/blog/new-in-symfony-6-1-improved-console-autocompletion) Autocompletion values can now be defined directly in the command input definition, as the 5th parameter, for both arguments and inputs: ```php public function configure(): void { $this->addArgument( 'features', InputArgument::REQUIRED | InputArgument::IS_ARRAY, 'The features to enable', null, fn () => self::availableFeatures() ); } ``` --- --- url: /frontends/best-practices/testing/ab-testing.md --- # A/B Testing practices A/B testing is a method of comparing two versions of a webpage or app against each other to determine which one performs better. It is a way to compare two versions of a single variable, typically by testing a subject's response to variant A against variant B, and determining which of the two variants is more effective. ## Providers There are planty of A/B testing providers available. Here are some of the most popular ones: * [AB Tasty](https://www.abtasty.com/) * [Optimizely](https://www.optimizely.com/) * [VWO](https://vwo.com/) * [Split.io](https://www.split.io/) * [Kameleoon](https://www.kameleoon.com/) * [PostHog](https://posthog.com/) You need to pick the right one for your needs. Depending on the size of your company, the complexity of your tests, and the budget you have available. There are generous free plans available in that list, so in most cases, you can start with that. ## Best practices ### Start with a hypothesis Before you start your A/B test, you should have a clear hypothesis. What do you want to test? What do you expect to happen? What is the goal of the test? ### Split components dynamically to avoid enlagred bundle sizes You should split your components dynamically. This will help you to avoid enlarged bundle sizes. You can use the `import()` function to load components on demand. Example: ```ts const myExperimentFlag = useABTesting("myExperimentFlag"); const MyComponent = myExperimentFlag ? import("./MyComponentVariantA") : import("./MyComponentVariantB"); // later in the template ``` ### Testing smaller components While dynamic splitting is very effective to avoid loading too much code to the client's browser, this would not be efficient with some very small components. For example if you only want to test a different button variant, then in most cases it could be done in a single component. Example: ```ts const myExperimentFlag = useABTesting("myExperimentFlag"); // later in the template // or more slear split using v-show/v-if ``` ### Clean your code After the test is finished, you should clean your code. Remove all the unused code and components. This will help you to keep your codebase clean and maintainable. Not removing unused variants will cost you many maintenance problems, especially while refactoring your application. --- --- url: /docs/guides/development/accessibility.md --- # Accessibility Shopware is committed to creating inclusive and barrier-free shopping experiences. Accessibility affects both the core Storefront and all custom themes and extensions. Developers are responsible for ensuring their implementations comply with accessibility standards, such as WCAG 2.1 Level AA. ## In this section ## Why accessibility matters * Legal compliance (e.g., EU accessibility regulations) * Better usability for all users * Improved SEO and performance * Future-proof storefront implementations Shopware continuously introduces accessibility improvements in new releases. Always test extensions with the `ACCESSIBILITY_TWEAKS` feature flag enabled to ensure compatibility. --- --- url: /docs/guides/development/accessibility/storefront-accessibility.md --- # Accessibility in the Storefront Shopware is committed to creating inclusive and barrier-free shopping experiences for our merchants and their customers. ## What does Shopware do to ensure accessibility? * Shopware is committed to fulfilling the [WCAG 2.1 AA](https://www.w3.org/TR/WCAG21/) accessibility guidelines and Barrier-Free Information Technology Regulation (BITV 2.0) in the Storefront. * You can find more information on [shopware.design](https://shopware.design/foundations/accessibility.html) and [in our blog post](https://www.shopware.com/en/news/accessible-online-store-by-2025/). * The Storefront is using [Bootstrap components](https://getbootstrap.com/docs/5.3/getting-started/accessibility/) that already consider good accessibility practices, for example, using aria roles. * Much of the HTML structure and CSS styling already fulfill accessibility guidelines. However, there are still [open accessibility issues](https://github.com/shopware/shopware/issues?q=state%3Aopen%20label%3Aarea%2Faccessibility) that will be addressed. * Automated [E2E testing with playwright](https://github.com/shopware/shopware/tree/trunk/tests/acceptance) and axe reporter are used to ensure future accessibility. ## How are core accessibility improvements released? Starting with **Shopware 6.6+**, accessibility improvements have been introduced, and **6.7+** includes further enhancements. Accessibility improvements are rolled out in regular minor releases, similar to other improvements or bug fixes. There is no significant "accessibility release" planned that ships all accessibility improvements at once. ## How to deal with breaking accessibility changes? Ensuring an accessible shop page can require changes in the HTML/Twig structure or the CSS. This can cause unintended behavior in an extension that modifies an area being changed to improve accessibility. Because of this, breaking accessibility changes are not enabled by default. All accessibility changes that include breaking changes are implemented behind a feature flag: ```env ACCESSIBILITY_TWEAKS=1 ``` However, breaking accessibility changes are still released regularly inside minor releases. They are not active by default to not cause a breaking change. The feature flag `ACCESSIBILITY_TWEAKS` can be activated in your `.env`, similar to the major feature flags like `V6_7_0_0`. When the feature flag is enabled, all available accessibility improvements are activated. This allows you to check whether your project or extension is affected by the change and to prepare an adaptation already if necessary. ::: warning With the major version v6.7.0, all accessibility improvements will become the default. ::: ### Example of a breaking accessibility change Let's say, for example, that a list is not using proper markup and is changed to improve accessibility. This is what a suboptimal HTML structure could look like: ```twig ``` Let's assume it should be changed to a proper list. Instead of implementing this right away, it is implemented behind the `ACCESSIBILITY_TWEAKS` flag, including instructions on how it should be changed: ```twig {# @deprecated tag:v6.7.0 - The list will be changed to `