diff --git a/concepts/framework/data-abstraction-layer.md b/concepts/framework/data-abstraction-layer.md index 2447b4c082..69533f588a 100644 --- a/concepts/framework/data-abstraction-layer.md +++ b/concepts/framework/data-abstraction-layer.md @@ -35,7 +35,7 @@ This is the recommended way for developers to interface with the DAL or the data Before using the repositories, you will need to get them from the [Dependency Injection Container (DIC)](../../guides/plugins/plugins/plugin-fundamentals/dependency-injection). This is done with [Constructor injection](https://symfony.com/doc/current/service_container/injection_types.html#constructor-injection), so you will need to extend your services constructor by expecting an EntityRepository: -```php +```PHP // /src/Service/DalExampleService.php public function __construct (EntityRepository $productRepository) { @@ -47,11 +47,10 @@ If you are using [Service autowiring](https://symfony.com/doc/current/service_co Alternatively, configure the `product.repository` service to be injected explicitly: -```html -// src/Resources/config/service.xml - - - +```PHP +// src/Resources/config/services.php +$services->set(Swag\ExamplePlugin\Service\DalExampleService::class) + ->args([service('product.repository')]); ``` You can read more about dependency injection and service registration in Shopware in the services guides: diff --git a/guides/hosting/configurations/observability/profiling.md b/guides/hosting/configurations/observability/profiling.md index d5ce6761a1..b27cdf947a 100644 --- a/guides/hosting/configurations/observability/profiling.md +++ b/guides/hosting/configurations/observability/profiling.md @@ -34,7 +34,7 @@ The OpenTelemetry profiler is not installed by default. Checkout the [OpenTeleme To add custom spans to the profiler, you can use the `Shopware\Core\Profiling\Profiler::trace` method: -```php +```PHP use Shopware\Core\Profiling\Profiler; $value = Profiler::trace('my-example-trace', function () { @@ -50,7 +50,7 @@ To add a custom profiler backend, you need to implement the `Shopware\Core\Profi The following example shows a custom profiler backend that logs the traces to the console: -```php +```PHP namespace App\Profiler; @@ -70,10 +70,9 @@ class ConsoleProfiler implements ProfilerInterface } ``` -```XML - - - +```PHP +$services->set(App\Profiler::class) + ->tag('shopware.profiler', ['integration' => 'Console']); ``` The attribute `integration` is used to identify the profiler backend in the configuration. diff --git a/guides/hosting/performance/session.md b/guides/hosting/performance/session.md index 421083fc88..5ff3acef07 100644 --- a/guides/hosting/performance/session.md +++ b/guides/hosting/performance/session.md @@ -51,15 +51,14 @@ To use one of these handlers, you must create a new service in the dependency in Example service definition: -```xml - - - +```PHP +$services->set('session.db', Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler::class) + ->args([/* ... */]); ``` Example session configuration: -```yaml +```YAML # config/packages/redis.yml framework: session: diff --git a/guides/plugins/plugins/bundle.md b/guides/plugins/plugins/bundle.md index d1ec4876b2..5a46e7f895 100644 --- a/guides/plugins/plugins/bundle.md +++ b/guides/plugins/plugins/bundle.md @@ -33,8 +33,8 @@ project-root/ │ │ └── Migration1234567890YourMigration.php │ └── Resources/ │ ├── config/ -│ │ ├── services.xml -│ │ └── routes.xml +│ │ ├── services.php +│ │ └── routes.php │ ├── views/ │ │ └── storefront/ │ │ └── page/ @@ -67,7 +67,7 @@ If you don't need these features, you can use the Symfony bundle class instead. By default, The namespace `App\` is registered to the `src` folder in any Shopware project to be used for customizations. We recommend using this namespace, if you like to change the project structure, you can change the `App\` namespace in the `composer.json` file of your project. -```php +```PHP // /src/YourBundleName.php /config/bundles.php //... App\YourBundleName\YourBundleName::class => ['all' => true], @@ -92,7 +92,7 @@ App\YourBundleName\YourBundleName::class => ['all' => true], ## Adding services, twig templates, routes, theme, etc You can add services, twig templates, routes, etc. to your bundle like you would do in a plugin. -Just create `Resources/config/services.xml` and `Resources/config/routes.xml` files or `Resources/views` for twig templates. +Just create `Resources/config/services.php` and `Resources/config/routes.php` files or `Resources/views` for twig templates. The bundle will be automatically detected and the files will be loaded. To mark your bundle as a theme, it's enough to implement the `Shopware\Core\Framework\ThemeInterface` interface in your bundle class. @@ -104,7 +104,7 @@ You can also add a `theme.json` file to define the theme configuration like [des Migrations are not automatically detected in bundles. To enable migrations, you need to overwrite the `build` method in your bundle class like this: -```php +```PHP // /src/YourBundleName.php --all ``` If you use [Deployment Helper](../../hosting/installation-updates/deployments/deployment-helper.md), you can add it to the `.shopware-project.yaml` file like this: -```yaml +```YAML deployment: hooks: pre-update: | @@ -145,7 +145,7 @@ Shopware-CLI cannot detect bundles automatically, therefore the assets of the bu You will need to adjust the `composer.json` file of your project to specify the path to the bundle. This is done by adding the `extra` section to the `composer.json` file: -```json +```JSON { "extra": { "shopware-bundles": { diff --git a/guides/plugins/plugins/checkout/cart/add-cart-discounts.md b/guides/plugins/plugins/checkout/cart/add-cart-discounts.md index fa178d6693..653008631d 100644 --- a/guides/plugins/plugins/checkout/cart/add-cart-discounts.md +++ b/guides/plugins/plugins/checkout/cart/add-cart-discounts.md @@ -23,7 +23,7 @@ To add a discount to the cart, you should use the processor pattern. For this yo Let's start with the actual example code: -```php +```PHP // /src/Core/Checkout/ExampleProcessor.php /src/Service/ExampleController.php /src/Resources/config/services.xml - - - - - - - - - +```PHP +// /src/Resources/config/services.php +services(); + + $services->set(ExampleHandler::class) + ->tag('shopware.cart.line_item.factory'); +}; ``` Let's first have a look at an example handler: -```php +```PHP // /src/Service/ExampleHandler.php /Core/Checkout/Cart/ExampleProcessor.php /Resources/config/services.xml -... - - ... - - - - +Now register this processor in your `services.php` like this: + +```PHP +// /src/Resources/config/services.php +services(); + + $services->set(ExampleProcessor::class) + ->tag('shopware.cart.processor', ['priority' => 4800]); +}; ``` And that's it. You should now be able to create line items of type `example`. diff --git a/guides/plugins/plugins/checkout/cart/add-cart-validator.md b/guides/plugins/plugins/checkout/cart/add-cart-validator.md index 940c78b434..0786daf1a9 100644 --- a/guides/plugins/plugins/checkout/cart/add-cart-validator.md +++ b/guides/plugins/plugins/checkout/cart/add-cart-validator.md @@ -35,7 +35,7 @@ Your validator has to implement the interface `Shopware\Core\Checkout\Cart\CartV But let's have a look at the example validator first: -```php +```PHP // /src/Core/Checkout/Cart/Custom/CustomCartValidator.php /src/Resources/config/services.xml - - - - - - - - - +```PHP +// /src/Resources/config/services.php +services(); + + $services->set(CustomCartValidator::class) + ->tag('shopware.cart.validator'); +}; ``` ### Adding the custom cart error @@ -121,7 +121,7 @@ It has to extend from the abstract class `Shopware\Core\Checkout\Cart\Error\Erro So now let's have a look at the example error class: -```php +```PHP // /src/Core/Checkout/Cart/Custom/Error/CustomCartBlockedError.php /src/Resources/snippet/en\_GB/example.en-GB.json { "checkout": { diff --git a/guides/plugins/plugins/checkout/cart/change-price-of-item.md b/guides/plugins/plugins/checkout/cart/change-price-of-item.md index 49cbce65b6..e8e5afd795 100644 --- a/guides/plugins/plugins/checkout/cart/change-price-of-item.md +++ b/guides/plugins/plugins/checkout/cart/change-price-of-item.md @@ -39,7 +39,7 @@ Your collector class has to implement the interface `Shopware\Core\Checkout\Cart Let's have a look at an example: -```php +```PHP // /src/Core/Checkout/Cart/OverwritePriceCollector.php /src/Core/Checkout/Cart/OverwritePriceCollector.php /src/Resources/config/services.xml - - - - - - - - - - - - - +```PHP +// /src/Resources/config/services.php +services(); + + $services->set(OverwritePriceCollector::class) + ->args([ + service(QuantityPriceCalculator::class), + ]) + // after product collector/processor + ->tag('shopware.cart.processor', ['priority' => 4500]) + ->tag('shopware.cart.collector', ['priority' => 4500]); +}; ``` And that's it. Your processor / collector should now be working. diff --git a/guides/plugins/plugins/checkout/cart/customize-price-calculation.md b/guides/plugins/plugins/checkout/cart/customize-price-calculation.md index bca121a75a..ee20cdf03a 100644 --- a/guides/plugins/plugins/checkout/cart/customize-price-calculation.md +++ b/guides/plugins/plugins/checkout/cart/customize-price-calculation.md @@ -27,7 +27,7 @@ So let's do that real quick. If you're looking for an in-depth explanation, head Here's an example decorated calculator: -```php +```PHP // /src/Service/CustomProductPriceCalculator.php /src/Resources/config/services.xml - - - - - - - - - +```PHP +// /src/Resources/config/services.php +services(); + + $services->set(CustomProductPriceCalculator::class) + ->decorate(ProductPriceCalculator::class) + ->args([ + service('.inner'), + ]); +}; ``` ## Next steps diff --git a/guides/plugins/plugins/checkout/cart/tax-provider.md b/guides/plugins/plugins/checkout/cart/tax-provider.md index 1823b921c4..d7076a09e8 100644 --- a/guides/plugins/plugins/checkout/cart/tax-provider.md +++ b/guides/plugins/plugins/checkout/cart/tax-provider.md @@ -23,7 +23,7 @@ Firstly you need to create a class which handles the tax calculation or calls yo You may then call a tax provider, which will calculate the taxes for you. For example, we simply apply a hefty 50% tax rate for all line-items in the cart. -```php +```PHP // /src/Checkout/Cart/Tax/TaxProvider.php /src/Resources/config/services.xml - +```PHP +// /src/Resources/config/services.php + +return static function (ContainerConfigurator $configurator): void { + $services = $configurator->services(); - - - - - + $services->set(TaxProvider::class) + ->tag('shopware.tax.provider'); +}; ``` ## Migrate your tax provider to the database @@ -108,7 +108,7 @@ To let Shopware know of your new tax provider, you will have to persist it to th You may want to have a look at the [migration guide](../../plugin-fundamentals/database-migrations) to learn more about migrations. -```php +```PHP // /src/Migration/MigrationTaxProvider.php /src/BasicExample.php - - - - - - +```PHP +use Shopware\Core\Checkout\Document\Service\DocumentConfigLoader; +use Shopware\Core\Checkout\Document\Service\DocumentFileRendererRegistry; +use Shopware\Core\System\NumberRange\ValueGenerator\NumberRangeValueGeneratorInterface; +use Swag\BasicExample\Core\Checkout\Document\Render\ExampleDocumentRenderer; + +$services->set(ExampleDocumentRenderer::class) + ->args([ + service('order.repository'), + service(DocumentConfigLoader::class), + service(NumberRangeValueGeneratorInterface::class), + service(DocumentFileRendererRegistry::class), + ]) + ->tag('document.renderer'); ``` ### Adding a file type renderer @@ -358,26 +364,33 @@ Depending on the file type we either get the content with `$this->fileRendererRe ### Registering the renderer in the service container -Now we need to register our custom `ExampleDocumentRenderer` in the service container. Create or update your `services.xml` file: +Now we need to register our custom `ExampleDocumentRenderer` in the service container. Create or update your `services.php` file: ::: code-group -```xml [PLUGIN_ROOT/src/Resources/config/services.xml] - - - - - - - - - - - - - +```PHP [PLUGIN_ROOT/src/Resources/config/services.php] +services(); + + $services->set(ExampleDocumentRenderer::class) + ->args([ + service('order.repository'), + service(DocumentConfigLoader::class), + service(NumberRangeValueGeneratorInterface::class), + service(DocumentFileRendererRegistry::class), + ]) + ->tag('document.renderer'); +}; ``` ::: diff --git a/guides/plugins/plugins/checkout/payment/add-payment-plugin.md b/guides/plugins/plugins/checkout/payment/add-payment-plugin.md index 4b515c128f..966ba95caf 100644 --- a/guides/plugins/plugins/checkout/payment/add-payment-plugin.md +++ b/guides/plugins/plugins/checkout/payment/add-payment-plugin.md @@ -39,19 +39,20 @@ Please make sure to add the `shopware.payment.method` tag to your service defini We'll use a class called `MyCustomPaymentHandler` here. -```xml [/src/Resources/config/services.xml] - - - - - - - - - - +```PHP [/src/Resources/config/services.php] +services(); + + $services->set(MyCustomPaymentHandler::class) + ->tag('shopware.payment.method'); +}; ``` Now, let's start with the actual examples. @@ -116,20 +117,24 @@ class MyCustomPaymentHandler extends AbstractPaymentHandler } ``` -```xml [services.xml] - +```PHP [services.php] + +return static function (ContainerConfigurator $configurator): void { + $services = $configurator->services(); - - - - - - - + $services->set(MyCustomPaymentHandler::class) + ->tag('shopware.payment.method') + ->args([ + service(OrderTransactionStateHandler::class), + ]); +}; ``` ::: @@ -227,20 +232,24 @@ class MyCustomPaymentHandler extends AbstractPaymentHandler } ``` -```xml [services.xml] - +```PHP [services.php] + +return static function (ContainerConfigurator $configurator): void { + $services = $configurator->services(); - - - - - - - + $services->set(MyCustomPaymentHandler::class) + ->tag('shopware.payment.method') + ->args([ + service(OrderTransactionStateHandler::class), + ]); +}; ``` ::: @@ -350,7 +359,7 @@ class MyCustomPaymentHandler extends AbstractPaymentHandler // In here you should probably call your payment provider to precess the payment // $this->myPaymentProvider->processPayment($transaction); - + // afterward you should update the transaction with the new state $this->transactionStateHandler->process($transaction->getOrderTransactionId(), $context); @@ -359,20 +368,24 @@ class MyCustomPaymentHandler extends AbstractPaymentHandler } ``` -```xml [services.xml] - +```PHP [services.php] + +use function Symfony\Component\DependencyInjection\Loader\Configurator\service; - - - - - - - +return static function (ContainerConfigurator $configurator): void { + $services = $configurator->services(); + + $services->set(MyCustomPaymentHandler::class) + ->tag('shopware.payment.method') + ->args([ + service(OrderTransactionStateHandler::class), + ]); +}; ``` ::: @@ -489,22 +502,27 @@ class MyCustomPaymentHandler extends AbstractPaymentHandler ``` -```xml [services.xml] - - - - - - - - - - - - - +```PHP [services.php] +services(); + + $services->set(MyCustomPaymentHandler::class) + ->tag('shopware.payment.method') + ->args([ + service('order_transaction_capture_refund.repository'), + service(OrderTransactionStateHandler::class), + service(OrderTransactionCaptureStateHandler::class), + ]); +}; ``` ::: @@ -567,16 +585,16 @@ class MyCustomPaymentHandler extends AbstractPaymentHandler // In here you should probably call your payment provider to create a billing agreement // $this->myPaymentProvider->createBillingAgreement($transaction); } - + // Don't forget to capture the initial payment as well // $this->myPaymentProvider->processPayment($transaction); - + // afterward you should update the transaction with the new state $this->transactionStateHandler->process($transaction->getOrderTransactionId(), $context); return null; } - + /** * call your PSP here for capturing a recurring payment * a valid billing agreement between the customer and the PSP should usually already be in place @@ -602,20 +620,24 @@ class MyCustomPaymentHandler extends AbstractPaymentHandler ``` -```xml [services.xml] - +```PHP [services.php] + +return static function (ContainerConfigurator $configurator): void { + $services = $configurator->services(); - - - - - - - + $services->set(MyCustomPaymentHandler::class) + ->tag('shopware.payment.method') + ->args([ + service(OrderTransactionStateHandler::class), + ]); +}; ``` ::: @@ -695,7 +717,7 @@ class SwagBasicExample extends Plugin 'name' => 'Example payment', 'description' => 'Example payment description', 'pluginId' => $pluginId, - // if true, payment method will also be available after the order + // if true, payment method will also be available after the order // is created, e.g. if payment fails and the user wants to try again 'afterOrderEnabled' => true, // the technicalName helps you to identify the payment method uniquely @@ -784,27 +806,26 @@ Remove any other occurrences of the following tags: ::: code-group -```xml [services.xml] - - - - - - - - - - - - - - - - - - +```PHP [services.php] +services(); + + $services->set(MyCustomPaymentHandler::class) + // this is the new tag for payment handlers + ->tag('shopware.payment.method') + + // remove any of these other tags + ->tag('shopware.payment.method.sync') + ->tag('shopware.payment.method.async') + ->tag('shopware.payment.method.prepared') + ->tag('shopware.payment.method.recurring') + ->tag('shopware.payment.method.refund'); +}; ``` ::: diff --git a/guides/plugins/plugins/checkout/payment/customize-payment-provider.md b/guides/plugins/plugins/checkout/payment/customize-payment-provider.md index 486d7e2d17..053df4eb54 100644 --- a/guides/plugins/plugins/checkout/payment/customize-payment-provider.md +++ b/guides/plugins/plugins/checkout/payment/customize-payment-provider.md @@ -24,7 +24,7 @@ First, we create a new class that extends from the provider we want to customise In this example we customise the class `Shopware\Core\Checkout\Payment\Cart\PaymentHandler\DebitPayment` and name our class `ExampleDebitPayment`. The constructor has to accept an instance of `OrderTransactionStateHandler` like the original service and additionally an instance of `DebitPayment` that we want to decorate. -After we've created our customized payment provider class, we have to register it to the DI-container via the `services.xml`. +After we've created our customized payment provider class, we have to register it to the DI-container via the `services.php`. ::: code-group @@ -66,20 +66,26 @@ class ExampleDebitPayment extends DebitPayment } ``` -```xml [services.xml] - +```PHP [services.php] + +return static function (ContainerConfigurator $configurator): void { + $services = $configurator->services(); - - - - - - - + $services->set(ExampleDebitPayment::class) + ->decorate(DebitPayment::class) + ->args([ + service(OrderTransactionStateHandler::class), + service('.inner'), + ]); +}; ``` ::: diff --git a/guides/plugins/plugins/content/cms/add-data-to-cms-elements.md b/guides/plugins/plugins/content/cms/add-data-to-cms-elements.md index b90d11d5fd..dd2a3f49f4 100644 --- a/guides/plugins/plugins/content/cms/add-data-to-cms-elements.md +++ b/guides/plugins/plugins/content/cms/add-data-to-cms-elements.md @@ -23,7 +23,7 @@ so head over to the official guide about [Adding a custom CMS element](add-cms-e To manipulate the data of these elements during the loading of the configuration, we create a `DailyMotionCmsElementResolver` resolver in our plugin. -```php +```PHP // /src/DataResolver/DailyMotionCmsElementResolver.php +```PHP [PLUGIN_ROOT/src/Resources/config/services.php] + +return static function (ContainerConfigurator $configurator): void { + $services = $configurator->services(); - - - - - - + $services->set(DailyMotionCmsElementResolver::class) + ->tag('shopware.cms.data_resolver'); +}; ``` ::: diff --git a/guides/plugins/plugins/content/mail/add-data-to-mails.md b/guides/plugins/plugins/content/mail/add-data-to-mails.md index cf117e5c11..18990cda55 100644 --- a/guides/plugins/plugins/content/mail/add-data-to-mails.md +++ b/guides/plugins/plugins/content/mail/add-data-to-mails.md @@ -78,22 +78,28 @@ If we add {{ myCustomData }} to any mail template, it should Of course you still have to register the decoration to the service container. Beware of the `decorates` attribute of our service. -Here's the respective example `services.xml`: +Here's the respective example `services.php`: ::: code-group -```xml [PLUGIN_ROOT/src/Resources/config/services.xml] - - - - - - - - - +```PHP [PLUGIN_ROOT/src/Resources/config/services.php] +services(); + + $services->set(AddDataToMails::class) + ->decorate(MailService::class) + ->args([ + service('.inner'), + ]); +}; ``` ::: @@ -142,22 +148,22 @@ class MyMailSubscriber implements EventSubscriberInterface You have to register the subscriber to the service container as well. -Here's the respective example `services.xml`: +Here's the respective example `services.php`: ::: code-group -```xml [PLUGIN_ROOT/src/Resources/config/services.xml] - - - - - - - - - +```PHP [PLUGIN_ROOT/src/Resources/config/services.php] +services(); + + $services->set(MyMailSubscriber::class) + ->tag('kernel.event_subscriber'); +}; ``` ::: diff --git a/guides/plugins/plugins/content/media/add-custom-file-extension.md b/guides/plugins/plugins/content/media/add-custom-file-extension.md index ba4a44f79f..6eb0379217 100644 --- a/guides/plugins/plugins/content/media/add-custom-file-extension.md +++ b/guides/plugins/plugins/content/media/add-custom-file-extension.md @@ -31,7 +31,7 @@ This is of course done via a [subscriber](../../plugin-fundamentals/listening-to Have a look at the following code example: -```php +```PHP // /src/Service/Subscriber.php -```php +```PHP // /src/Core/Content/Media/TypeDetector/CustomImageTypeDetector.php - - -```xml -// /src/Resources/config/services.xml - - - - - - - - - + + +```PHP +// /src/Resources/config/services.php +services(); + + $services->set(CustomImageTypeDetector::class) + ->tag('shopware.media_type.detector', ['priority' => 10]); +}; ``` diff --git a/guides/plugins/plugins/content/media/prevent-deletion-of-media-files-referenced-in-your-plugins.md b/guides/plugins/plugins/content/media/prevent-deletion-of-media-files-referenced-in-your-plugins.md index 9dc1f3be32..9bfb6866c8 100644 --- a/guides/plugins/plugins/content/media/prevent-deletion-of-media-files-referenced-in-your-plugins.md +++ b/guides/plugins/plugins/content/media/prevent-deletion-of-media-files-referenced-in-your-plugins.md @@ -44,7 +44,7 @@ In this section, we're going to register a subscriber for the `\Shopware\Core\Co Have a look at the following code example: -```php +```PHP // /src/Subscriber/UnusedMediaSubscriber.php /src/Subscriber/UnusedMediaSubscriber.php private function getUsedMediaIds(array $idsToBeDeleted): array { @@ -121,21 +121,21 @@ Make sure to register your event subscriber to the [Dependency injection contain by using the tag `kernel.event_subscriber`. - - -```xml -// /src/Resources/config/services.xml - - - - - - - - - + + +```PHP +// /src/Resources/config/services.php +services(); + + $services->set(UnusedMediaSubscriber::class) + ->tag('kernel.event_subscriber'); +}; ``` diff --git a/guides/plugins/plugins/content/seo/add-custom-seo-url.md b/guides/plugins/plugins/content/seo/add-custom-seo-url.md index 6e5f0cca55..e775db07a4 100644 --- a/guides/plugins/plugins/content/seo/add-custom-seo-url.md +++ b/guides/plugins/plugins/content/seo/add-custom-seo-url.md @@ -40,7 +40,7 @@ For this example, the controller from the [Add custom controller guide](../../st Let's now have a look at our example controller: -```php +```PHP // /src/Storefront/Controller/ExampleController.php /src/Migration/Migration1619094740AddStaticSeoUrl.php -```php +```PHP // /src/Storefront/Framework/Seo/SeoUrlRoute/ExamplePageSeoUrlRoute.php - + -```xml -// /src/Resources/config/services.xml - - +```PHP +// /src/Resources/config/services.php + - - +return static function (ContainerConfigurator $configurator): void { + $services = $configurator->services(); - - - - + $services->set(ExamplePageSeoUrlRoute::class) + ->args([ + service(ExampleDefinition::class), + ]) + ->tag('shopware.seo_url.route'); +}; ``` @@ -300,7 +304,7 @@ Once again, let's have a look at an example subscriber here: -```php +```PHP // /src/Service/DynamicSeoUrlPageSubscriber.php - + + +```PHP +// /src/Resources/config/services.php +/src/Resources/config/services.xml - - +use function Symfony\Component\DependencyInjection\Loader\Configurator\service; - - - - +return static function (ContainerConfigurator $configurator): void { + $services = $configurator->services(); - - - - + $services->set(DynamicSeoUrlPageSubscriber::class) + ->args([ + service(SeoUrlUpdater::class), + ]) + ->tag('kernel.event_subscriber'); +}; ``` @@ -374,7 +381,7 @@ The most important values you'll have to set in the migration are: Now here is the said example migration: -```php +```PHP // /src/Migration/Migration1619514731AddExampleSeoUrlTemplate.php /src/Service/DynamicSeoUrlPageSubscriber.php /src/Service/DynamicSeoUrlsService.php seoUrlPersister->updateSeoUrls($context, self::ROUTE_NAME, $ids, []); @@ -571,7 +578,7 @@ This way the respective SEO URLs will be marked as `is_deleted` for the system. In the example mentioned above, we're just using a `Context` instance, for whichever language that is. You can be more specific here though, in order to properly define the language ID yourself here and therefore ensuring it is written for the right language. -```php +```PHP $context = new Context( $event->getContext()->getSource(), $event->getContext()->getRuleIds(), diff --git a/guides/plugins/plugins/content/seo/extend-robots-txt.md b/guides/plugins/plugins/content/seo/extend-robots-txt.md index 83abac59e5..e16c81efde 100644 --- a/guides/plugins/plugins/content/seo/extend-robots-txt.md +++ b/guides/plugins/plugins/content/seo/extend-robots-txt.md @@ -91,21 +91,26 @@ class RobotsExtensionListener - - -```XML - - - - - - - - - - + + +```PHP +services(); + + $services->set(RobotsExtensionListener::class) + ->args([ + service('logger'), + ]) + ->tag('kernel.event_listener', ['event' => RobotsDirectiveParsingEvent::class]); +}; ``` @@ -143,20 +148,21 @@ class CustomDirectiveListener - + + +```PHP + - +use Shopware\Storefront\Page\Robots\Event\RobotsUnknownDirectiveEvent; +use Swag\Example\Listener\CustomDirectiveListener; +use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; - - - - - - +return static function (ContainerConfigurator $configurator): void { + $services = $configurator->services(); + + $services->set(CustomDirectiveListener::class) + ->tag('kernel.event_listener', ['event' => RobotsUnknownDirectiveEvent::class]); +}; ``` @@ -243,20 +249,21 @@ class RobotsValidationListener - + + +```PHP + - +return static function (ContainerConfigurator $configurator): void { + $services = $configurator->services(); - - - - - - + $services->set(RobotsValidationListener::class) + ->tag('kernel.event_listener', ['event' => RobotsDirectiveParsingEvent::class]); +}; ``` diff --git a/guides/plugins/plugins/content/sitemap/add-custom-sitemap-entries.md b/guides/plugins/plugins/content/sitemap/add-custom-sitemap-entries.md index 437eacc8ed..ef34b128b5 100644 --- a/guides/plugins/plugins/content/sitemap/add-custom-sitemap-entries.md +++ b/guides/plugins/plugins/content/sitemap/add-custom-sitemap-entries.md @@ -67,7 +67,7 @@ Let's have a look at the example class: -```php +```PHP // /src/Core/Content/Sitemap/Provider/CustomUrlProvider.php - - -```xml -// /src/Resources/config/services.xml - - - - - - - - - - - - - + + +```PHP +// /src/Resources/config/services.php +services(); + + $services->set(CustomUrlProvider::class) + ->args([ + service('swag_example.repository'), + service(Connection::class), + service('router'), + ]) + ->tag('shopware.sitemap_url_provider'); +}; ``` diff --git a/guides/plugins/plugins/content/sitemap/modify-sitemap-entries.md b/guides/plugins/plugins/content/sitemap/modify-sitemap-entries.md index 6e1bebdf1a..3eca0bfb1f 100644 --- a/guides/plugins/plugins/content/sitemap/modify-sitemap-entries.md +++ b/guides/plugins/plugins/content/sitemap/modify-sitemap-entries.md @@ -39,7 +39,7 @@ Start by decorating the corresponding URL provider, for example -```php +```PHP // /src/Core/Content/Sitemap/Provider/DecoratedProductUrlProvider.php - - -```xml -// /src/Resources/config/services.xml - - - - - - - - - + + +```PHP +// /src/Resources/config/services.php +services(); + + $services->set(DecoratedProductUrlProvider::class) + ->decorate(ProductUrlProvider::class) + ->args([ + service('.inner'), + ]); +}; ``` @@ -133,7 +138,7 @@ This is the recommended extension point when you need to: -```php +```PHP // /src/Core/Content/Sitemap/ProductSitemapQuerySubscriber.php - - -```xml -// /src/Resources/config/services.xml - - - - - - - - - + + +```PHP +// /src/Resources/config/services.php +services(); + + $services->set(ProductSitemapQuerySubscriber::class) + ->tag('kernel.event_subscriber'); +}; ``` diff --git a/guides/plugins/plugins/content/stock/implementing-your-own-stock-storage.md b/guides/plugins/plugins/content/stock/implementing-your-own-stock-storage.md index bab281f2c3..a22c267789 100644 --- a/guides/plugins/plugins/content/stock/implementing-your-own-stock-storage.md +++ b/guides/plugins/plugins/content/stock/implementing-your-own-stock-storage.md @@ -26,7 +26,7 @@ First, to communicate stock alterations to a third-party service, you will have -```php +```PHP // /src/Swag/Example/Service/StockStorageDecorator.php - - -```xml -// /src/Resources/config/services.xml - - - - - - - - - + + +```PHP +// /src/Resources/config/services.php +services(); + + $services->set(StockStorageDecorator::class) + ->decorate(StockStorage::class) + ->args([ + service('.inner'), + ]); +}; ``` diff --git a/guides/plugins/plugins/content/stock/loading-stock-information-from-different-source.md b/guides/plugins/plugins/content/stock/loading-stock-information-from-different-source.md index 95c89e4ae4..3e04015605 100644 --- a/guides/plugins/plugins/content/stock/loading-stock-information-from-different-source.md +++ b/guides/plugins/plugins/content/stock/loading-stock-information-from-different-source.md @@ -22,7 +22,7 @@ For example, to load stock from a third-party API, you need to decorate `\Shopwa -```php +```PHP // /src/Swag/Example/Service/StockStorageDecorator.php - - -```xml -// /src/Resources/config/services.xml - - - - - - - - - + + +```PHP +// /src/Resources/config/services.php +services(); + + $services->set(StockStorageDecorator::class) + ->decorate(StockStorage::class) + ->args([ + service('.inner'), + ]); +}; ``` @@ -114,7 +120,7 @@ There are several required values and some optional values. For example: -```php +```PHP $stockData = \Shopware\Core\Content\Product\Stock\StockData::fromArray([ 'productId' => 'product-1', 'stock' => 5, @@ -127,7 +133,7 @@ $stockData = \Shopware\Core\Content\Product\Stock\StockData::fromArray([ It is also possible to provide arbitrary data via extensions: -```php +```PHP $stockData = \Shopware\Core\Content\Product\Stock\StockData::fromArray([ 'productId' => 'product-1', 'stock' => 5, @@ -139,6 +145,6 @@ $stockData->addArrayExtension('extraData', ['foo' => 'bar']); The values in the `StockData` instance will be used to update the loaded product instance. Furthermore, fetching the `StockData` instance from the product via the `stock_data` extension is possible. For example: -```php +```PHP $stockData = $product->getExtension('stock_data'); ``` diff --git a/guides/plugins/plugins/elasticsearch/add-product-entity-extension-to-elasticsearch.md b/guides/plugins/plugins/elasticsearch/add-product-entity-extension-to-elasticsearch.md index 74627021c4..40f10c5da6 100644 --- a/guides/plugins/plugins/elasticsearch/add-product-entity-extension-to-elasticsearch.md +++ b/guides/plugins/plugins/elasticsearch/add-product-entity-extension-to-elasticsearch.md @@ -23,44 +23,49 @@ We will extend the product extension with an `OneToOneAssociationField` and `One To extend the elasticsearch definition we need to extend the product definition first and add the subscriber. This is described in the above mentioned articles. Here we show you how this could look like in the end. -The service.xml with all needed definitions. - -```xml -// /src/Core/Content/DependencyInjection/product.xml - - - - - - - - - - - - - - - - - - - - - - - - - - - +The services.php with all needed definitions. + +```PHP +// /src/Core/Content/DependencyInjection/product.php +services(); + + $services->set(CustomExtension::class) + ->tag('shopware.entity.extension'); + + $services->set(OneToOneExampleExtensionDefinition::class) + ->tag('shopware.entity.definition', ['entity' => 'one_to_one_swag_example_extension']); + + $services->set(OneToManyExampleExtensionDefinition::class) + ->tag('shopware.entity.definition', ['entity' => 'one_to_many_swag_example_extension']); + + $services->set(ProductSubscriber::class) + ->tag('kernel.event_subscriber'); + + $services->set(MyProductEsDecorator::class) + ->decorate(ElasticsearchProductDefinition::class) + ->args([ + service(MyProductEsDecorator::class . '.inner'), + service(\Doctrine\DBAL\Connection::class), + ]); +}; ``` The product extension `CustomExtension.php` provides the extensions to the product entity. -```php +```PHP // /src/Extension/Content/Product/CustomExtension.php /src/Extension/Content/Product/OneToManyExampleExtensionDefinition.php /src/Extension/Content/Product/OneToOneExampleExtensionDefinition.php /src/Elasticsearch/Product/MyProductEsDecorator.php /src/Core/Content/Example/ExampleEntity.php use Shopware\Core\Framework\DataAbstractionLayer\Entity; use Shopware\Core\Framework\DataAbstractionLayer\EntityCustomFieldsTrait; @@ -65,9 +65,9 @@ class ExampleEntity extends Entity Now follows the important part. For this to work, you have to add the Data Abstraction Layer \(DAL\) field `CustomFields` to your entity definition. -```php +```PHP // /src/Core/Content/Example/ExampleDefinition.php -use Shopware\Core\Framework\DataAbstractionLayer\Field\CustomFields; +use Shopware\Core\Framework\DataAbstractionLayer\Field\CustomFields; [...] class ExampleDefinition extends EntityDefinition @@ -97,7 +97,7 @@ Once again, this example is built upon the [Add custom complex data](../data-han If you want to support custom fields now, you have to add a new column `custom_fields` of type `JSON` to your migration. -```php +```PHP // /src/Migration/Migration1611664789Example.php public function update(Connection $connection): void { @@ -128,9 +128,9 @@ Note the new `custom_fields` column here. It has to be a JSON field and should d Make sure to understand entity translations in general first, which is explained here [Add data translations](../data-handling/add-data-translations). If you want your custom fields to be translatable, you can simply work with a `TranslatedField` here as well. -```php +```PHP // /src/Core/Content/Example/ExampleDefinition.php -use Shopware\Core\Framework\DataAbstractionLayer\Field\TranslatedField; +use Shopware\Core\Framework\DataAbstractionLayer\Field\TranslatedField; [...] @@ -156,9 +156,9 @@ Just add the `TranslatedField` and apply `customFields` as a parameter. In your translated entity definition, you then add the `CustomFields` field instead. -```php +```PHP // /src/Core/Content/Example/Aggregate/ExampleTranslation/ExampleTranslationDefinition.php -use Shopware\Core\Framework\DataAbstractionLayer\Field\CustomFields; +use Shopware\Core\Framework\DataAbstractionLayer\Field\CustomFields; [...] class ExampleTranslationDefinition extends EntityTranslationDefinition @@ -190,7 +190,7 @@ So let's assume you've got your own `example` entity up and running, and now you In that case, you can use your entities' repository and start creating or updating entities with custom fields. If you don't understand what's going on here, head over to our guide about [Writing data](../data-handling/writing-data) first. -```php +```PHP $this->swagExampleRepository->upsert([[ 'id' => '', 'customFields' => ['swag_example_size' => 15] @@ -201,7 +201,7 @@ This will execute perfectly fine, and you just saved a custom field with name `s As already mentioned, you do not have to define a custom field first before saving it. That's because there is no validation happening here yet, you can write whatever valid JSON you want to that column, so the following example would also execute without any issues: -```php +```PHP $this->swagExampleRepository->upsert([[ 'id' => '', 'customFields' => [ 'foo' => 'bar', 'baz' => [] ] @@ -216,19 +216,21 @@ So now you've already filled the custom fields of one of your entity instances v Only if you want your custom field to show up in the Administration and to be editable in there, you have to define the custom fields first in a custom field set. For this you have to use the custom fieldset repository, which can be retrieved from the dependency injection container via the `custom_field_set.repository` key and is used like any other repository. -```xml - - - - - - - ... - - - +```PHP +// /src/Resources/config/services.php +services(); + + $services->set(CustomFieldClass::class) + ->args([service('custom_field_set.repository')]); +}; ``` If you need to learn how that is done in full, head to our guide regarding [Writing data](../data-handling/writing-data). @@ -236,7 +238,7 @@ If you need to learn how that is done in full, head to our guide regarding [Writ Now use the `create` method of the repository to create a new custom field set. Plugin lifecycle events are perfect for this as the container can provide the `custom_field_set.repository` service and can be used to set up on installation and remove the set on removal. -```php +```PHP use Shopware\Core\System\CustomField\CustomFieldTypes; use \Shopware\Core\Defaults; @@ -275,7 +277,7 @@ $this->customFieldSetRepository->create([ This will now create a custom field set with the name `swag_example_set` and the field, `swag_example_size`. This time we also define its type, which should be of type integer here. The type is important to mention, because the Administration will use this information to display a proper field. Also, when trying to write the custom field `swag_example_size`, the value has to be an integer. The translated labels are added to both the field and the set, which are going to be displayed in the Administration. Also, the fallback language can be defined in case the system language is not guaranteed to be either en_GB or de_DE. - + If you have several custom fields and want to order them within a specific order, you can do so with the `customFieldPosition` property. ::: info @@ -299,7 +301,7 @@ While theoretically your custom field is now properly defined for the Administra On uninstallation of your plugin, you should remove your custom field definition. To update or delete a `custom_field_set`, you can use the standard repository methods like `update`, `upsert`, or `delete`: -```php +```PHP $setId = $this->customFieldSetRepository->searchIds((new Criteria())->addFilter(new EqualsFilter('name', 'swag_example_set')), $context)->firstId(); $this->customFieldSetRepository->delete([['id' => $setId]], $context); ``` @@ -314,7 +316,7 @@ UPDATE swag_example SET custom_fields = JSON_REMOVE(custom_fields, '$.swag_examp If you have a table with a lot of data, like orders or products, you should not approach it carelessly to avoid overwhelming the database with too many changes at once. This can look like this instead: -```php +```PHP $updateLimit = 1000; do { diff --git a/guides/plugins/plugins/framework/custom-field/fetching-data-from-entity-selection.md b/guides/plugins/plugins/framework/custom-field/fetching-data-from-entity-selection.md index 6876c300aa..05ec088e6f 100644 --- a/guides/plugins/plugins/framework/custom-field/fetching-data-from-entity-selection.md +++ b/guides/plugins/plugins/framework/custom-field/fetching-data-from-entity-selection.md @@ -25,7 +25,7 @@ To resolve the `id` and getting access to the product we have linked here, we ca Lets create a `ProductSubscriber` first which will listen to the `ProductEvents::PRODUCT_LOADED_EVENT`. -```php +```PHP // /src/Subscriber/ProductSubscriber.php /src/Resources/config/services.xml - +```PHP +// /src/Resources/config/services.php + +return static function (ContainerConfigurator $configurator): void { + $services = $configurator->services(); - - - - - - + $services->set(ProductSubscriber::class) + ->tag('kernel.event_subscriber'); +}; ``` Now our `ProductSubscriber` should be called every time a product is loaded, so we can resolve the custom field `custom_linked_product`. -```php +```PHP // /src/Subscriber/ProductSubscriber.php getEntities() as $productEntity) { $customFields = $productEntity->getCustomFields(); @@ -116,28 +115,29 @@ Inside the `onProductLoaded` method we can get access to the loaded product enti But, how we can load the linked product by its `id` if the custom field was set? We have to inject the product repository to achieve it. -First we update the `services.xml` and inject the product repository. +First we update the `services.php` and inject the product repository. + +```PHP +// /src/Resources/config/services.php +/src/Resources/config/services.xml - +use function Symfony\Component\DependencyInjection\Loader\Configurator\service; - +return static function (ContainerConfigurator $configurator): void { + $services = $configurator->services(); - - - - - - - + $services->set(ProductSubscriber::class) + ->args([service('product.repository')]) + ->tag('kernel.event_subscriber'); +}; ``` Now we can use the product repository in our subscriber. -```php +```PHP // /src/Subscriber/ProductSubscriber.php productRepository = $productRepository; } @@ -167,7 +167,7 @@ As you can see, the product repository was injected and is now available to the Let's have a look at the final implementation of the subscriber. -```php +```PHP // /src/Subscriber/ProductSubscriber.php productRepository = $productRepository; } diff --git a/guides/plugins/plugins/framework/data-handling/add-complex-data-to-existing-entities.md b/guides/plugins/plugins/framework/data-handling/add-complex-data-to-existing-entities.md index d7e1ff09d7..35e81865d9 100644 --- a/guides/plugins/plugins/framework/data-handling/add-complex-data-to-existing-entities.md +++ b/guides/plugins/plugins/framework/data-handling/add-complex-data-to-existing-entities.md @@ -29,7 +29,7 @@ You add new fields by overriding the method `extendFields` and add your new fiel Here's an example class called `CustomExtension`: -```php +```PHP // /src/Extension/Content/Product/CustomExtension.php /src/Resources/config/services.xml - +```PHP +// /src/Resources/config/services.php + +return static function (ContainerConfigurator $configurator): void { + $services = $configurator->services(); - - - - - - + $services->set(CustomExtension::class) + ->tag('shopware.entity.extension'); +}; ``` ### Adding a field with a database @@ -81,7 +80,7 @@ In this guide, you're extending the product entity in order to add a new string Let's start with the `CustomExtension` class by adding a new field in the `extendFields` method. -```php +```PHP // /src/Extension/Content/Product/CustomExtension.php /src/Extension/Content/Product/ExampleExtensionDefinition.php /src/Resources/config/services.xml - +```PHP +// /src/Resources/config/services.php + +return static function (ContainerConfigurator $configurator): void { + $services = $configurator->services(); - - - - + $services->set(CustomExtension::class) + ->tag('shopware.entity.extension'); - - - - - + $services->set(ExampleExtensionDefinition::class) + ->tag('shopware.entity.definition', ['entity' => 'swag_example_extension']); +}; ``` #### Adding the new database table Of course, you have to add the new database table via a [Database migration](../../plugin-fundamentals/database-migrations). Look at the guide linked above to see how exactly this is done. Here's the example migration and how it could look like: -```php +```PHP productRepository->upsert([[ 'id' => '', 'exampleExtension' => [ @@ -270,7 +268,7 @@ We can use the DAL event which gets fired every time the product entity is loade Below, you can find an example implementation where we add our extension when the product gets loaded. -```php +```PHP // /src/Subscriber/ProductSubscriber.php /src/Resources/config/services.php +/src/Resources/config/services.xml - +use Swag\BasicExample\Subscriber\ProductSubscriber; +use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; - +return static function (ContainerConfigurator $configurator): void { + $services = $configurator->services(); - - - - - - + $services->set(ProductSubscriber::class) + ->tag('kernel.event_subscriber'); +}; ``` ## Entity extension vs. Custom fields @@ -335,7 +332,7 @@ This feature is available since Shopware 6.6.10.0 In case your project or plugin requires many entity extensions, you can register a `BulkEntityExtension` which allows extending multiple entities at once: -```php +```PHP - - +```PHP +$services->set(\Examples\MyBulkExtension::class) + ->tag('shopware.bulk.entity.extension'); ``` diff --git a/guides/plugins/plugins/framework/data-handling/add-custom-complex-data.md b/guides/plugins/plugins/framework/data-handling/add-custom-complex-data.md index bd8add32d3..f11c67e4b4 100644 --- a/guides/plugins/plugins/framework/data-handling/add-custom-complex-data.md +++ b/guides/plugins/plugins/framework/data-handling/add-custom-complex-data.md @@ -27,7 +27,7 @@ In this guide we'll name our table `swag_example`, you'll find this name a few m As already mentioned in the prerequisites, creating a database table is done via plugin migrations [Plugin migrations](../../plugin-fundamentals/database-migrations), head over to this guide to understand how this example works. -```php +```PHP // /src/Migration/Migration1611664789Example.php /src/Core/Content/Example/ExampleDefinition.php`. Below you can see our example definition, which is explained afterwards: -```php +```PHP // /src/Core/Content/Example/ExampleDefinition.php /src/Core/Content/Example/ExampleDefinition.php - +```PHP +services(); - - - - - - + $services->set(ExampleDefinition::class) + ->tag('shopware.entity.definition', ['entity' => 'swag_example']); +}; ``` Please note the tag for your definition and the respective `entity` attribute, which has to contain the technical name of your entity, which you provided in your entity definition. In this case this must be `swag_example`. @@ -196,7 +196,7 @@ The properties of your entity class have to be at least `protected`, otherwise t For the same reason `readonly` properties are not allowed. This holds true not just for `Entity` classes, but for all classes that extend the generic `Struct` class. ::: -```php +```PHP // /src/Core/Content/Example/ExampleEntity.php /src/Core/Content/Example/ExampleDefinition.php class ExampleDefinition extends EntityDefinition { @@ -274,7 +274,7 @@ So create a `ExampleCollection` class in the same directory as your `ExampleDefi This is how your collection class could then look like: -```php +```PHP // /src/Core/Content/Example/ExampleCollection.php /src/Core/Content/Example/ExampleDefinition.php class ExampleDefinition extends EntityDefinition { diff --git a/guides/plugins/plugins/framework/data-handling/add-data-indexer.md b/guides/plugins/plugins/framework/data-handling/add-data-indexer.md index bd53d3936e..9d87ff4a02 100644 --- a/guides/plugins/plugins/framework/data-handling/add-data-indexer.md +++ b/guides/plugins/plugins/framework/data-handling/add-data-indexer.md @@ -12,7 +12,7 @@ This guide is built upon the [Plugin base guide](../../plugin-base-guide), but a It is possible to add data indexer for your own entities, like the one created in the [Adding custom complex data](./add-custom-complex-data) guide or for existing entities. However, if you want to react on changes of existing entities the preferred way should be subscribing to the events if available. See the [Index data using existing events](#index-data-using-existing-events) section below. To create a new indexer, just create a new class in your plugin: -```php +```PHP // /src/Core/Framework/DataAbstractionLayer/Indexing/ExampleIndexer.php - - - - - - - - - - - +```PHP +services(); + + $services->set(ExampleIndexer::class) + ->args([ + service(IteratorFactory::class), + service('customer.repository'), + service(Connection::class), + ]) + ->tag('shopware.entity_indexer'); +}; ``` The indexer service has to be tagged as `shopware.entity_indexer` in order to work. @@ -157,7 +163,7 @@ By default, all messages which are returned by the `public function update()` fu By default, indexing is also active while working with an indexer, which means, that entities that are written over the DAL also trigger `EntityWrittenContainerEvent` events. So the indexers are triggered again. This can lead to an infinite loop. Therefore, the connection should be used directly to alter data in the database. You can find more information about this in the corresponding ADR [when to use plain SQL or the DAL](../../../../../resources/references/adr/2021-05-14-when-to-use-plain-sql-or-dal.md). However, if you want to use the DAL for manipulation data in a data indexer, indexing can be disabled. This can be done by passing adding a flag to the context, as shown in the example below: -```php +```PHP public function update(EntityWrittenContainerEvent $event): ?EntityIndexingMessage { $updates = $event->getPrimaryKeys(CustomerDefinition::ENTITY_NAME); @@ -175,7 +181,7 @@ public function update(EntityWrittenContainerEvent $event): ?EntityIndexingMessa ## Index data using existing events -There are already a bunch of indexers in shopware that you can use. If you take a look at the `CustomerIndexer` or `CategoryIndexer` classes for example, you will see that they dispatch an event in the `handle` method. This should be used for indexing data of the main entities. Among others, the following indexers already exist and dispatch events that can be used for indexing data: +There are already a bunch of indexers in shopware that you can use. If you take a look at the `CustomerIndexer` or `CategoryIndexer` classes for example, you will see that they dispatch an event in the `handle` method. This should be used for indexing data of the main entities. Among others, the following indexers already exist and dispatch events that can be used for indexing data: * `CustomerIndexer` * `CategoryIndexer` @@ -194,7 +200,7 @@ There are already a bunch of indexers in shopware that you can use. If you take For this we need a new subscriber. If you are not familiar with a subscriber, have a look at our [Listening to events](../../plugin-fundamentals/listening-to-events) guide. For this example, we just write a new entry to the `log_entry` database table, indicating that a customer was updated. -```php +```PHP // /src/Service/Subscriber.php - - - - - - - - - +```PHP +services(); + + $services->set(Subscriber::class) + ->args([service(Connection::class)]) + ->tag('kernel.event_subscriber'); +}; ``` It is recommended to work directly with the `Connection` since the event is dispatched in the context of an indexer. If we would use the Data Abstraction Layer \(DAL\) for writing changes to the database, the indexer would be triggered again, because it listens for `EntityWrittenContainerEvent` events. This would lead to an infinite loop. Using the `Connection` directly prevents the DAL from dispatching entity written events. Also the performance of plain sql is much higher, which is very important for indexers in general. diff --git a/guides/plugins/plugins/framework/data-handling/add-data-translations.md b/guides/plugins/plugins/framework/data-handling/add-data-translations.md index 73ada1fd6f..3834fb26e9 100644 --- a/guides/plugins/plugins/framework/data-handling/add-data-translations.md +++ b/guides/plugins/plugins/framework/data-handling/add-data-translations.md @@ -44,7 +44,7 @@ The translation table's columns should be the following: This is how your migration could look like: -```php +```PHP // /src/Migration/Migration1612863838ExampleTranslation.php /src/Core/Content/Example/Aggregate/ExampleTranslation/ExampleTranslationDefinition.php /src/Resources/config/services.xml - - +```PHP +// /src/Resources/config/services.php +services(); - - - - + $services->set(ExampleDefinition::class) + ->tag('shopware.entity.definition', ['entity' => 'swag_example']); - - - - - + $services->set(ExampleTranslationDefinition::class) + ->tag('shopware.entity.definition', ['entity' => 'swag_example_translation']); +}; ``` ### Entity class @@ -162,7 +162,7 @@ So far we introduced our definition, we can create our `ExampleTranslationEntity Here's our `ExampleTranslationEntity`: -```php +```PHP // /src/Core/Content/Example/Aggregate/ExampleTranslation/ExampleTranslationEntity.php /src/Core/Content/Example/Aggregate/ExampleTranslation/ExampleTranslationDefinition.php class ExampleTranslationDefinition extends EntityTranslationDefinition { @@ -232,7 +232,7 @@ As we already know, we should create an `EntityCollection` for our `Entity` too. Our collection class could then look like this: -```php +```PHP // /src/Core/Content/Example/Aggregate/ExampleTranslation/ExampleTranslationCollection.php /src/Core/Content/Example/ExampleDefinition.php - - +```PHP +$services->set(Examples\ExampleEntity::class) + ->tag('shopware.entity'); ``` That's it. @@ -66,7 +65,7 @@ To define more fields, you typically use the `Field` attribute. The `Field` attribute requires the `type` parameter, which is the type of the field. The type can be any of the `FieldType` constants. -```php +```PHP - - - - - - - - +```PHP +// SwagBasicExample/src/Resources/config/services.php +services(); + + $services->set(ReadingData::class) + ->args([service('product.repository')]); +}; ``` And here's the respective class including its constructor: -```php +```PHP // SwagBasicExample/src/Service/ReadingData.php productRepository->search(new Criteria(), $context); @@ -95,7 +97,7 @@ Now let's get into actually filtering your search result to get more precise res Often you have an ID from an entity and you just want to find the whole dataset related to that ID, so here you go: -```php +```PHP public function readData(Context $context): void { $product = $this->productRepository->search(new Criteria([$myId]), $context)->first(); @@ -114,7 +116,7 @@ While searching for an ID will do the trick quite often, you might want to searc In order to do this, you can apply filters to the `Criteria` object, such as an `EqualsFilter`, which accepts a field name and the value to search for. You can find the `EqualsFilter` here: `Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter` -```php +```PHP public function readData(Context $context): void { $criteria = new Criteria(); @@ -136,7 +138,7 @@ For this case, you can combine filters using the `OrFilter` or the `AndFilter`, Let's just build the example mentioned above: -```php +```PHP public function readData(Context $context): void { $criteria = new Criteria(); @@ -161,7 +163,7 @@ E.g.: Fetch all products, whose name is `Example product`, but also return the t In that case, you can just use the `addPostFilter` instead of `addFilter`: -```php +```PHP public function readData(Context $context): void { $criteria = new Criteria(); @@ -181,7 +183,7 @@ There is more than just an `EqualsFilter`, which is the SQL equivalent of `WHERE Of course associations to other entities are also possible in Shopware 6. If you, for example, want to load all product-reviews, which is an entity itself, related to the product you have found, you can do so by adding associations to the criteria object. -```php +```PHP public function readData(Context $context): void { $criteria = new Criteria(); @@ -196,7 +198,7 @@ Just like the available entity fields, you can find all possible associations in Also worth to mention is the fact, that you can chain the association key. E.g. a product-review has another association to the customer, who created that review. If you want access to both the review itself, as well as the customer, you can just write the association like that: -```php +```PHP public function readData(Context $context): void { $criteria = new Criteria(); @@ -213,7 +215,7 @@ Yes, this is doable. You can apply filters to an association. E.g. "Add all prod For this we can use `getAssociation` instead, which basically returns its own `Criteria` object, on which you can apply a filter. -```php +```PHP public function readData(Context $context): void { $criteria = new Criteria(); @@ -231,7 +233,7 @@ Once again: Note, that we used `getAssociation` here now instead of `addAssociat Another example to clarify what's going on here: -```php +```PHP public function readData(Context $context): void { // This will always return the product with the given name, no matter if it has a review with 4 or more stars. @@ -258,7 +260,7 @@ Every `ManyToMany` association comes with a mapping entity, such as the `Product The following example will **not** work: -```php +```PHP public function readData(Context $context): void { $criteria = new Criteria(); @@ -274,7 +276,7 @@ Since mapping entities just consist of two primary keys, there is no need to sea Of course you can also aggregate your data. Just like filters and associations, this can be done by using an `addAggregation` method on the `Criteria` object. Let's create an example aggregation, that returns the average rating for a product: -```php +```PHP public function readData(Context $context): void { $criteria = new Criteria(); @@ -298,7 +300,7 @@ There's just a few more things missing: Limiting your result intentionally to e. Let's start with the limiting of the result: -```php +```PHP public function readData(Context $context): void { $criteria = new Criteria(); @@ -311,7 +313,7 @@ public function readData(Context $context): void That's quite self-explanatory, isn't it? Just use the `setLimit` method with your desired limit as parameter. Little spoiler: It's the same for the offset! -```php +```PHP public function readData(Context $context): void { $criteria = new Criteria(); @@ -325,7 +327,7 @@ public function readData(Context $context): void This way you get the 2nd possible product. But since you didn't define a sorting yourself, the result can be quite confusing, so let's add a sorting. -```php +```PHP public function readData(Context $context): void { $criteria = new Criteria(); @@ -350,7 +352,7 @@ Imagine you need to iterate over all products of your shop, which contains more Instead, the `RepositoryIterator` will return a batch of data, which size you can define, with each iteration. Just be sure to not use it unnecessarily, since it will create a new database request with each iteration, which is not needed for smaller chunks of data. -```php +```PHP public function readData(Context $context): void { $criteria = new Criteria(); @@ -374,7 +376,7 @@ Put differently, you must ensure that your sorting means that there's only one c For example, ordering products by `manufacturerNumber` alone could cause this issue, because several products can have the same `manufacturerNumber`, so there's several correct orderings of those products. On the other hand, because each product is guaranteed to have a unique ID, sorting by ID is an easy way to mitigate this issue: -```php +```PHP $criteria = new Criteria(); //This sorting alone would result in sorting that is nondeterministic as several products might have the same value for this field: $criteria->addSorting(new FieldSorting('manufacturerNumber')); diff --git a/guides/plugins/plugins/framework/data-handling/replacing-associated-data.md b/guides/plugins/plugins/framework/data-handling/replacing-associated-data.md index b0e5d1e0ab..f9aadc4be3 100644 --- a/guides/plugins/plugins/framework/data-handling/replacing-associated-data.md +++ b/guides/plugins/plugins/framework/data-handling/replacing-associated-data.md @@ -25,7 +25,7 @@ So let's start with the main issue going on here. Let's imagine you've created a The following example will show you how **not** to do it. It's assuming that you've previously assigned the category `Old category` with the ID `oldId` to the product. -```php +```PHP public function replaceData(Context $context): void { $this->productRepository->update([ @@ -55,25 +55,29 @@ In order to delete it, we once again need its repository. The name for the entit So let's inject this repository into our class called `ReplacingData`: -```xml -// SwagBasicExample/src/Resources/config/services.xml - - - - - - - - - - +```PHP +// SwagBasicExample/src/Resources/config/services.php +services(); + + $services->set(ReplacingData::class) + ->args([ + service('product.repository'), + service('product_category.repository'), + ]); +}; ``` Afterwards, you can just use the `delete` method on the repository, just like you did before in the [Writing data](writing-data) guide. -```php +```PHP public function replaceData(Context $context): void { $this->productCategoryRepository->delete([ @@ -87,7 +91,7 @@ public function replaceData(Context $context): void Now the association to the old category was removed and you can now use the code above to add the new category instead. -```php +```PHP public function replaceData(Context $context): void { $productId = 'myProductId'; @@ -118,7 +122,7 @@ And that's it, you've successfully deleted one association and then replaced it Replacing `OneToOne` or `ManyToOne` associations works just like expected via an `update` call, e.g. for the tax of a product: -```php +```PHP public function replaceData(Context $context): void { $this->productRepository->update([ diff --git a/guides/plugins/plugins/framework/data-handling/using-database-events.md b/guides/plugins/plugins/framework/data-handling/using-database-events.md index 2c6278da94..cd1864a285 100644 --- a/guides/plugins/plugins/framework/data-handling/using-database-events.md +++ b/guides/plugins/plugins/framework/data-handling/using-database-events.md @@ -36,7 +36,7 @@ You can use this event to capture state, perform actions, and sync data after an Below is an example subscriber listening to the generic entity write event and logging the ID's of the written entities. -```php +```PHP // /src/Subscriber/EntityWriteSubscriber.php /src/Subscriber/DeleteSubscriber.php /src/Subscriber/ProductSubscriber.php /src/Resources/config/services.xml - +```PHP +// /src/Resources/config/services.php + +return static function (ContainerConfigurator $configurator): void { + $services = $configurator->services(); - - - - - - + $services->set(ProductSubscriber::class) + ->tag('kernel.event_subscriber'); +}; ``` diff --git a/guides/plugins/plugins/framework/data-handling/writing-data.md b/guides/plugins/plugins/framework/data-handling/writing-data.md index 520a8a9916..ef73087b91 100644 --- a/guides/plugins/plugins/framework/data-handling/writing-data.md +++ b/guides/plugins/plugins/framework/data-handling/writing-data.md @@ -32,25 +32,29 @@ Dealing with the Data Abstraction Layer is done by using the automatically gener The repository's service name follows this pattern: `entity_name.repository` For products this then would be `product.repository`. Additional to that, you're going to need the `tax` repository later for this guide, so let's add this as well already. -```xml -// SwagBasicExample/src/Resources/config/services.xml - - - - - - - - - - +```PHP +// SwagBasicExample/src/Resources/config/services.php +services(); + + $services->set(WritingData::class) + ->args([ + service('product.repository'), + service('tax.repository'), + ]); +}; ``` And here's the respective class including its constructor: -```php +```PHP // SwagBasicExample/src/Service/WritingData.php productRepository->create([ @@ -105,7 +109,7 @@ private function getTaxId(Context $context): string First of all, for this example you'll need the following new imports: -```php +```PHP use Shopware\Core\Defaults; use Shopware\Core\Framework\Context; use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria; @@ -130,7 +134,7 @@ And that's it, this will write and create your first entity, a product. Of cours In Shopware 6 we're using UUIDs for the ID fields in the entities. This comes with a major advantage: You can define your IDs when creating an entity already and thus do not have to figure out which ID your newly created entity received, e.g. by auto-increment. -```php +```PHP public function writeData(): void { $context = Context::createDefaultContext(); @@ -164,7 +168,7 @@ So note the `id` field we've provided now - even though you're just creating you So what if you don't want to create a new entity, but rather update an existing one? For that case, you can use the `update` method on the repository. Let's just update our previously created product and change its name. -```php +```PHP public function writeData(Context $context): void { $criteria = new Criteria(); @@ -197,7 +201,7 @@ In order to create data, we've used the `create` method. For updating data, we'v Here's an example on how to delete the previously created product: -```php +```PHP public function writeData(Context $context): void { $criteria = new Criteria(); @@ -225,7 +229,7 @@ If you don't know how to add associations to an entity, maybe to your own entity Earlier in this guide, you created a product and used an existing tax entity for that case. This is representing a ManyToOne association, but OneToOne associations are handled the same. -```php +```PHP public function writeData(Context $context): void { $this->productRepository->create([ @@ -256,7 +260,7 @@ OneToMany and ManyToMany associations are handled the same. An example in the product context would be assigning a category to a product. -```php +```PHP public function writeData(Context $context): void { $criteria = new Criteria(); @@ -288,7 +292,7 @@ Every `ManyToMany` association comes with a mapping entity. It's important to kn The following example will fail: -```php +```PHP public function writeData(Context $context): void { // This is the product_category.repository service @@ -309,7 +313,7 @@ Your only way to solve this is by replacing the association. Head over to our gu So you don't want to assign an existing tax entity when creating a product, but rather you'd like to create a new tax entity in the same step. That is also possible, and this section will show you an example on how to do it. -```php +```PHP public function writeData(Context $context): void { $this->productRepository->create([ @@ -330,7 +334,7 @@ In order to create a tax entity while creating the product, you have to provide And that's already it - now the tax will be created in the same step when the product is created and will be assigned automatically. This works almost the same for `ToMany` associations. -```php +```PHP public function writeData(Context $context): void { $this->productRepository->create([ diff --git a/guides/plugins/plugins/framework/event/finding-events.md b/guides/plugins/plugins/framework/event/finding-events.md index 421154e622..787e19f6c8 100644 --- a/guides/plugins/plugins/framework/event/finding-events.md +++ b/guides/plugins/plugins/framework/event/finding-events.md @@ -33,7 +33,7 @@ Finding those "event classes" can be done by searching for the term `@Event` in You can use those events in a [subscriber](../../plugin-fundamentals/listening-to-events) like the following: -```php +```PHP public static function getSubscribedEvents(): array { return [ @@ -48,7 +48,7 @@ As you can see, you can either use the event class constants, if available, or t You'll then have access to several event specific information, e.g. your listener method will have access to an [EntityWrittenEvent](https://github.com/shopware/shopware/blob/v6.4.0.0/src/Core/Framework/DataAbstractionLayer/Event/EntityWrittenEvent.php) instance when subscribing to the `written` event. -```php +```PHP public function onCustomEntityWritten(EntityWrittenEvent $event): void { } @@ -72,7 +72,7 @@ There are multiple ways to find them: You will most likely look into our Core code quite a lot, while trying to understand what's happening and why things are happening. On your journey looking through the code, you may stumble upon code looking like this: -```php +```PHP $someEvent = new SomeEvent($parameters, $moreParameters); $this->eventDispatcher->dispatch($someEvent, $someEvent->getName()); ``` @@ -85,7 +85,7 @@ If the second parameter is not applied, the class name will be used as a fallbac When subscribing to those events, your event listener method will have access to the previously created event instance. -```php +```PHP public static function getSubscribedEvents(): array { return [ @@ -121,27 +121,30 @@ Every service, that wants to fire an event sooner or later, needs access to the Hence, you can have a look at all the service definitions for the [Dependency injection container](../../plugin-fundamentals/dependency-injection) and therefore quickly figure out, which services and classes are having access to the said `event_dispatcher`: -```xml - +```PHP + +use Some\Service; +use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; - - - - - - - +use function Symfony\Component\DependencyInjection\Loader\Configurator\service; + +return static function (ContainerConfigurator $configurator): void { + $services = $configurator->services(); + + $services->set(Service::class) + ->args([ + service('Another/Service'), + service('event_dispatcher'), + ]); +}; ``` -Therefore, you could simply search for occurrences of the `event_dispatcher` in the respective `.xml` files. +Therefore, you could simply search for occurrences of the `event_dispatcher` in the respective service definition files. You can also do this the other way around, by having a look at the service's constructor parameters. -```php +```PHP public function __construct( Some\Service $someService, EventDispatcherInterface $eventDispatcher @@ -178,7 +181,7 @@ a `Criteria` instance. Let's have a look at an [example code](https://github.com/shopware/shopware/blob/v6.4.0.0/src/Core/Content/Product/SalesChannel/Listing/ResolveCriteriaProductListingRoute.php#L55-L59): -```php +```PHP #[Route(path: '/store-api/product-listing/{categoryId}', name: 'store-api.product.listing', methods: ['POST'], defaults: ['_entity' => 'product'])] public function load(string $categoryId, Request $request, SalesChannelContext $context, Criteria $criteria): ProductListingRouteResponse { @@ -217,7 +220,7 @@ Therefore, we have added fine-grained route events that are thrown for every rou To subscribe to a specific event, replace the `{route}` placeholder with the [actual symfony route name](https://symfony.com/doc/current/routing.html), e.g. `store-api.product.listing`. -```php +```PHP public static function getSubscribedEvents(): array { return [ diff --git a/guides/plugins/plugins/framework/extension/creating-custom-extension.md b/guides/plugins/plugins/framework/extension/creating-custom-extension.md index 22a8804f65..f86944f5a5 100644 --- a/guides/plugins/plugins/framework/extension/creating-custom-extension.md +++ b/guides/plugins/plugins/framework/extension/creating-custom-extension.md @@ -17,7 +17,7 @@ While Shopware provides many built-in extension points, you may need to create c All extension points must extend the base `Extension` class and define a typed result: -```php +```PHP extensionDispatcher->publish( CustomProductFilterExtension::NAME, $extension, @@ -158,7 +158,7 @@ class CustomProductService ### 3. Create an Event Subscriber -```php +```PHP 'onProductFilter', ]; } - + public function onProductFilter(CustomProductFilterExtension $event): void { // Check if we should apply custom filtering if (!$this->shouldApplyCustomFilter($event->filterParams)) { return; } - + // Get filtered product IDs from external API $filteredIds = $this->apiService->getFilteredProductIds( $event->criteria, $event->context, $event->filterParams ); - + if (empty($filteredIds)) { // No products match the filter $event->result = new EntitySearchResult( @@ -208,21 +208,21 @@ class CustomProductFilterSubscriber implements EventSubscriberInterface $event->stopPropagation(); return; } - + // Create new criteria with filtered IDs $newCriteria = clone $event->criteria; $newCriteria->setIds($filteredIds); - + // Apply additional filtering $filteredProducts = $this->filterService->applyBusinessRules( $newCriteria, $event->context ); - + $event->result = $filteredProducts; $event->stopPropagation(); } - + private function shouldApplyCustomFilter(array $filterParams): bool { return isset($filterParams['custom_filter']) && $filterParams['custom_filter'] === true; @@ -232,32 +232,34 @@ class CustomProductFilterSubscriber implements EventSubscriberInterface ### 4. Register Services -```xml - - - - - - - - - - - +```PHP +// services.php +$services->set(MyPlugin\Service\CustomProductService::class) + ->args([ + service(Shopware\Core\Framework\Extensions\ExtensionDispatcher::class), + service('product.repository'), + ]); + +$services->set(MyPlugin\Subscriber\CustomProductFilterSubscriber::class) + ->args([ + service(MyPlugin\Service\ExternalApiService::class), + service(MyPlugin\Service\ProductFilterService::class), + ]) + ->tag('kernel.event_subscriber'); ``` ## Advanced Extension Patterns ### 1. Conditional Extension Execution -```php +```PHP public function onExtension(MyExtension $event): void { // Only execute under certain conditions if (!$this->shouldExecute($event)) { return; } - + $event->result = $this->customImplementation($event); $event->stopPropagation(); } @@ -270,7 +272,7 @@ private function shouldExecute(MyExtension $event): bool ### 2. Extension with Error Handling -```php +```PHP public function onExtension(MyExtension $event): void { try { @@ -282,7 +284,7 @@ public function onExtension(MyExtension $event): void 'error' => $e->getMessage(), 'extension' => get_class($event) ]); - + // The extension system will handle the error // and potentially dispatch error events } @@ -291,7 +293,7 @@ public function onExtension(MyExtension $event): void ### 3. Extension with Data Enrichment -```php +```PHP public function onExtension(MyExtension $event): void { // Don't replace the result, just enrich it @@ -308,14 +310,14 @@ private function enrichResult($result, MyExtension $event) 'processedAt' => new \DateTime(), 'context' => $event->context->getSalesChannelId() ])); - + return $result; } ``` ### 4. Multi-Phase Extension -```php +```PHP public static function getSubscribedEvents(): array { return [ @@ -416,12 +418,12 @@ Use `.error` events to: Here's a complete example of a plugin that creates and uses a custom extension point: -```php +```PHP // 1. Extension class final class ProductRecommendationExtension extends Extension { public const NAME = 'my-plugin.product-recommendation'; - + public function __construct( public readonly ProductEntity $product, public readonly SalesChannelContext $context, @@ -435,7 +437,7 @@ class ProductRecommendationService public function getRecommendations(ProductEntity $product, SalesChannelContext $context): ProductCollection { $extension = new ProductRecommendationExtension($product, $context); - + return $this->extensionDispatcher->publish( ProductRecommendationExtension::NAME, $extension, @@ -456,7 +458,7 @@ class ProductRecommendationSubscriber implements EventSubscriberInterface 'my-plugin.product-recommendation.pre' => 'onGetRecommendations', ]; } - + public function onGetRecommendations(ProductRecommendationExtension $event): void { // Custom AI-powered recommendations @@ -465,7 +467,7 @@ class ProductRecommendationSubscriber implements EventSubscriberInterface $event->context, $event->limit ); - + $event->result = $recommendations; $event->stopPropagation(); } diff --git a/guides/plugins/plugins/framework/extension/finding-extensions.md b/guides/plugins/plugins/framework/extension/finding-extensions.md index 9bff01776a..b4bc923b82 100644 --- a/guides/plugins/plugins/framework/extension/finding-extensions.md +++ b/guides/plugins/plugins/framework/extension/finding-extensions.md @@ -42,7 +42,7 @@ Here are some common Extension Point you might encounter: #### Product Extensions -```php +```PHP // Product price calculation src/Core/Content/Product/Extension/ProductPriceCalculationExtension.php @@ -55,7 +55,7 @@ src/Core/Content/Product/Extension/ProductListingCriteriaExtension.php #### Cart Extensions -```php +```PHP // Checkout place order src/Core/Checkout/Cart/Extension/CheckoutPlaceOrderExtension.php @@ -65,7 +65,7 @@ src/Core/Checkout/Cart/Extension/CheckoutCartRuleLoaderExtension.php #### CMS Extensions -```php +```PHP // CMS slots data enrichment src/Core/Content/Cms/Extension/CmsSlotsDataEnrichExtension.php @@ -81,11 +81,11 @@ Extension Points follow a consistent naming pattern: Extension Points use a `NAME` constant that defines the event name: -```php +```PHP final class ResolveListingExtension extends Extension { public const NAME = 'listing-loader.resolve'; - + // ... } ``` @@ -95,7 +95,7 @@ final class ResolveListingExtension extends Extension Extension Points are dispatched with lifecycle suffixes: - `{name}.pre` - Before the default implementation -- `{name}.post` - After the default implementation +- `{name}.post` - After the default implementation - `{name}.error` - When an error occurs ## Finding Extension Usage @@ -104,17 +104,16 @@ Extension Points are dispatched with lifecycle suffixes: Services that use Extension Points typically inject the `ExtensionDispatcher`: -```xml - - - +```PHP +$services->set(Some\Service::class) + ->args([service(Shopware\Core\Framework\Extensions\ExtensionDispatcher::class)]); ``` ### In Constructor Parameters Look for services that inject the `ExtensionDispatcher`: -```php +```PHP public function __construct( private readonly ExtensionDispatcher $extensionDispatcher ) { @@ -125,7 +124,7 @@ public function __construct( Extension Points are typically dispatched using this pattern: -```php +```PHP $extension = new SomeExtension($parameters); $result = $this->extensionDispatcher->publish( SomeExtension::NAME, @@ -147,11 +146,11 @@ $result = $this->extensionDispatcher->publish( **Event Name**: `product.calculate-prices` **Return Type**: `void` -```php +```PHP final class ProductPriceCalculationExtension extends Extension { public const NAME = 'product.calculate-prices'; - + public function __construct( public readonly iterable $products, public readonly SalesChannelContext $context @@ -165,11 +164,11 @@ final class ProductPriceCalculationExtension extends Extension **Event Name**: `listing-loader.resolve` **Return Type**: `EntitySearchResult` -```php +```PHP final class ResolveListingExtension extends Extension { public const NAME = 'listing-loader.resolve'; - + public function __construct( public readonly Criteria $criteria, public readonly SalesChannelContext $context @@ -185,11 +184,11 @@ final class ResolveListingExtension extends Extension **Event Name**: `checkout.place-order` **Return Type**: `OrderPlaceResult` -```php +```PHP final class CheckoutPlaceOrderExtension extends Extension { public const NAME = 'checkout.place-order'; - + public function __construct( public readonly Cart $cart, public readonly SalesChannelContext $context @@ -205,11 +204,11 @@ final class CheckoutPlaceOrderExtension extends Extension **Event Name**: `cms.slots.data-enrich` **Return Type**: `CmsSlotCollection` -```php +```PHP final class CmsSlotsDataEnrichExtension extends Extension { public const NAME = 'cms.slots.data-enrich'; - + public function __construct( public readonly CmsSlotCollection $slots, public readonly SalesChannelContext $context @@ -223,7 +222,7 @@ final class CmsSlotsDataEnrichExtension extends Extension Create an event subscriber to listen for Extension Points: -```php +```PHP 'onResolveListing', ]; } - + public function onResolveListing(ResolveListingExtension $event): void { // Custom logic here @@ -253,10 +252,9 @@ class ProductListingSubscriber implements EventSubscriberInterface Register your subscriber in the service configuration: -```xml - - - +```PHP +$services->set(MyPlugin\Subscriber\ProductListingSubscriber::class) + ->tag('kernel.event_subscriber'); ``` ## Extension Lifecycle @@ -270,7 +268,7 @@ Extension Points follow a specific lifecycle: ### Lifecycle Example -```php +```PHP public function handleExtension(SomeExtension $event): void { // This runs in the .pre phase @@ -293,7 +291,7 @@ public function handlePostExtension(SomeExtension $event): void Always use proper type hints for Extension Point parameters: -```php +```PHP public function onResolveListing(ResolveListingExtension $event): void { // Type-safe access to properties @@ -306,14 +304,14 @@ public function onResolveListing(ResolveListingExtension $event): void Check if a result has already been set: -```php +```PHP public function onExtension(SomeExtension $event): void { if ($event->result !== null) { // Another extension already provided a result return; } - + $event->result = $this->myImplementation($event); } ``` @@ -322,7 +320,7 @@ public function onExtension(SomeExtension $event): void Only stop propagation when you're providing a complete replacement: -```php +```PHP public function onExtension(SomeExtension $event): void { if ($this->shouldReplaceDefault($event)) { @@ -337,7 +335,7 @@ public function onExtension(SomeExtension $event): void Extension Points have built-in error handling, but you can also handle errors gracefully: -```php +```PHP public function onExtension(SomeExtension $event): void { try { @@ -360,7 +358,7 @@ The Symfony profiler shows all dispatched Extension Points in the "Events" tab. You can log Extension Point calls to understand the flow: -```php +```PHP public function onExtension(SomeExtension $event): void { $this->logger->debug('Extension called', [ diff --git a/guides/plugins/plugins/framework/filesystem/filesystem.md b/guides/plugins/plugins/framework/filesystem/filesystem.md index 5e3028f755..a56e418bc9 100644 --- a/guides/plugins/plugins/framework/filesystem/filesystem.md +++ b/guides/plugins/plugins/framework/filesystem/filesystem.md @@ -34,7 +34,7 @@ However, every plugin/bundle gets an own namespace that should be used for priva To make use of the filesystem, we register a new service, which helps to read and write files to the filesystem. -```php +```PHP // /src/Service/ExampleFilesystemService.php /src/Resources/config/services.xml - - - - - - - - - - - +```PHP +// /src/Resources/config/services.php +services(); + + $services->set(ExampleFilesystemService::class) + ->args([ + service('swag_basic_example.filesystem.public'), + service('swag_basic_example.filesystem.private'), + ]); + // There are also predefined file system services: + // ->args([ + // service('shopware.filesystem.private'), + // service('shopware.filesystem.public'), + // ]) +}; ``` Now, this service can be used to read or write files to the private plugin filesystem or to list all files in the public plugin filesystem. You should visit the [Flysystem API documentation](https://flysystem.thephpleague.com/docs/usage/filesystem-api/) for more information. diff --git a/guides/plugins/plugins/framework/flow/add-flow-builder-action.md b/guides/plugins/plugins/framework/flow/add-flow-builder-action.md index bfc7f40e32..ad85cb5bf7 100644 --- a/guides/plugins/plugins/framework/flow/add-flow-builder-action.md +++ b/guides/plugins/plugins/framework/flow/add-flow-builder-action.md @@ -33,7 +33,7 @@ To create a custom flow action, firstly you have to make a plugin and install it First of all, we need to define an aware interface for your own action. I intended to create the `CreateTagAction`, so I need to create a related aware named `TagAware`, will be placed in directory `/src/Core/Framework/Event`. Our new interface has to extend from interfaces `Shopware\Core\Framework\Event\FLowEventAware`: -```php +```PHP // /src/Core/Framework/Event/TagAware.php /src/Core/Content/Flow/Dispatching/Action`. Below you can find an example implementation: -```php +```PHP // /src/Core/Content/Flow/Dispatching/Action/CreateTagAction.php getStore($key)` if you want to get the data from aware interfaces. E.g: `tag_id` in `TagAware`, `customer_id` from `CustomerAware` and so on. - Use `$flow->getData($key)` if you want to get the data from original events or additional data. E.g: `tag`, `customer`, `contactFormData` and so on. -You also need to register this action in the container as a service. Make sure to define a tag `` at `/src/Resources/config/services.xml`. This tag will ensure that your action is included in the response of the *`/api/_info/flow-actions.json`* API. The priority attribute will determine the order of the action in the API response. +You also need to register this action in the container as a service. Make sure to define a tag `flow.action` with `priority: 600` at `/src/Resources/config/services.php`. This tag will ensure that your action is included in the response of the *`/api/_info/flow-actions.json`* API. The priority attribute will determine the order of the action in the API response. -```XML -// /src/Resources/config/services.xml - - - - +```PHP +// /src/Resources/config/services.php +$services->set(Swag\CreateTagAction\Core\Content\Flow\Dispatching\Action\CreateTagAction::class) + ->args([service('tag.repository')]) + ->tag('flow.action', ['priority' => 600, 'key' => 'action.create.tag']); ``` Great, your own action is created completely. Let's go to the next step. @@ -163,7 +162,7 @@ There are three scopes for the `CreateTagAction`: - Just define the empty array in `CreateTagAction::requirements` -```php +```PHP // plugin root>/src/Core/Content/Flow/Dispatching/Action/CreateTagAction.php ... @@ -185,7 +184,7 @@ Here, the action name is empty as the action name snippet is not yet defined. Make the `CreateTagAction` available for all events related to Order and Customer. -```php +```PHP // /src/Core/Content/Flow/Dispatching/Action/CreateTagAction.php ... @@ -203,7 +202,7 @@ Make the `CreateTagAction` available for all events related to Order and Custome - Event must implement the `TagAware` -```php +```PHP // /src/Core/Content/Flow/Subscriber/BusinessEventCollectorSubscriber.php /src/Core/Content/Flow/Dispatching/Action/CreateTagAction.php ... @@ -270,7 +269,7 @@ class BasicExampleEvent extends Event implements TagAware - To show the new event in Flow Builder Triggers list -```php +```PHP // /src/Core/Content/Subscriber/BusinessEventCollectorSubscriber.php /src/Resources/config/services.xml`. +And don't forget to register your subscriber to the container at `/src/Resources/config/services.php`. -```xml - - - - +```PHP + +$services->set(Swag\CreateTagAction\Core\Content\Subscriber\BusinessEventCollectorSubscriber::class) + ->args([service(Shopware\Core\Framework\Event\BusinessEventCollector::class)]) + ->tag('kernel.event_subscriber'); ``` - Define the Event snippet -```json +```JSON // /src/Resources/app/administration/src/module/sw-flow/snippet/en-GB.json { "sw-flow": { @@ -421,7 +420,7 @@ Component.override('sw-flow-sequence-action', { } return this.$super('getActionDescriptions', sequence) }, - + getCreateTagDescription(config) { const tags = config.tags.join(', '); diff --git a/guides/plugins/plugins/framework/flow/add-flow-builder-trigger.md b/guides/plugins/plugins/framework/flow/add-flow-builder-trigger.md index 4629c278a4..367a04fa27 100644 --- a/guides/plugins/plugins/framework/flow/add-flow-builder-trigger.md +++ b/guides/plugins/plugins/framework/flow/add-flow-builder-trigger.md @@ -52,7 +52,7 @@ In this example, we will name it ExampleEvent to some actions related to custome Below you can find an example implementation: -```php +```PHP // /src/Core/Checkout/Customer/Event/ExampleEvent.php /src/Core/Checkout/Customer/Subscriber/BusinessEventCollectorSubscriber.php /src/Core/Checkout/Customer/Subscriber/BusinessEventCollectorSubscriber.php public static function getSubscribedEvents() { @@ -392,14 +392,13 @@ public static function getSubscribedEvents() } ``` -And remember to register your subscriber to the container at `/src/Resources/config/services.xml` +And remember to register your subscriber to the container at `/src/Resources/config/services.php` -```xml -// /src/Resources/config/services.xml - - - - +```PHP +// /src/Resources/config/services.php +$services->set(Swag\ExamplePlugin\Core\Checkout\Customer\Subscriber\BusinessEventCollectorSubscriber::class) + ->args([service(Shopware\Core\Framework\Event\BusinessEventCollector::class)]) + ->tag('kernel.event_subscriber'); ``` Well done, you have successfully created your own flow trigger. diff --git a/guides/plugins/plugins/framework/rule/add-custom-rules.md b/guides/plugins/plugins/framework/rule/add-custom-rules.md index 0b1516632d..64edc5a371 100644 --- a/guides/plugins/plugins/framework/rule/add-custom-rules.md +++ b/guides/plugins/plugins/framework/rule/add-custom-rules.md @@ -29,7 +29,7 @@ To create a custom rule, we have to implement both backend \(PHP\) code and a us First of all, we need a new Rule class. In this example, we name it as `FirstMondayOfTheMonthRule`. It will be placed in the directory `/src/Core/Rule`. Our new class has to extend from the abstract class `Shopware\Core\Framework\Rule\Rule`. Below you can find an example implementation. -```php +```PHP // /src/Core/Rule/FirstMondayOfTheMonthRule.php getSalesChannelContext()->getCustomer(); $loggedIn = $customer !== null; @@ -121,7 +121,7 @@ $loggedIn = $customer !== null; It is possible to add config to our rule. This makes it possible to skip the [Custom rule component](#custom-rule-component) and the [Custom rule Administration template](#custom-rule-administration-template) parts. -```php +```PHP public function getConfig(): RuleConfig { return (new RuleConfig())->booleanField('isFirstMondayOfTheMonth'); @@ -134,7 +134,7 @@ when [Showing rule in the Administration](#showing-rule-in-the-administration) w You can access all active rules by using the `getRuleIds` method of the context. -```php +```PHP $context->getRuleIds(); ``` diff --git a/guides/plugins/plugins/framework/store-api/add-store-api-route.md b/guides/plugins/plugins/framework/store-api/add-store-api-route.md index eb736b4561..5b7320fd55 100644 --- a/guides/plugins/plugins/framework/store-api/add-store-api-route.md +++ b/guides/plugins/plugins/framework/store-api/add-store-api-route.md @@ -29,7 +29,7 @@ All fields that should be available through the API require the flag `ApiAware` First of all, we create an abstract class called `AbstractExampleRoute`. This class has to contain a method `getDecorated` and a method `load` with a `Criteria` and `SalesChannelContext` as parameter. The `load` method has to return an instance of `ExampleRouteResponse`, which we will create later on. -```php +```PHP // /src/Core/Content/Example/SalesChannel/AbstractExampleRoute.php /src/Core/Content/Example/SalesChannel/ExampleRoute.php - - - - - - - - +```PHP +services(); + + $services->set(ExampleRoute::class) + ->args([service('swag_example.repository')]); +}; ``` ### Route response After we have created our route, we need to create the mentioned `ExampleRouteResponse`. This class should extend from `Shopware\Core\System\SalesChannel\StoreApiResponse`, consequently inheriting a property `$object` of type `Shopware\Core\Framework\DataAbstractionLayer\Search\EntitySearchResult`. The `StoreApiResponse` parent constructor takes accepts one argument `$object` in order to set the value for the `$object` property (currently we provide this parameter our `ExampleRoute`). Finally, we add a method `getExamples` in which we return our entity collection that we got from the object. -```php +```PHP // /src/Core/Content/Example/SalesChannel/ExampleRouteResponse.php /src/Resources/config/` location. Have a look at the official [Symfony documentation](https://symfony.com/doc/current/routing.html) about routes and how they are registered. +The last thing we need to do now is to tell Shopware how to look for new routes in our plugin. This is done with a `routes.php` file at `/src/Resources/config/` location. Take a look at the official [Symfony documentation](https://symfony.com/doc/current/routing.html) about routes and how they are registered. + +```PHP +// /src/Resources/config/routes.php +/src/Resources/config/routes.xml - - +use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; - - +return static function (RoutingConfigurator $routes): void { + $routes->import('../../Core/**/*Route.php', 'attribute'); +}; ``` ## Check route via Symfony debugger @@ -157,7 +158,7 @@ The last thing we need to do now is to tell Shopware how to look for new routes To check if your route was registered correctly, you can use the [Symfony route debugger](https://symfony.com/doc/current/routing.html#debugging-routes). ```bash -// +// $ ./bin/console debug:router store-api.example.search ``` @@ -165,7 +166,7 @@ $ ./bin/console debug:router store-api.example.search To add the route to the Stoplight page, a JSON file is needed in a specific [format](https://swagger.io/specification/#paths-object). It contains information about the paths, methods, parameters, and more. You must place the JSON file in `/src/Resources/Schema/StoreApi/` so the shopware internal OpenApi3Generator can find it (for Admin API endpoints, use `AdminApi`). -```javascript +```JAVASCRIPT // /src/Resources/Schema/StoreApi/example.json { "openapi": "3.0.0", @@ -225,7 +226,7 @@ Your generated request and response could look like this: #### Request -```json +```JSON { "page": 0, "limit": 0, @@ -277,7 +278,7 @@ Your generated request and response could look like this: #### Response -```json +```JSON { "total": 0, "aggregations": {}, @@ -298,7 +299,7 @@ Your generated request and response could look like this: If you want to access the functionality of your route also from the Storefront you need to make it available there by adding a custom [Storefront controller](../../storefront/add-custom-controller) that will wrap your just created route. -```php +```PHP // /src/Storefront/Controller/ExampleController.php true` config option on the ro ### Register the Controller -```xml - - - - - - - - - - - - - - - - +```PHP +services(); + + $services->set(ExampleRoute::class) + ->args([service('swag_example.repository')]); + + $services->set(ExampleController::class) + ->args([service(ExampleRoute::class)]) + ->call('setContainer', [service('service_container')]); +}; ``` ### Register Storefront api-route -We need to tell Shopware that there is a new API-route for the `storefront` scope by extending the `routes.xml` to also include all Storefront controllers. +We need to tell Shopware that there is a new API-route for the `storefront` scope by extending the `routes.php` to also include all Storefront controllers. + +```PHP +// /src/Resources/config/routes.php +/src/Resources/config/routes.xml - - +use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; - - - +return static function (RoutingConfigurator $routes): void { + $routes->import('../../Core/**/*Route.php', 'attribute'); + $routes->import('../../Storefront/**/*Controller.php', 'attribute'); +}; ``` ### Requesting your route from the Storefront @@ -381,7 +381,7 @@ We expect that you have followed that guide and know how to register your custom When you want to request your custom route you can use the existing `http-client` service for that. -```javascript +```JAVASCRIPT // /src/Resources/app/storefront/src/example-plugin/example-plugin.plugin.js const { PluginBaseClass } = window; @@ -397,7 +397,7 @@ export default class ExamplePlugin extends PluginBaseClass { offset: 0, }), }); - + if (!response.ok) { throw new Error('Request failed'); } diff --git a/guides/plugins/plugins/framework/store-api/override-existing-route.md b/guides/plugins/plugins/framework/store-api/override-existing-route.md index f3fef7fa48..303160dbd1 100644 --- a/guides/plugins/plugins/framework/store-api/override-existing-route.md +++ b/guides/plugins/plugins/framework/store-api/override-existing-route.md @@ -21,7 +21,7 @@ Furthermore, you should have a look at our guide about [Adding a Store API route First, we have to create a new class which extends `AbstractExampleRoute`. In this example we will name it `ExampleRouteDecorator`. -```php +```PHP // /src/Core/Content/Example/SalesChannel/ExampleRouteDecorator.php /src/Resources/config/services.xml - +```PHP +// /src/Resources/config/services.php + +return static function (ContainerConfigurator $configurator): void { + $services = $configurator->services(); - - ... + // ... - - - - - - + $services->set(ExampleRouteDecorator::class) + ->decorate(ExampleRoute::class) + ->public() + ->args([ + service('swag_example.repository'), + service('.inner'), + ]); +}; ``` diff --git a/guides/plugins/plugins/framework/system-check/add-custom-check.md b/guides/plugins/plugins/framework/system-check/add-custom-check.md index 49ebeab2df..a6e5f7a192 100644 --- a/guides/plugins/plugins/framework/system-check/add-custom-check.md +++ b/guides/plugins/plugins/framework/system-check/add-custom-check.md @@ -17,7 +17,7 @@ First, you need to add a new `LocalDiskSpaceCheck` class that extends the `Shopw Each check contains a set of categorization methods that help to classify the check, and determine when and where it should be executed. -```php +```PHP class LocalDiskSpaceCheck extends BaseCheck { public function category(): Category @@ -42,7 +42,7 @@ class LocalDiskSpaceCheck extends BaseCheck The next step is to implement the actual check logic. We will check if the disk space is below a certain threshold and return the appropriate result. -```php +```PHP class LocalDiskSpaceCheck extends BaseCheck { public function __construct( @@ -86,13 +86,14 @@ class LocalDiskSpaceCheck extends BaseCheck Finally, you need to register the custom check as a service resource. -```xml - - %shopware.filesystem.public.type% - %shopware.filesystem.public.config.root% - %warning_threshold_in_mb% - - +```PHP +$services->set(YourNameSpace\LocalDiskSpaceCheck::class) + ->args([ + '%shopware.filesystem.public.type%', + '%shopware.filesystem.public.config.root%', + '%warning_threshold_in_mb%', + ]) + ->tag('shopware.system_check'); ``` ### Trigger the check diff --git a/guides/plugins/plugins/framework/system-check/index.md b/guides/plugins/plugins/framework/system-check/index.md index 5708525f3a..26dc9ad41b 100644 --- a/guides/plugins/plugins/framework/system-check/index.md +++ b/guides/plugins/plugins/framework/system-check/index.md @@ -37,7 +37,7 @@ The `SystemChecker` class makes sure the system is working correctly by running All the system checks in Shopware are tagged with `shopware.system_check`, so you can also fetch all the checks using the Symfony service locator. and run them in your custom flow. -```php +```PHP class CustomSystemChecker { public function __construct(private readonly iterable $checks) @@ -51,17 +51,16 @@ class CustomSystemChecker } ``` -```xml - - - +```PHP +$services->set(YourNamepace\CustomSystemChecker::class) + ->args([tagged_iterator('shopware.system_check')]); ``` ### Custom triggers For customized triggers, you can also inject the `Shopware\Core\Framework\SystemCheck\SystemChecker` service into your service and trigger the checks programmatically. -```php +```PHP $results = $systemChecker->check(SystemCheckExecutionContext::WEB); # or also use any custom logic you might have... $customChecker->check(); diff --git a/guides/plugins/plugins/plugin-fundamentals/add-custom-commands.md b/guides/plugins/plugins/plugin-fundamentals/add-custom-commands.md index 3679193170..162c862b63 100644 --- a/guides/plugins/plugins/plugin-fundamentals/add-custom-commands.md +++ b/guides/plugins/plugins/plugin-fundamentals/add-custom-commands.md @@ -9,17 +9,11 @@ nav: Shopware CLI commands are based on [Symfony Console](https://symfony.com/doc/current/console.html). This means that creating custom commands in Shopware plugins follows the standard Symfony approach. -To add a custom command in a Shopware plugin, you must register it as a service in your plugin’s `src/Resources/config/services.xml` and tag it with `console.command`: +To add a custom command in a Shopware plugin, you must register it as a service in your plugin's `src/Resources/config/services.php` and tag it with `console.command`: -```html - - - - - - - - +```PHP +$services->set(Swag\BasicExample\Command\ExampleCommand::class) + ->tag('console.command'); ``` Commands registered as services in a Shopware plugin are automatically available via `bin/console`. diff --git a/guides/plugins/plugins/plugin-fundamentals/add-custom-service.md b/guides/plugins/plugins/plugin-fundamentals/add-custom-service.md index b50af8a6b8..18d6e30608 100644 --- a/guides/plugins/plugins/plugin-fundamentals/add-custom-service.md +++ b/guides/plugins/plugins/plugin-fundamentals/add-custom-service.md @@ -18,21 +18,19 @@ Therefore, you can refer to the [Plugin Base Guide](../plugin-base-guide). ## Adding service -For adding a custom service, you need to provide a `services.xml` file in your plugin. -Place a file with name `services.xml` into a directory called `src/Resources/config/`. +For adding a custom service, you need to provide a `services.php` file in your plugin. +Place a file with name `services.php` into a directory called `src/Resources/config/`. ::: code-group -```xml [PLUGIN_ROOT/src/Resources/config/services.xml] - +```PHP [PLUGIN_ROOT/src/Resources/config/services.php] + +use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; - - - +return static function (ContainerConfigurator $configurator): void { + $services = $configurator->services(); +}; ``` ::: @@ -41,24 +39,26 @@ Now you have two possibilities to add a service to your plugin. ### Using autowire and autoconfigure -Set `autowire` and `autoconfigure` to `true` in your `services.xml` file. +Set `autowire` and `autoconfigure` to `true` in your `services.php` file. Symfony will then automatically register your service. Read more about it in the [Symfony docs](https://symfony.com/doc/current/service_container.html#creating-configuring-services-in-the-container). ::: code-group -```xml [PLUGIN_ROOT/src/Resources/config/services.xml] - +```PHP [PLUGIN_ROOT/src/Resources/config/services.php] + +return static function (ContainerConfigurator $configurator): void { + $services = $configurator->services() + ->defaults() + ->autowire() + ->autoconfigure(); - - - - - + $services->load('Swag\\BasicExample\\', '../../') + ->exclude('../../{Resources,Migration,*.php}'); +}; ``` ::: @@ -73,17 +73,17 @@ Use this option if you want to have more control over your service. ::: code-group -```xml [PLUGIN_ROOT/src/Resources/config/services.xml] - +```PHP [PLUGIN_ROOT/src/Resources/config/services.php] + +return static function (ContainerConfigurator $configurator): void { + $services = $configurator->services(); - - - - + $services->set(ExampleService::class); +}; ``` ::: @@ -115,10 +115,10 @@ By default, all services in Shopware 6 are marked as _private_. Read more about [private and public services](https://symfony.com/doc/current/service_container.html#public-versus-private-services). ::: -## Alternatives to XML +## Alternatives to PHP configuration -Symfony offers two other file formats to define your services: YAML and PHP. -In Shopware, it is also possible to use one of these. +Symfony supports two other file formats to define your services: YAML and XML. +However, starting with Symfony 7.4, XML service configuration has been deprecated, and it will no longer be supported in Symfony 8.0. Choose the one that suits you best. ## Next steps diff --git a/guides/plugins/plugins/plugin-fundamentals/add-scheduled-task.md b/guides/plugins/plugins/plugin-fundamentals/add-scheduled-task.md index c6b372c612..6e05553dfe 100644 --- a/guides/plugins/plugins/plugin-fundamentals/add-scheduled-task.md +++ b/guides/plugins/plugins/plugin-fundamentals/add-scheduled-task.md @@ -13,7 +13,7 @@ Quite often one might want to run any type of code on a regular basis, e.g. to c ## Prerequisites -This guide is built upon our [plugin base guide](../plugin-base-guide), but that one is not mandatory. Knowing how the `services.xml` file in a plugin works is also helpful, which will be taught in our guides about [Dependency Injection](dependency-injection) and [Creating a service](add-custom-service). It is shortly explained here as well though, so no worries! +This guide is built upon our [plugin base guide](../plugin-base-guide), but that one is not mandatory. Knowing how the `services.php` file in a plugin works is also helpful, which will be taught in our guides about [Dependency Injection](dependency-injection) and [Creating a service](add-custom-service). It is shortly explained here as well though, so no worries! ::: info Refer to this video on **[Adding scheduled tasks](https://www.youtube.com/watch?v=88S9P3x6wYE)**. Also available on our free online training ["Shopware 6 Backend Development"](https://academy.shopware.com/courses/shopware-6-backend-development-with-jisse-reitsma). @@ -21,38 +21,44 @@ Refer to this video on **[Adding scheduled tasks](https://www.youtube.com/watch? ## Registering scheduled task in the DI container -A `ScheduledTask` and its respective `ScheduledTaskHandler` are registered in a plugin's `services.xml`. For it to be found by Shopware 6 automatically, you need to place the `services.xml` file in a `Resources/config/` directory, relative to the location of your plugin's base class. The path could look like this: `/src/Resources/config/services.xml`. - -Here's an example `services.xml` containing a new `ScheduledTask` as well as a new `ScheduledTaskHandler`: - -```xml -// /src/Resources/config/services.xml - - - - - - - - - - - - - +A `ScheduledTask` and its respective `ScheduledTaskHandler` are registered in a plugin's `services.php`. For it to be found by Shopware 6 automatically, you need to place the `services.php` file in a `Resources/config/` directory, relative to the location of your plugin's base class. The path could look like this: `/src/Resources/config/services.php`. + +Here's an example `services.php` containing a new `ScheduledTask` as well as a new `ScheduledTaskHandler`: + +```PHP +// /src/Resources/config/services.php +services(); + + $services->set(ExampleTask::class) + ->tag('shopware.scheduled.task'); + + $services->set(ExampleTaskHandler::class) + ->args([ + service('scheduled_task.repository'), + service('logger'), + ]) + ->tag('messenger.message_handler'); +}; ``` Note the tags required for both the task and its respective handler, `shopware.scheduled.task` and `messenger.message_handler`. Your custom task will now be saved into the database once your plugin is activated. ## ScheduledTask and its handler -As you might have noticed, the `services.xml` file tries to find both the task itself as well as the new task handler in a directory called `Service/ScheduledTask`. This naming is up to you, Shopware 6 decided to use this name though. +As you might have noticed, the `services.php` file tries to find both the task itself and the new task handler in a directory called `Service/ScheduledTask`. This naming is up to you, Shopware 6 decided to use this name though. Here's the an example `ScheduledTask`: -```php +```PHP // /src/Service/ScheduledTask/ExampleTask.php /src/Service/ScheduledTask/ExampleTaskHandler.php /src/Resources/config/services.xml - +```PHP +// /src/Resources/config/services.php + +return static function (ContainerConfigurator $configurator): void { + $services = $configurator->services(); - - + $services->set(ExampleService::class); - - - - - + $services->set(ExampleServiceDecorator::class) + ->decorate(ExampleService::class) + ->args([service('.inner')]); +}; ``` Now we have to define an abstract class because it's more beautiful and not so strict like interfaces. With an abstract class we can add new functions easier, you can read more about this at the end of this article. The abstract class has to include an abstract function called `getDecorated()` which has the return type of our instance. @@ -51,7 +54,7 @@ To avoid misunderstandings: The abstract service class and the implementation of Therefore, this is how your abstract class could then look like: -```php +```PHP // /src/Service/AbstractExampleService.php /src/Service/ExampleService.php /src/Service/ExampleServiceDecorator.php /src/Service/AbstractExampleService.php /src/Service/ExampleService.php +```PHP [PLUGIN_ROOT/src/Resources/config/services.php] + +return static function (ContainerConfigurator $configurator): void { + $services = $configurator->services(); - - - - - - + $services->set(ExampleService::class) + ->args([service(SystemConfigService::class)]); +}; ``` ::: diff --git a/guides/plugins/plugins/plugin-fundamentals/listening-to-events.md b/guides/plugins/plugins/plugin-fundamentals/listening-to-events.md index f97e196fd5..b2a4ab8b24 100644 --- a/guides/plugins/plugins/plugin-fundamentals/listening-to-events.md +++ b/guides/plugins/plugins/plugin-fundamentals/listening-to-events.md @@ -24,8 +24,8 @@ Also available on our free online training ["Shopware 6 Backend Development"](ht ### Plugin base class -Registering a custom subscriber requires to load a `services.xml` file with your plugin. -This is done by either placing a file with name `services.xml` into a directory called `src/Resources/config/`. +Registering a custom subscriber requires loading a `services.php` file with your plugin. +This is done by either placing a file with name `services.php` into a directory called `src/Resources/config/`. Basically, that's it already if you're familiar with [Symfony subscribers](https://symfony.com/doc/current/event_dispatcher.html#creating-an-event-subscriber). Don't worry, we got you covered here as well. @@ -37,7 +37,7 @@ As mentioned above, such a subscriber for Shopware 6 looks exactly the same as i Therefore, this is how your subscriber could then look like: -```php +```PHP // /src/Subscriber/MySubscriber.php /src/Subscriber/MySubscriber.php /src/Resources/config/services.xml - +```PHP +// /src/Resources/config/services.php + +return static function (ContainerConfigurator $configurator): void { + $services = $configurator->services(); - - - - - - + $services->set(MySubscriber::class) + ->tag('kernel.event_subscriber'); +}; ``` That's it, your subscriber service is now automatically loaded at runtime, and it should start listening to the mentioned events to be dispatched. diff --git a/guides/plugins/plugins/plugin-fundamentals/use-plugin-configuration.md b/guides/plugins/plugins/plugin-fundamentals/use-plugin-configuration.md index 75620cdfbb..dcbda4828a 100644 --- a/guides/plugins/plugins/plugin-fundamentals/use-plugin-configuration.md +++ b/guides/plugins/plugins/plugin-fundamentals/use-plugin-configuration.md @@ -17,7 +17,7 @@ In order to add a plugin configuration, you sure need to provide your plugin fir The plugin in this example already knows a subscriber, which listens to the `product.loaded` event and therefore will be called every time a product is loaded. -```php +```PHP // /src/Subscriber/MySubscriber.php /src/Resources/config/config.xml /src/Resources/config/services.xml - - - - - - - - - - - +```PHP +// /src/Resources/config/services.php +services(); + + $services->set(MySubscriber::class) + ->args([service(SystemConfigService::class)]) + ->tag('kernel.event_subscriber'); +}; ``` Note the new `argument` being provided to your subscriber. Now create a new field in your subscriber and pass in the `SystemConfigService`: -```php +```PHP // /src/Subscriber/MySubscriber.php .config.`. Thus, it would be `SwagBasicExample.config.example` here. -```php +```PHP // /src/Subscriber/MySubscriber.php window.pluginConfig = { @@ -226,7 +228,7 @@ For Storefront JavaScript plugins, you can pass configuration values from Twig t ``` -```javascript +```JAVASCRIPT // In your Storefront JavaScript plugin const { PluginBaseClass } = window; @@ -234,7 +236,7 @@ export default class ExamplePlugin extends PluginBaseClass { init() { // Access the configuration value passed from Twig const exampleConfig = window.pluginConfig?.example; - + if (exampleConfig) { console.log('Plugin configuration:', exampleConfig); // Use the configuration value in your plugin logic diff --git a/guides/plugins/plugins/redis.md b/guides/plugins/plugins/redis.md index 934a23e638..8a9abe400d 100644 --- a/guides/plugins/plugins/redis.md +++ b/guides/plugins/plugins/redis.md @@ -15,16 +15,17 @@ Once you've set up your Redis connections as explained in the [Redis configurat 1. Inject `Shopware\Core\Framework\Adapter\Redis\RedisConnectionProvider` and retrieve connections by name: - ```xml - - - %myservice.redis_connection_name% - + ```PHP + $services->set(MyCustomService::class) + ->args([ + service(Shopware\Core\Framework\Adapter\Redis\RedisConnectionProvider::class), + '%myservice.redis_connection_name%', + ]); ``` ```php class MyCustomService - { + { public function __construct ( private RedisConnectionProvider $redisConnectionProvider, string $connectionName, @@ -42,20 +43,18 @@ Once you've set up your Redis connections as explained in the [Redis configurat 2. Use `Shopware\Core\Framework\Adapter\Redis\RedisConnectionProvider` as factory to define custom services: - ```xml - - - %myservice.redis_connection_name% - + ```PHP + $services->set('my.custom.redis_connection', \Redis::class) + ->factory([service(Shopware\Core\Framework\Adapter\Redis\RedisConnectionProvider::class), 'getConnection']) + ->args(['%myservice.redis_connection_name%']); - - - + $services->set(MyCustomService::class) + ->args([service('my.custom.redis_connection')]); ``` ```php class MyCustomService - { + { public function __construct ( private object $redisConnection, ) { } @@ -71,10 +70,9 @@ Once you've set up your Redis connections as explained in the [Redis configurat 3. Inject connection directly by name: - ```xml - - - + ```PHP + $services->set(MyCustomService::class) + ->args([service('shopware.redis.connection.connection_name')]); ``` Be cautious with this approach! If you change the Redis connection names in your configuration, it will cause container build errors. diff --git a/guides/plugins/plugins/storefront/add-cookie-to-manager.md b/guides/plugins/plugins/storefront/add-cookie-to-manager.md index 08b1dea29f..c84e523708 100644 --- a/guides/plugins/plugins/storefront/add-cookie-to-manager.md +++ b/guides/plugins/plugins/storefront/add-cookie-to-manager.md @@ -29,22 +29,21 @@ It is recommended to use an event listener if you're listening to a single event ### Registering your event listener -Start with creating the `services.xml` and registering your event listener. - -```xml -// /src/Resources/config/services.xml - - - - - - - - - - +Start with creating the `services.php` and registering your event listener. + +```PHP +// /src/Resources/config/services.php +services(); + + $services->set(CookieListener::class) + ->tag('kernel.event_listener', ['event' => 'Shopware\Core\Content\Cookie\Event\CookieGroupCollectEvent']); +}; ``` In the next step we'll create the actual listener class. @@ -61,7 +60,7 @@ Since Shopware 6.7.3.0, cookies use structured objects (`CookieEntry` and `Cooki Let's have a look at an example: -```php +```PHP // /src/Listener/CookieListener.php - - - - - - - - - - - +```PHP [PLUGIN_ROOT/src/Resources/config/services.php] +services(); + + $services->set(ExampleController::class) + ->public() + ->call('setContainer', [service('service_container')]); +}; ``` ::: -Please also note the `call` tag, which is necessary in order to set the DI container to the controller. +Please also note the `call` method, which is necessary in order to set the DI container to the controller. -### Routes.xml example +### Routes.php example Once we've registered our new controller, we have to tell Shopware how we want it to search for new routes in our plugin. -This is done with a `routes.xml` file at `/src/Resources/config/` location. -Have a look at the official [Symfony documentation](https://symfony.com/doc/current/routing.html) about routes and how they are registered. +This is done with a `routes.php` file at `/src/Resources/config/` location. +Take a look at the official [Symfony documentation](https://symfony.com/doc/current/routing.html) about routes and how they are registered. ::: code-group -```xml [PLUGIN_ROOT/src/Resources/config/routes.xml] - - +```PHP [PLUGIN_ROOT/src/Resources/config/routes.php] + - +return static function (RoutingConfigurator $routes): void { + $routes->import('../../Storefront/Controller/*Controller.php', 'attribute'); +}; ``` ::: @@ -228,7 +227,7 @@ use Symfony\Component\Routing\Attribute\Route; #[Route(defaults: [PlatformRequest::ATTRIBUTE_ROUTE_SCOPE => [StorefrontRouteScope::ID]])] class ExampleController extends StorefrontController -{ +{ #[Route(path: '/example', name: 'frontend.example.example', methods: ['GET'])] public function showExample(Request $request, SalesChannelContext $context): Response { @@ -305,7 +304,7 @@ class SwagBasicExample extends Plugin Now you can use the route name `swag.test.foo-bar` in your controller without the need for a prefix. -```php +```PHP #[Route(path: '/example', name: 'swag.test.foo-bar', methods: ['GET'])] public function showExample(Request $request, SalesChannelContext $context): Response { diff --git a/guides/plugins/plugins/storefront/add-custom-page.md b/guides/plugins/plugins/storefront/add-custom-page.md index 2c74792bb5..f4ad97f9ee 100644 --- a/guides/plugins/plugins/storefront/add-custom-page.md +++ b/guides/plugins/plugins/storefront/add-custom-page.md @@ -148,11 +148,13 @@ Remember to register your new page loader in the DI container: ::: code-group -```xml [PLUGIN_ROOT/src/Resources/config/services.xml] - - - - +```PHP [PLUGIN_ROOT/src/Resources/config/services.php] +$services->set(ExamplePageLoader::class) + ->public() + ->args([ + service('Shopware\Storefront\Page\GenericPageLoader'), + service('event_dispatcher'), + ]); ``` ::: @@ -197,19 +199,29 @@ class ExampleController extends StorefrontController Note, that we've added the page to the template variables. -#### Adjusting the services.xml +#### Adjusting the services.php -In addition, it is necessary to pass the argument with the ID of the `ExamplePageLoader` class to the [configuration](add-custom-controller#services-xml-example) of the controller service in the `services.xml`. +In addition, it is necessary to pass the argument with the ID of the `ExamplePageLoader` class to the [configuration](add-custom-controller#services-xml-example) of the controller service in the `services.php`. ::: code-group -```xml [PLUGIN_ROOT/src/Resources/config/services.xml] - - - - - - +```PHP [PLUGIN_ROOT/src/Resources/config/services.php] +services(); + + $services->set(ExampleController::class) + ->public() + ->args([service(ExamplePageLoader::class)]) + ->call('setContainer', [service('service_container')]); +}; ``` ::: diff --git a/guides/plugins/plugins/storefront/add-custom-twig-function.md b/guides/plugins/plugins/storefront/add-custom-twig-function.md index 1875552df1..255b771bf3 100644 --- a/guides/plugins/plugins/storefront/add-custom-twig-function.md +++ b/guides/plugins/plugins/storefront/add-custom-twig-function.md @@ -65,16 +65,14 @@ class SwagCreateMd5Hash extends AbstractExtension ::: -Of course, you can do everything in the `createMd5Hash` function that PHP can do, but the `service.xml` handles registration of the service in the DI container. +Of course, you can do everything in the `createMd5Hash` function that PHP can do, but the `services.php` handles registration of the service in the DI container. ::: code-group -```xml [PLUGIN_ROOT/src/Resources/config/services.xml] - - - - - +```PHP [PLUGIN_ROOT/src/Resources/config/services.php] +$services->set(SwagCreateMd5Hash::class) + ->public() + ->tag('twig.extension'); // Required ``` Once done, you can access this `TwigFunction` within your plugin. diff --git a/guides/plugins/plugins/storefront/add-data-to-storefront-page.md b/guides/plugins/plugins/storefront/add-data-to-storefront-page.md index f5dc9011ae..942b1f1589 100644 --- a/guides/plugins/plugins/storefront/add-data-to-storefront-page.md +++ b/guides/plugins/plugins/storefront/add-data-to-storefront-page.md @@ -43,7 +43,7 @@ In this example, we're going to extend the [FooterPagelet](https://github.com/sh All pages or pagelets throw `Loaded` events and this is the right event to subscribe to if you want to add data to the page or pagelet. In our case we want to add data to the `FooterPagelet` so we need to subscribe to the `FooterPageletLoadedEvent`. -```php +```PHP // SwagBasicExample/src/Service/AddDataToPage.php - - - +```PHP +// Resources/config/services.php +$services->set(\Swag\BasicExample\Service\AddDataToPage::class) + ->tag('kernel.event_subscriber'); ``` ### Adding data to the page @@ -95,7 +93,7 @@ First you should read our guide for [adding store-api routes](../framework/store Our new Route should look like this: -```php +```PHP productRepository ->aggregate($criteria, $context->getContext()) ->get('productCount'); - + return new ProductCountRouteResponse($productCountResult); } } @@ -170,38 +168,40 @@ class ProductCountRoute extends AbstractProductCountRoute ### Register route class -```xml - - - - - - - - - +```PHP +services(); + + $services->set(ProductCountRoute::class) + ->args([service('product.repository')]); +}; ``` -The routes.xml according to our guide for [adding store-api routes](../framework/store-api/add-store-api-route) should look like this. +The routes.php according to our guide for [adding store-api routes](../framework/store-api/add-store-api-route) should look like this. + +```PHP +// /src/Resources/config/routes.php + - +use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; - - +return static function (RoutingConfigurator $routes): void { + $routes->import('../../Core/**/*Route.php', 'attribute'); +}; ``` ### ProductCountRouteResponse The RouteResponse according to our guide for [adding store-api routes](../framework/store-api/add-store-api-route) should look like this -```php +```PHP - - - - - - - - - - - - - +```PHP +services(); + + $services->set(ProductCountRoute::class) + ->public() + ->args([service('product.repository')]); + + $services->set(AddDataToPage::class) + ->args([service(ProductCountRoute::class)]) + ->tag('kernel.event_subscriber'); +}; ``` ### Displaying the data in the Storefront diff --git a/guides/plugins/plugins/storefront/add-dynamic-content-via-ajax-calls.md b/guides/plugins/plugins/storefront/add-dynamic-content-via-ajax-calls.md index 6a85b7eb90..ccf2259b3b 100644 --- a/guides/plugins/plugins/storefront/add-dynamic-content-via-ajax-calls.md +++ b/guides/plugins/plugins/storefront/add-dynamic-content-via-ajax-calls.md @@ -54,36 +54,35 @@ As you might have seen, this controller isn't too different from the controller The route attribute has an added `defaults: ['XmlHttpRequest' => true]` to allow XmlHttpRequest, and it returns a `JsonResponse` instead of a normal `Response`. Using a `JsonResponse` instead of a normal `Response` causes the data structures passed to it to be automatically turned into a `JSON` string. -The following `services.xml` and `routes.xml` are identical as in the before mentioned article, but here they are for reference anyway: +The following `services.php` and `routes.php` are identical as in the before mentioned article, but here they are for reference anyway: ::: code-group -```xml [PLUGIN_ROOT/src/Resources/config/services.xml] - - - - - - - - - - - - +```PHP [PLUGIN_ROOT/src/Resources/config/services.php] +services(); + + $services->set(ExampleController::class) + ->public() + ->call('setContainer', [service('service_container')]); +}; ``` -```xml [PLUGIN_ROOT/src/Resources/config/routes.xml] - - +```PHP [PLUGIN_ROOT/src/Resources/config/routes.php] + - +return static function (RoutingConfigurator $routes): void { + $routes->import('../../Storefront/Controller/**/*Controller.php', 'attribute'); +}; ``` ::: diff --git a/guides/plugins/plugins/storefront/add-scss-variables-via-subscriber.md b/guides/plugins/plugins/storefront/add-scss-variables-via-subscriber.md index 541157e21c..9a00dbe45d 100644 --- a/guides/plugins/plugins/storefront/add-scss-variables-via-subscriber.md +++ b/guides/plugins/plugins/storefront/add-scss-variables-via-subscriber.md @@ -44,7 +44,7 @@ You can add a new subscriber according to the [Listening to events](../plugin-fu -```php +```PHP - + -```xml - +```PHP + +return static function (ContainerConfigurator $configurator): void { + $services = $configurator->services(); - - - - - - + $services->set(ThemeVariableSubscriber::class) + ->tag('kernel.event_subscriber'); +}; ``` @@ -107,7 +106,7 @@ Inside your `ThemeVariableSubscriber` you can also read values from the plugin c First, lets add a new plugin configuration field according to the [Plugin Configurations](../plugin-fundamentals/add-plugin-configuration): -```xml +```XML // /src/Resources/config/config.xml /src/Resources/config/config.xml -```php +```PHP - + + +```PHP + +use function Symfony\Component\DependencyInjection\Loader\Configurator\service; - +return static function (ContainerConfigurator $configurator): void { + $services = $configurator->services(); - - - - - - - - + $services->set(ThemeVariableSubscriber::class) + // add argument SystemConfigService + ->args([service(SystemConfigService::class)]) + ->tag('kernel.event_subscriber'); +}; ``` @@ -218,7 +219,7 @@ class ThemeVariableSubscriber implements EventSubscriberInterface Adding config fields via `$event->addVariable()` for every field individually may be a bit cumbersome in some cases. You could also loop over all config fields and call `addVariable()` for each one. However, this depends on your use case. -```php +```PHP // /src/Subscriber/ThemeVariableSubscriber.php ..json` as the naming pattern for the file. The domain can be freely defined (we recommend your extension name in kebab case), while the locale **must** map to the ISO string of the supported locale in this snippet file — for example: `my-app.de.json`. -Locales should follow the ISO string of the supported language, such as `de`, `en`, or `es-AR`. +Locales should follow the ISO string of the supported language, such as `de`, `en`, or `es-AR`. This format follows [IETF BCP 47](https://datatracker.ietf.org/doc/html/bcp47), restricted to [ISO 639-1 (2-letter) language codes](https://en.wikipedia.org/wiki/ISO_639-1) as used by [Symfony](https://symfony.com/doc/current/reference/constraints/Locale.html), but with dashes (`-`) instead of underscores (`_`). For more information on selecting proper locales, see our documentation on [Fallback language selection](../../../../concepts/translations/fallback-language-selection.md). @@ -98,13 +98,13 @@ Or use injection via [DI container](#using-translation-generally-in-php). Translation without placeholders: -```php +```PHP $this->trans('header.example'); ``` Translation with placeholders: -```php +```PHP $this->trans('soldProducts', ['%count%' => 3, '%country%' => 'Germany']); ``` @@ -114,13 +114,13 @@ If we need to use a snippet elsewhere in PHP, we can use [Dependency Injection](../plugin-fundamentals/dependency-injection) to inject the `translator` service, which implements Symfony's `Symfony\Contracts\Translation\TranslatorInterface`: -```xml - - - +```PHP +$services->set(Swag\Example\Service\SwagService::class) + ->public() + ->args([service('translator')]); ``` -```php +```PHP private TranslatorInterface $translator; public function __construct(TranslatorInterface $translator) @@ -131,6 +131,6 @@ public function __construct(TranslatorInterface $translator) Then, call the `trans` method, which has the same parameters as the method from controllers. -```php +```PHP $this->translator->trans('soldProducts', ['%count%' => 3, '%country%' => 'Germany']); ``` diff --git a/products/cli/extension-commands/build.md b/products/cli/extension-commands/build.md index 457f2eb30f..354d60b0d5 100644 --- a/products/cli/extension-commands/build.md +++ b/products/cli/extension-commands/build.md @@ -150,7 +150,7 @@ build: - ``` -For example, to exclude the `src/Resources/config/services.xml` file from checksum calculation: +For example, to exclude the `src/Resources/config/services.php` file from checksum calculation: ```yaml # .shopware-extension.yml @@ -158,7 +158,7 @@ build: zip: checksum: ignore: - - src/Resources/config/services.xml + - src/Resources/config/services.php ``` To verify the checksum of installed extensions, you can use the [FroshTools](https://github.com/FriendsOfShopware/FroshTools#froshextensionchecksumcheck---check-extension-file-integrity) plugin which provides a checksum verification feature for all extensions. diff --git a/products/extensions/advanced-search/How-to-add-more-fields-to-product-search.md b/products/extensions/advanced-search/How-to-add-more-fields-to-product-search.md index a6101e52e9..f74611a939 100644 --- a/products/extensions/advanced-search/How-to-add-more-fields-to-product-search.md +++ b/products/extensions/advanced-search/How-to-add-more-fields-to-product-search.md @@ -13,14 +13,16 @@ In this example, we create a field called `productNumberPrefix` to make it searc **1. Decorate the ElasticsearchDefinition** -```xml - - - - +```PHP +$services->set(YourPluginNameSpace\ElasticsearchProductDefinitionDecorator::class) + ->decorate(Shopware\Elasticsearch\Product\ElasticsearchProductDefinition::class) + ->args([ + service('.inner'), + service(Shopware\Commercial\AdvancedSearch\Domain\Search\SearchLogic::class), + ]); ``` -```php +```PHP - - - - - - - +```PHP +// YourPluginNameSpace should be changed to your respectively ElasticsearchDefinition and Definition classes +use function Symfony\Component\DependencyInjection\Loader\Configurator\service; + +$services->set(YourPluginNameSpace\YourCustomElasticsearchDefinition::class) + ->args([ + service(YourPluginNameSpace\YourCustomDefinition::class), + service('Doctrine\DBAL\Connection'), + service(Shopware\Commercial\AdvancedSearch\Domain\Search\SearchLogic::class), + ]) + ->tag('shopware.es.definition') + ->tag('advanced_search.supported_definition'); ``` diff --git a/products/extensions/advanced-search/How-to-modify-search-logic.md b/products/extensions/advanced-search/How-to-modify-search-logic.md index 366eab766c..deac89a606 100644 --- a/products/extensions/advanced-search/How-to-modify-search-logic.md +++ b/products/extensions/advanced-search/How-to-modify-search-logic.md @@ -19,14 +19,16 @@ This class is the central place to build the Elasticsearch query: To modify the search logic, you can decorate the search logic class and add your own logic into it: -```xml - - - - +```PHP +$services->set(YourPluginNameSpace\Domain\Search\SearchLogicDecorator::class) + ->decorate(Shopware\Commercial\AdvancedSearch\Domain\Search\SearchLogic::class) + ->args([ + service('.inner'), + service(Shopware\Commercial\AdvancedSearch\Domain\Configuration\ConfigurationLoader::class), + ]); ``` -```php +```PHP - - +```PHP +$services->set(YourPluginNameSpace\CartAmountRule::class) + ->public() + ->tag('shopware.approval_rule.definition'); ``` ## App diff --git a/products/extensions/b2b-suite-migration/development/adding-component.md b/products/extensions/b2b-suite-migration/development/adding-component.md index 7d437c144e..1271a7affe 100644 --- a/products/extensions/b2b-suite-migration/development/adding-component.md +++ b/products/extensions/b2b-suite-migration/development/adding-component.md @@ -17,10 +17,9 @@ This section guides developers on adding a new component to the migration proces - Tag the class with `b2b.migration.configurator`. **Example**: - ```XML - - - + ```PHP + $services->set(Shopware\Commercial\B2B\B2BSuiteMigration\Components\EmployeeManagement\EmployeeManagementMigrationConfigurator::class) + ->tag('b2b.migration.configurator', ['priority' => 9000]); ``` ```PHP diff --git a/products/extensions/b2b-suite-migration/development/extending-migration.md b/products/extensions/b2b-suite-migration/development/extending-migration.md index 772974518b..00071d1284 100644 --- a/products/extensions/b2b-suite-migration/development/extending-migration.md +++ b/products/extensions/b2b-suite-migration/development/extending-migration.md @@ -18,10 +18,9 @@ This section explains how to add new fields to an existing entity or introduce n **Example**: - ```XML - - - + ```PHP + $services->set(MigrationExtension\B2BMigration\B2BExtensionMigrationConfigurator::class) + ->tag('b2b.migration.configurator.extension'); ``` ```PHP diff --git a/products/extensions/b2b-suite-migration/development/handler.md b/products/extensions/b2b-suite-migration/development/handler.md index a4ae6c15f3..c4e735fc4c 100644 --- a/products/extensions/b2b-suite-migration/development/handler.md +++ b/products/extensions/b2b-suite-migration/development/handler.md @@ -53,7 +53,7 @@ Handlers allow custom logic to transform data before mapping it to the target fi currency_factor auth_id.b2b_store_front_auth.customer_id.customer.sales_channel_id.sales_channel.language_id - + state_id expiration_date @@ -76,15 +76,14 @@ Handlers allow custom logic to transform data before mapping it to the target fi To use a handler, implement a PHP class (e.g., `RolePermissionsTransformer`) that extends `Shopware\Commercial\B2B\B2BSuiteMigration\Core\Domain\DataTransformer\AbstractFieldTransformer` and tag it with `b2b.migration.transformer` in your service configuration. -```XML - - - - - +```PHP +$services->set(Shopware\Commercial\B2B\B2BSuiteMigration\Components\QuoteManagement\DataTransformer\QuoteComment\StateTransformer::class) + ->lazy() + ->args([service(Shopware\Core\Framework\Extensions\ExtensionDispatcher::class)]) + ->tag('b2b.migration.transformer'); ``` -The best practice is to mark this service as `lazy="true"` to improve performance by loading it only when needed. +The best practice is to mark this service as `lazy` to improve performance by loading it only when needed. ## Handler Implementation @@ -134,7 +133,7 @@ protected function requiredSourceFields(): array ```XML ``` - + ```PHP protected function requiredSourceFields(): array { @@ -148,11 +147,11 @@ protected function requiredSourceFields(): array foo bar - + permissions ``` - + ```PHP protected function requiredSourceFields(): array { @@ -180,7 +179,7 @@ protected function _transform( - **Return Value**: - For a single target field: Return a single value (e.g., string, integer, or JSON-encoded string). - For multiple target fields: Return an associative array where keys are target field names and values are the corresponding transformed values. - + #### Examples **_Example 1: Single Source to Single Target_** @@ -226,7 +225,7 @@ Transform multiple source fields to multiple target fields, such as generating a list_id mode - + order_id order_version_id @@ -241,7 +240,7 @@ public function transform(Field $field, array $sourceRecord): mixed if (!isset($field->getSourceElements()['list_id']) || !isset($field->getSourceElements()['mode'])) { // Handle missing required source fields } - + $listId = $sourceRecord['list_id']; $mode = $sourceRecord['mode']; // Perform transformation logic based on listId and mode diff --git a/products/extensions/b2b-suite/guides/core/authentication.md b/products/extensions/b2b-suite/guides/core/authentication.md index c82803a9bc..3320e81209 100644 --- a/products/extensions/b2b-suite/guides/core/authentication.md +++ b/products/extensions/b2b-suite/guides/core/authentication.md @@ -54,7 +54,7 @@ CREATE TABLE IF NOT EXISTS `b2b_my` ( This modifier column allows you to store the context owner independent of the actual source table of the context owner. You can access the current context owner always through the identity. -```php +```PHP [...] /** @var AuthenticationService $authenticationService */ @@ -75,7 +75,7 @@ echo 'The context owner id ' . $ownershipContext->contextOwnerId . '\n'; You can even load the whole identity through the `AuthenticationService`. -```php +```PHP [...] $ownerIdentity = $authenticationService->getIdentityByAuthId($contextOwnerId); @@ -87,7 +87,7 @@ $ownerIdentity = $authenticationService->getIdentityByAuthId($contextOwnerId); Sometimes you want to flag records to be owned by certain identities. -```sql +```SQL CREATE TABLE IF NOT EXISTS `b2b_my` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `auth_id` INT(11) NULL DEFAULT NULL, @@ -103,7 +103,7 @@ CREATE TABLE IF NOT EXISTS `b2b_my` ( To fill this column, we again access the current identity, but instead of the `contextOwnerId`, we access the `authId`. -```php +```PHP [...] $ownershipContext = $authenticationService @@ -119,7 +119,7 @@ The B2B Suite views the context owner as some kind of admin that, from the persp Therefore, commonly used queries are: -```php +```PHP /** @var Connection $connection */ $connection = $this->container->get('dbal_connection'); /** @var Identity $identity */ @@ -174,11 +174,11 @@ Example implementations are either: `Shopware\B2B\Debtor\Framework\DebtorIdentit In the *CredentialsBuilder*, you create the *CredentialsEntity*, which is used for logging in the B2B Suite. -```php +```PHP public function createCredentials(array $parameters): AbstractCredentialsEntity { $entity = new CredentialsEntity(); - + $entity->email = $parameters['email']; $entity->salesChannelId = IdValue::create($this->contextProvider->getSalesChannelContext()->getSalesChannel()->getId()); $entity->customerScope = $this->systemConfigService->get('core.systemWideLoginRegistration.isCustomerBoundToSalesChannel'); @@ -196,23 +196,23 @@ Next, you have to provide the means to register your identity on login. This is The *LoginContextService* is passed as an argument to help you retrieve and create the appropriate auth and context owner ids. Notice that the interface is designed to be chained to create dependent auth ids on the fly. -```php +```PHP [...] public function fetchIdentityByCredentials(CredentialsEntity $credentialsEntity, LoginContextService $contextService, bool $isApi = false): Identity { if (!$credentialsEntity->email) { throw new NotFoundException('Unable to handle context'); } - + $entity = $this->yourEntityRepository->fetchOneByEmail($email); /** @var DebtorIdentity $debtorIdentity */ $debtorIdentity = $this->debtorRepository->fetchIdentityById($entity->debtor->id, $contextService); - + $authId = $contextService->getAuthId(YourEntityRepository::class, $entity->id, $debtorIdentity->getAuthId()); - + $this->yourEntityRepository->setAuthId($entity->id, $authId); - + return new YourEntityIdentity($authId, (int) $entity->id, YourEntityRepository::TABLE_NAME, $entity, $debtorIdentity); } [...] @@ -220,12 +220,10 @@ context owner ids. Notice that the interface is designed to be chained to create Finally, you register your authentication provider (in our case a repository) as a tagged service through the DIC. -```xml - - [...] - - - +```PHP +$services->set('b2b_my.contact_authentication_identity_loader', Shopware\B2B\My\AuthenticationIdentityLoader::class) + // [...] + ->tag('b2b_front_auth.authentication_repository'); ``` ## Sales representative diff --git a/products/extensions/b2b-suite/guides/core/entity-acl.md b/products/extensions/b2b-suite/guides/core/entity-acl.md index 5f2bbad1e4..a8690764e6 100644 --- a/products/extensions/b2b-suite/guides/core/entity-acl.md +++ b/products/extensions/b2b-suite/guides/core/entity-acl.md @@ -40,10 +40,10 @@ The ACL is represented as M:N relation tables in the database and always looks l ```sql CREATE TABLE `b2b_acl_*` ( `id` INT(11) NOT NULL AUTO_INCREMENT, - `entity_id` INT(11) NOT NULL, + `entity_id` INT(11) NOT NULL, `referenced_entity_id` INT(11) NOT NULL, `grantable` TINYINT(4) NOT NULL DEFAULT '0', - + [...] ); ``` @@ -73,7 +73,7 @@ The address ACL repository can be retrieved through the DIC by the `b2b_address. The repository then provides the following methods. If you are already familiar with other ACL implementations, most methods will look quite familiar. -```php +```PHP container->get('b2b_address.acl_repository'); $contactRepository = $this->container->get('b2b_contact.repository'); $contact = $contactRepository->fetchOneById(1); $aclAddressRepository->allow( - $contact, // the contact + $contact, // the contact 22, // the id of the address true // whether the contact may grant access to other contacts ); @@ -209,18 +209,18 @@ $aclAddressRepository->allow( We can then deny the access just by this: -```php +```PHP $aclAdressRepository->deny( - $contact, // the contact + $contact, // the contact 22, // the id of the address ); ``` or just set it not grantable, by -```php +```PHP $aclAdressRepository->allow( - $contact, // the contact + $contact, // the contact 22, // the id of the address false // whether the contact may grant access to other contacts ); @@ -230,18 +230,18 @@ $aclAdressRepository->allow( If you want to know whether a certain contact can access an entity, you can call `isAllowed`. -```php +```PHP $aclAdressRepository->isAllowed( - $contact, // the contact + $contact, // the contact 22, // the id of the address ); ``` Or you just want to check whether an entity can be granted by a contact. -```php +```PHP $aclAdressRepository->isGrantable( - $contact, // the contact + $contact, // the contact 22, // the id of the address ); ``` @@ -250,7 +250,7 @@ One of the more complex problems you might face is that you want to filter a que This can be achieved by this snippet: -```php +```PHP address` relation. Now we need to tell the B2B Suite to create the necessary tables. In Shopware, this must be done during the plugin installation process. Because the container is not yet set up with the B2B Suite services, we use a static factory method in the following code: -```php +```PHP use Shopware\B2B\Acl\Framework\AclDdlService; use Shopware\B2B\Address\Framework\AddressContactTable; @@ -343,19 +343,17 @@ AclDdlService::create()->createTable(new AddressContactTable()); Now the table exists, but we must still make the table definition accessible through the DIC, so the ACL component can set up appropriate repositories. This is achieved through a tag in the service definition: -```xml - - - +```PHP +$services->set('b2b_address.contact_acl_table', Shopware\B2B\Address\Framework\AddressContactAclTable::class) + ->tag('b2b_acl.table'); ``` -Finally, we need to register the service in the DIC. This is done by this XML snippet: +Finally, we need to register the service in the DIC. This is done by this PHP snippet: -```xml - - - s_user_addresses - +```PHP +$services->set('b2b_address.acl_repository', Shopware\B2B\Acl\Framework\AclRepository::class) + ->factory([service('b2b_acl.repository_factory'), 'createRepository']) + ->args(['s_user_addresses']); ``` There we are; the addresses are ACL-ified entities. @@ -367,7 +365,7 @@ other contexts than *contact* and *role*. For this, you have to create a differe An `AclContextResolver` is responsible for extracting the primary key out of a given context object and produces a query that joins the main ACL table. This is done by implementing `getQuery`, `isMainContext`, and `extractId`. -```php +```PHP getNextPrefix(); @@ -416,7 +414,7 @@ Notice the `getMainPrefix` call. This allows the ACL component to be joined with An implementation of extract id usually looks like this: -```php +```PHP public function extractId($context): int { if ($context instanceof ContactIdentity) { diff --git a/products/extensions/b2b-suite/guides/core/overload-classes.md b/products/extensions/b2b-suite/guides/core/overload-classes.md index 4a8106ccec..4f784e51ce 100644 --- a/products/extensions/b2b-suite/guides/core/overload-classes.md +++ b/products/extensions/b2b-suite/guides/core/overload-classes.md @@ -13,67 +13,84 @@ nav: To add new functionality or overload existing classes to change functionality, the B2B Suite uses the [Dependency Injection](../../../../../guides/plugins/plugins/plugin-fundamentals/dependency-injection) as an extension system instead of events and hooks, which Shopware uses. -### How does a services.xml look like - -In the release package, our service.xml looks like this: - -```xml - - - - Shopware\B2B\Role\Framework\RoleRepository - [...] - - - - - - - [...] - - - [...] - - +### How does a services.php look like + +In the release package, our services.php looks like this: + +```PHP +// /src/Resources/config/services.php +services(); + $parameters = $configurator->parameters(); + + $parameters->set('b2b_role.repository_class', RoleRepository::class); + + $services->set('b2b_role.repository_abstract') + ->abstract(true) + ->args([ + service('dbal_connection'), + service('b2b_common.repository_dbal_helper'), + ]); + // [...] + + $services->set('b2b_role.repository', '%b2b_role.repository_class%') + ->parent('b2b_role.repository_abstract'); + // [...] +}; ``` For development (GitHub), it looks like this: -```xml - - - - - - - - - - - - - - - - - - - - - - - - - - - +```PHP +// /src/Resources/config/services.php +services(); + + $services->set('b2b_role.repository', RoleRepository::class) + ->args([ + service('dbal_connection'), + service('b2b_common.repository_dbal_helper'), + ]); + + $services->set('b2b_role.grid_helper', GridHelper::class) + ->args([service('b2b_role.repository')]); + + $services->set('b2b_role.crud_service', RoleCrudService::class) + ->args([ + service('b2b_role.repository'), + service('b2b_role.validation_service'), + ]); + + $services->set('b2b_role.validation_service', RoleValidationService::class) + ->args([ + service('b2b_common.validation_builder'), + service('validator'), + ]); + + $services->set('b2b_role.acl_route_table', AclRouteAclTable::class) + ->tag('b2b_acl.table'); +}; ``` -We generate the new services.xml files for our package automatically. +We generate the new services.php files for our package automatically. ### How do I use it @@ -83,18 +100,23 @@ You only have to change the parameter or overload the service id. Your service file could look like this: -```xml - - - - - - - [...] - - +```PHP +// /src/Resources/config/services.php +services(); + + $services->set('b2b_role.repository', YourClass::class) + ->parent('b2b_role.repository_abstract') + ->args([service(YourClass::class)]); + // [...] +}; ``` Just define a class with the same service id as our normal class and add our abstract class as the parent. @@ -102,24 +124,24 @@ After that, add your own arguments or override ours. An example of your class could look like this: -```php +```PHP myService = array_pop($args); - + + $this->myService = array_pop($args); + parent::__construct(... $args); } - + public function updateRole(RoleEntity $role): RoleEntity { // your stuff diff --git a/products/extensions/b2b-suite/guides/core/rest-api.md b/products/extensions/b2b-suite/guides/core/rest-api.md index 9b6e7747c4..ede057e50c 100644 --- a/products/extensions/b2b-suite/guides/core/rest-api.md +++ b/products/extensions/b2b-suite/guides/core/rest-api.md @@ -24,7 +24,7 @@ A REST-API Controller is just a plain old PHP-Class, registered to the DIC. An action is a public method suffixed with `Action`. It always gets called with the request implementation derived from Shopware default `Shopware\B2B\Common\MvcExtension\Request` as a parameter. -```php +```PHP +```PHP +$services->set('my.controller', My\Namespace\MyApiController::class); - - - +$services->set('my.api_route_provider', My\Namespace\DependencyInjection\MyApiRouteProvider::class) + ->tag('b2b_common.rest_route_provider'); ``` Notice that the route provider is tagged as a `b2b_common.rest_route_provider`. @@ -92,7 +91,7 @@ Refer to this linked documentation to learn more about placeholders and placehol If you want to use parameters, you have to define an order in which the parameters should be passed to the action: -```php +```PHP [ 'GET', // the HTTP method '/my/hello/{name}', // the sub-route will be concatenated to http://my-shop.de/api/b2b/my/hello/world @@ -104,7 +103,7 @@ If you want to use parameters, you have to define an order in which the paramete And now, you can use the placeholders value as a parameter: -```php +```PHP - - - - - - - +Register the `TemplateNamespaceHierarchyBuilder` by tagging it in the `services.php` file of your plugin. + +```PHP +// /src/Resources/config/services.php +services(); + + $services->set(TemplateNamespaceHierarchyBuilder::class) + ->tag('shopware.twig.hierarchy_builder', ['priority' => 750]); +}; ``` The really important part here is the priority. `750` should work fine for most cases, but if you are having problems here, play around with the priority. @@ -32,7 +34,7 @@ The really important part here is the priority. `750` should work fine for most The `TemplateNamespaceHierarchyBuilder` looks like this. Please replace `MyPlugin` with the name of your plugin. -```php +```PHP - - +```PHP +$services->set(SwagMigrationOwnProfileExample\Profile\OwnProfile\OwnProfile::class) + ->tag('shopware.migration.profile'); ``` ## Creating a gateway Next, you have to create a new gateway which supports your profile: -```php +```PHP - - - - +```PHP +use function Symfony\Component\DependencyInjection\Loader\Configurator\service; + +$services->set(SwagMigrationOwnProfileExample\Profile\OwnProfile\Gateway\OwnLocaleGateway::class) + ->args([ + service(SwagMigrationAssistant\Migration\Gateway\Reader\ReaderRegistry::class), + service(SwagMigrationAssistant\Profile\Shopware\Gateway\Connection\ConnectionFactory::class), + ]) + ->tag('shopware.migration.gateway'); ``` ## Creating a credentials page If you want to try your current progress in the Administration, you can select the profile and gateway in the migration wizard. If you try to go to the next page, there will be an error message because no credentials page was found. To create a new credentials page, you have to add an `index.js` for your new component into `Resources/app/administration/src/own-profile/profile`: -```javascript +```JAVASCRIPT import { Component } from 'src/core/shopware'; import template from './swag-migration-profile-ownProfile-local-credential-form.html.twig'; @@ -296,7 +298,7 @@ Component.register('swag-migration-profile-ownProfile-local-credential-form', { As you can see above, currently, the template does not exist and you have to create this file: `swag-migration-profile-ownProfile-local-credential-form.html.twig` -```html +```HTML {% block own_profile_page_credentials %}
@@ -374,7 +376,7 @@ Note that the component name isn't random and consists of: To see your credentials page, you have to register this component in your `main.js`: -```javascript +```JAVASCRIPT import './own-profile/profile'; ``` @@ -382,7 +384,7 @@ import './own-profile/profile'; Now the credential page is loaded in the Administration and the connection check will succeed. But there is no data selection if you open the data selection table. To add an entry to this table, you have to create a `ProductDataSet` first: -```php +```PHP - - +```PHP +$services->set(SwagMigrationOwnProfileExample\Profile\OwnProfile\DataSelection\ProductDataSelection::class) + ->tag('shopware.migration.data_selection'); - - - +$services->set(SwagMigrationOwnProfileExample\Profile\OwnProfile\DataSelection\DataSet\ProductDataSet::class) + ->tag('shopware.migration.data_set'); ``` ## Creating a product gateway reader Currently, you can see the `DataSelection` in the Administration, but if you select it and start a migration, no product will be migrated. That is because the gateway `read` function isn't implemented yet. But before you can implement this function, you have to create a new `ProductReader` first: -```php +```PHP - - - +```PHP +$services->set(SwagMigrationOwnProfileExample\Profile\OwnProfile\Gateway\Reader\ProductReader::class) + ->parent(SwagMigrationAssistant\Profile\Shopware\Gateway\Local\Reader\AbstractReader::class) + ->args([ + service(SwagMigrationAssistant\Profile\Shopware\Gateway\Connection\ConnectionFactory::class), + ]) + ->tag('shopware.migration.reader'); ``` Once the `ProductReader` is created and registered, you can use it in the `read` method of the `OwnLocaleGateway`: -```php +```PHP - - - - +```PHP +$services->set(SwagMigrationOwnProfileExample\Profile\OwnProfile\Converter\ProductConverter::class) + ->args([ + service(SwagMigrationAssistant\Migration\Mapping\MappingService::class), + service(SwagMigrationAssistant\Migration\Logging\LoggingService::class), + ]) + ->tag('shopware.migration.converter'); ``` To write new entities, you have to create a new writer class, but for the product entity, you can use the `ProductWriter`: -```php +```PHP - - - - - +In the end, you have to register your decorated converter in your `services.php`: + +```PHP +$services->set(SwagMigrationExtendConverterExample\Profile\Shopware\Converter\Shopware55DecoratedProductConverter::class) + ->decorate(SwagMigrationAssistant\Profile\Shopware55\Converter\Shopware55ProductConverter::class) + ->args([ + service('.inner'), + service(SwagMigrationAssistant\Migration\Mapping\MappingService::class), + service(SwagMigrationAssistant\Migration\Logging\LoggingService::class), + service(SwagMigrationAssistant\Migration\Media\MediaFileService::class), + ]); ``` With this, you have decorated your first Shopware migration converter. diff --git a/products/extensions/migration-assistant/guides/extending-the-migration-connector.md b/products/extensions/migration-assistant/guides/extending-the-migration-connector.md index 82b058b40f..1aa283fbc3 100644 --- a/products/extensions/migration-assistant/guides/extending-the-migration-connector.md +++ b/products/extensions/migration-assistant/guides/extending-the-migration-connector.md @@ -19,7 +19,7 @@ With this setup, you have the bundle plugin in Shopware 5 and also the bundle pl To fetch your data via the Shopware 5 API, you have to create a bundle repository first: -```php +```PHP +```PHP +$services->set('swag_migration_bundle_api_example.bundle_repository', SwagMigrationBundleApiExample\Repository\BundleRepository::class) + ->parent(SwagMigrationConnector\Repository\AbstractRepository::class); ``` ## Creating bundle service In the next step, you create a new `BundleService`, which uses your new `BundleRepository` to fetch all bundles and products to map them to one result array: -```php +```PHP @@ -143,19 +141,20 @@ class BundleService extends AbstractApiService } ``` -You have to register the `BundleService` in your `service.xml`: +You have to register the `BundleService` in your `services.php`: -```html - - - +```PHP +$services->set('swag_migration_bundle_api_example.bundle_service', SwagMigrationBundleApiExample\Service\BundleService::class) + ->args([ + service('swag_migration_bundle_api_example.bundle_repository'), + ]); ``` ## Create a new API controller At last, you have to create a new API controller, which uses the `BundleService` to get your bundle data: -```php +```PHP @@ -186,7 +185,7 @@ class Shopware_Controllers_Api_SwagMigrationBundles extends Shopware_Controllers Now you have to create the `BundleReader` in the [SwagMigrationBundleExample](extending-a-shopware-migration-profile) plugin, which only contains the Shopware 5 API route: -```php +```PHP - - +```PHP +$services->set(SwagMigrationBundleExample\Profile\Shopware\Gateway\Api\BundleReader::class) + ->parent(SwagMigrationAssistant\Profile\Shopware\Gateway\Api\Reader\ApiReader::class) + ->tag('shopware.migration.reader'); ``` With that, you have implemented your first plugin migration via API.