



  

Sylius documentation

[![](_images/logo_big.png)](index.html#document-index)

* [Getting Started with Sylius](index.html#document-getting-started-with-sylius/index)

	+ [Installation](index.html#document-getting-started-with-sylius/installation)

	+ [Basic configuration](index.html#document-getting-started-with-sylius/basic-configuration)

	+ [Shipping & Payment](index.html#document-getting-started-with-sylius/shipping-and-payment)

	+ [First product](index.html#document-getting-started-with-sylius/first-product)

	+ [Shop Customizations](index.html#document-getting-started-with-sylius/shop-customizations)

	+ [Custom business logic](index.html#document-getting-started-with-sylius/custom-business-logic)

	+ [Using API](index.html#document-getting-started-with-sylius/using-api)

	+ [Plugin installation](index.html#document-getting-started-with-sylius/plugin-installation)

	+ [Deployment](index.html#document-getting-started-with-sylius/deployment)

	+ [Summary](index.html#document-getting-started-with-sylius/summary)

* [The Book](index.html#document-book/index)

	+ [Introduction](index.html#introduction)

	+ [Installation](index.html#installation)

	+ [Architecture](index.html#architecture)

	+ [Configuration](index.html#configuration)

	+ [Customers](index.html#customers)

	+ [Products](index.html#products)

	+ [Carts & Orders](index.html#carts-orders)

	+ [API](index.html#api)

	+ [Frontend](index.html#frontend)

	+ [Themes](index.html#themes)

	+ [Sylius Plus](index.html#sylius-plus)

	+ [Sylius Plugins](index.html#sylius-plugins)

	+ [Organization](index.html#organization)

	+ [Support](index.html#support)

	+ [Contributing](index.html#contributing)

* [The Customization Guide](index.html#document-customization/index)

	+ [Customizing Models](index.html#document-customization/model)

	+ [Customizing Forms](index.html#document-customization/form)

	+ [Customizing Repositories](index.html#document-customization/repository)

	+ [Customizing Factories](index.html#document-customization/factory)

	+ [Customizing Controllers](index.html#document-customization/controller)

	+ [Customizing Validation](index.html#document-customization/validation)

	+ [Customizing Menus](index.html#document-customization/menu)

	+ [Customizing Templates](index.html#document-customization/template)

	+ [Customizing Translations](index.html#document-customization/translation)

	+ [Customizing Flashes](index.html#document-customization/flash)

	+ [Customizing State Machines](index.html#document-customization/state_machine)

	+ [Customizing Grids](index.html#document-customization/grid)

	+ [Customizing Fixtures](index.html#document-customization/fixtures)

	+ [Customizing Fixture Suites](index.html#document-customization/fixture_suites)

	+ [Customizing API](index.html#document-customization/api/index)

	+ [Tips & Tricks](index.html#document-customization/tips_and_tricks)

	+ [Good to know](index.html#good-to-know)

* [The Cookbook](index.html#document-cookbook/index)

	+ [CLI](index.html#cli)

	+ [Entities](index.html#entities)

	+ [Administration](index.html#administration)

	+ [Shop](index.html#shop)

	+ [Payments](index.html#payments)

	+ [Emails](index.html#emails)

	+ [Promotions](index.html#promotions)

	+ [Inventory](index.html#inventory)

	+ [Shipping methods](index.html#shipping-methods)

	+ [Images](index.html#images)

	+ [Deployment](index.html#deployment)

	+ [Configuration](index.html#configuration)

	+ [Frontend](index.html#frontend)

	+ [Taxation](index.html#taxation)

	+ [API](index.html#api)

* [The BDD Guide](index.html#document-bdd/index)

	+ [Basic Usage](index.html#document-bdd/basic-usage)

	+ [How to add a new context?](index.html#document-bdd/how-to-add-new-context)

	+ [How to add a new page object?](index.html#document-bdd/how-to-add-new-page)

	+ [How to define a new suite?](index.html#document-bdd/how-to-define-new-suite)

	+ [How to use transformers?](index.html#document-bdd/how-to-use-transformers)

* [Components & Bundles](index.html#document-components_and_bundles/index)

	+ [Sylius Bundles Documentation](index.html#document-components_and_bundles/bundles/index)

	+ [Sylius Components Documentation](index.html#document-components_and_bundles/components/index)

* [The Performance Guide](index.html#document-performance/index)

	+ [Database indexes](index.html#document-performance/database-indexes)

	+ [Query optimization](index.html#document-performance/query-optimization)

[🔔 Roadmap](https://sylius.com/roadmap)

[![Blackfire](_static/img/blackfire.png)](https://blackfire.io/docs/introduction?utm_source=sylius&utm_medium=docs&utm_campaign=profiler)

  

 

[Sylius](index.html#document-index)

* [Docs](index.html#document-index) »

* Sylius documentation

* [Sylius demo](//demo.sylius.com)

* |

* [Edit on GitHub](https://github.com/Sylius/Sylius/blob/1.13/docs/index.rst)

[![Sylius](https://sylius.com/assets/docs-banner.png)](https://sylius.com/docs-banner/link)

Sylius Documentation[¶](#sylius-documentation "Permalink to this headline")

===========================================================================

[![Sylius Welcome Page](_images/logo_big.png)](_images/logo_big.png)

  

[Sylius](https://sylius.com) is a modern e-commerce solution for PHP, based on

[Symfony Framework](https://symfony.com).

Note

This documentation assumes you have a working knowledge of the Symfony

Framework. If you’re not familiar with Symfony, please start with

reading the [Quick Tour](https://symfony.com/doc/current/quick_tour) from the Symfony documentation.

Tip

**Getting Started with Sylius, The Book, Customization Guide, The Cookbook, BDD Guide and Performance Guide**

are chapters describing the usage of **the whole Sylius platform**, on the examples for Sylius-Standard distribution.

For tips on using only some bundles of Sylius head to Bundles and Components docs.

Getting Started with Sylius[¶](#getting-started-with-sylius "Permalink to this headline")

-----------------------------------------------------------------------------------------

The essential guide for the Sylius newcomers that want to know it’s most important features, quickly see the power of customization

and run their first Sylius shop within a few hours.

### Getting Started with Sylius[¶](#getting-started-with-sylius "Permalink to this headline")

[**Getting started with Sylius - Online course (8h)**

 LEARN NOW](https://sylius.com/online-course/)This tutorial is dedicated to Sylius newcomers, who want to quickly check our system out - see basic configuration,

do some small customizations, and be able to sell the first products in their new webshop. It shows the quickest

and simplest way from an idea (“I want to sell some stuff online”) to the first results (“I can sell some stuff online!”).

#### Installation[¶](#installation "Permalink to this headline")

So you want to try creating an online shop with Sylius? Great! The first step is the most important one, so let’s start

with the Sylius project installation via Composer. We will be using the latest stable version of Sylius.

##### Before installation[¶](#before-installation "Permalink to this headline")

There are some prerequisites that your local environment should fulfill before installation (not many of them).

[![_images/installation_checklist.png](_images/installation_checklist.png)](_images/installation_checklist.png)

  

For more details, take a look at [this chapter](index.html#document-book/installation/requirements) in **The Book**.

##### Project setup[¶](#project-setup "Permalink to this headline")

The easiest way to install Sylius on your local machine is to use the following command:

```

composer create-project sylius/sylius-standard MyFirstShop

```

It will create a `MyFirstShop` directory with a brand new Sylius application inside.

Note

Are you familiar with Docker? Check out the [Sylius Installation Guide with Docker](index.html#document-book/installation/installation_with_docker)

Warning

Beware! The next step includes the database setup. It will set your database credentials

(username, password, and database name) in the file with environment variables (`.env` is the most basic one).

Warning

Specific Sylius versions may support various Symfony versions. To make sure the correct Symfony version will be

installed (Symfony 6.0 for example) use:

```

composer config extra.symfony.require "^6.0"

composer update

```

Otherwise, you may face the problem of having Symfony components of the wrong version installed.

To launch a Sylius application initial data has to be set up: an administrator account and base locale.

Run the Sylius installation command to do that.

```

cd MyFirstShop

bin/console sylius:install

```

This command will do several things for you - the first two steps are checking if your environment fulfills technical requirements,

and setting the project database. You will also be asked if you want to have default fixtures loaded into your database - let’s say

“No” to that, we will configure the store manually.

You will be also asked if you want to generate API Tokens.

![_images/installation1.png](_images/installation1.png)

It’s essential to put some attention to the 3rd installation step. There you configure your default administrator account, which

will be later used to access Sylius admin panel.

![_images/installation2.png](_images/installation2.png)

To derive joy from Sylius SemanticUI-based views, you should use `yarn` to load our assets.

```

yarn install

yarn build

```

That’s it! You’re ready to launch your empty Sylius-based web store.

##### Launching application[¶](#launching-application "Permalink to this headline")

For the testing reasons, the fastest way to start the application is using Symfony binary. It can be downloaded from [here](https://symfony.com/download). Let’s also start

browsing the application from the Admin panel.

```

symfony serve

open http://127.0.0.1:8000/admin

```

Great! You are closer to the final goal. Let’s configure your application a little bit, to make it usable by some future customers.

###### Learn more[¶](#learn-more "Permalink to this headline")

* [Installation chapter in The Book](index.html#document-book/installation/index)

#### Basic configuration[¶](#basic-configuration "Permalink to this headline")

The first place you should check out in the Admin panel is the **Configuration** section.

There you can find a bunch of modules used to customize your shop the most basic data.

##### Channel[¶](#channel "Permalink to this headline")

The most important one is the **Channels** section. It should consist of one channel already created by you

with the installation command. Channels contain the most basic data about your store, like available locales,

currencies, shop billing data, etc. You can modify the channel’s configuration:

![_images/channel.png](_images/channel.png)

##### Locale[¶](#locale "Permalink to this headline")

Sylius supports internationalization on many levels - you can easily add new locales to your shop to allow your customers

browsing it in their desired language. As set in the installation command, the only **Locale** available right now

should be **English (United States)**. This will be the base locale of your shop, therefore all of your products or

taxons etc. have to be created with at an english name at least.

![_images/locale.png](_images/locale.png)

##### Currency[¶](#currency "Permalink to this headline")

Each channel operates only on one **Base Currency**, but prices can be shown in multiple **Currencies**, with a ratio between them configured by **Exchange rates**.

For now, the only available currency should be **USD**, which was also created by the `sylius:install` command.

![_images/currency.png](_images/currency.png)

Note

All the previous data was created by the installation command - but you should also add two more things to the store

configuration to make it work in 100%. It will also be required to have them in the next chapter of this guide.

##### Country[¶](#country "Permalink to this headline")

Most of the shops ship their merchandise to various countries in the world. To configure which countries will be available

as shipping destinations in your store, you should add some countries in the **Countries** section.

Adding a country:

![_images/country-creation.png](_images/country-creation.png)

Added country displayed on the index page:

![_images/country-index.png](_images/country-index.png)

##### Zone[¶](#zone "Permalink to this headline")

The last configuration step is creating a zone. They are used for various reasons, like shipping and taxing operations,

and can consist of countries, provinces or other zones.

[![_images/zones-types.png](_images/zones-types.png)](_images/zones-types.png)

  

Let’s create one, basic zone named *United States* for the only country in the system (also *United States*).

This way the basic shop configuration is done!

![_images/zone-creation.png](_images/zone-creation.png)

###### Learn more[¶](#learn-more "Permalink to this headline")

* [Channels](index.html#document-book/configuration/channels)

* [Currencies](index.html#document-book/configuration/currencies)

* [Pricing](index.html#document-book/products/pricing)

* [Locales](index.html#document-book/configuration/locales)

#### Shipping & Payment[¶](#shipping-payment "Permalink to this headline")

The basic configuration is done. We can now proceed to let potential customers buy our merchandise.

During the checkout process, they should be able to define how do they want their order to be shipped,

as well as how they would pay for that.

##### Shipping method[¶](#shipping-method "Permalink to this headline")

Sylius allows configuring different ways to ship the order, depending on shipping address (the **Zone** concept is essential there!),

or affiliation to some specific **Shipping Category**. Let’s then create a shipping method called “FedEx” that would cost $10.00 for a whole order.

![_images/shipping-method.png](_images/shipping-method.png)

##### Payment method[¶](#payment-method "Permalink to this headline")

Customer should also be able to choose, how they are willing to pay. At least one payment method is required - let’s make it “Cash on delivery”.

Before creation, we need to specify the payment method gateway, which is a way for processing the payment

(*Offline*, *PayPal Commerce Platform*, *PayPal Express Checkout* and *Stripe* are supported by default).

Gateway selection:

[![_images/gateways.png](_images/gateways.png)](_images/gateways.png)

  

Payment method creation:

![_images/payment-method-creation.png](_images/payment-method-creation.png)

Attention

*Psst!* You can find integrations with more payment gateways if you take a look at some [Sylius plugins](https://sylius.com/plugins)

Great! The only thing left is creating some products, and we can go shopping!

###### Learn more[¶](#learn-more "Permalink to this headline")

* [Shipments](index.html#document-book/orders/shipments)

* [Payments](index.html#document-book/orders/payments)

#### First product[¶](#first-product "Permalink to this headline")

We move to one of the most important sections of the Admin panel - products management. As you can see, the **Catalog**

section in the menu is quite extended, but we will, for now, focus on product creation only.

You can pick a *simple* or *configurable* product type before its creation. Making long story short -

*simple* is a product that has just one version,

*configurable* is a product where the customer gets to choose some options of it (like size or colour).

Check out the [Products](index.html#document-book/products/products) chapter in **The Book** for more information.

[![_images/product-types.png](_images/product-types.png)](_images/product-types.png)

  

During the product creation, you can decide about many of its traits. How much does it cost? Is it a physical product that requires

shipping? Should it be tracked within the inventory system? On which channel will it be available? Should it have some taxes added during

the checkout or not? Take a while to explore these options later, right now let’s fill only the most critical data:

![_images/product-creation.png](_images/product-creation.png)

##### Summary[¶](#summary "Permalink to this headline")

Great, the first stage is done! The whole checkout process is described in [this part of the documentation](index.html#document-book/orders/checkout).

![_images/checkout-summary.png](_images/checkout-summary.png)

We can now move to some more advanced parts of the tutorial - Sylius features customization and deploying it into the server,

to make it available to the world.

###### Learn more[¶](#learn-more "Permalink to this headline")

* [Products](index.html#document-book/products/products)

* [Taxons](index.html#document-book/products/taxons)

* [Inventory](index.html#document-book/products/inventory)

* [Taxation](index.html#document-book/orders/taxation)

#### Shop Customizations[¶](#shop-customizations "Permalink to this headline")

What makes Sylius unique from other e-commerce systems is not only its highly developed community or clean code base. The developer

experience has always been a great advantage of this platform - and it includes easiness of customization and great extendability.

Let’s get the benefit from these features and make some simple customization, to make your store even more suitable for your

business needs.

##### Logo[¶](#logo "Permalink to this headline")

You can start with the shop panel. The default templates are elegant and straightforward, but for sure you would like

to make them unique for your online store. Maybe some colors should be different? Or even the whole product page does

not look like you want? Fortunately, twig templates are easy to override or customize (take a look at

[Customizing Templates chapter](index.html#document-customization/template) for more info).

In the beginning, try a very simple, but also one of the most crucial changes - displaying your shop logo in place of the Sylius logo.

Default logo in shop panel:

![_images/logo-before.png](_images/logo-before.png)

Firstly, we need to add our logo to the project. You can do it by copying your logo to the `<project\_root>/assets/shop/images/logo.png`

and importing it in `<project\_root>/assets/shop/entry.js`.

Your `entry.js` should look like this:

```

import './images/logo.png';

```

Now you should run `yarn build` to rebuild the assets.

The second step is to detect which template is responsible for displaying the logo and therefore which should be overridden

to customize a logo image.

It’s placed in **SyliusShopBundle**, at `Resources/views/Layout/Header/\_logo.html.twig` path, so to override it,

you should create the `templates/bundles/SyliusShopBundle/Layout/Header/\_logo.html.twig` file and copy the original file content.

Next, replace the `img` element source with a link to the logo or properly imported asset image (take a look at

[Managing assets](index.html#document-book/frontend/managing-assets) for more info).

The other way to achieve this is to modify the configuration of the `sylius.shop.layout.header.grid` template event.

Here for sake of example the same logo file `templates/shop/Layout/Header/\_logo.html.twig` used as in the example above.

Add the configuration to the file that stores your sylius template event settings:

```

# config.yaml

sylius\_ui:

 events:

 sylius.shop.layout.header.grid:

 blocks:

 logo: 'shop/Layout/Header/\_logo.html.twig'

```

If you want to learn more about template customization with sylius template events - click [here](index.html#document-customization/template).

Hint

We encourage to create and register another `.yaml` file to store template changes for more clarity in configuration files.

At the end of customization, the overridden file would look similar to this:

```

<div class="column">

 <a href="{{ path('sylius\_shop\_homepage') }}"><img src="{{ asset('build/app/shop/images/logo.png', 'app.shop') }}" alt="Logo" class="ui small image" /></a>

</div>

```

A custom logo should now be displayed on the Shop panel header:

![_images/logo-after.png](_images/logo-after.png)

Great! You’ve managed to customize a template in Sylius! Let’s move to something a little bit more complicated but also much

more satisfying - introducing your own business logic into the system.

#### Custom business logic[¶](#custom-business-logic "Permalink to this headline")

Templates customization is just the beginning of the broad spectrum of customization possibilities in Sylius. There

are very few things in Sylius you’re not able to customize or override. Let’s take a look at one of the typical example of

customizing Sylius default logic, in this case, logic related to shipments and their cost. It’s time for a custom shipping

calculator.

##### Custom shipping calculator[¶](#custom-shipping-calculator "Permalink to this headline")

Each shipping calculator is able to calculate a shipping cost for the provided order. This calculation is usually based on

bought products and some configuration done by Administrator. By default Sylius provides `FlatRateCalculator` and

`PerUnitRateCalculator` (their names are quite self-explaining), but it’s sometimes not enough. So let’s say your store packs

ordered products in parcels and you need to charge a customer for each of them.

You should start with the implementation of your custom shipping calculator service. Remember, that it must implement the

`CalculatorInterface` from **Shipping Component**. Let’s name it `ParcelCalculator` and place it in `src/ShippingCalculator`

directory.

```

<?php

# src/ShippingCalculator/ParcelCalculator.php

declare(strict\_types=1);

namespace App\ShippingCalculator;

use Sylius\Component\Shipping\Calculator\CalculatorInterface;

use Sylius\Component\Shipping\Model\ShipmentInterface;

final class ParcelCalculator implements CalculatorInterface

{

    public function calculate(ShipmentInterface $subject, array $configuration): int

    {

        $parcelSize = $configuration['size'];

        $parcelPrice = $configuration['price'];

        $numberOfPackages = ceil($subject->getUnits()->count() / $parcelSize);

        return (int) ($numberOfPackages \* $parcelPrice);

    }

    public function getType(): string

    {

        return 'parcel';

    }

}

```

Two more things are needed to make it work. A form type, that would be used to pass some data to the `$configuration` array

in the calculator service, and a proper service registration in the `services.yaml` file.

```

<?php

# src/Form/Type/ParcelShippingCalculatorType.php

declare(strict\_types=1);

namespace App\Form\Type;

use Sylius\Bundle\MoneyBundle\Form\Type\MoneyType;

use Symfony\Component\Form\AbstractType;

use Symfony\Component\Form\Extension\Core\Type\NumberType;

use Symfony\Component\Form\FormBuilderInterface;

final class ParcelShippingCalculatorType extends AbstractType

{

    public function buildForm(FormBuilderInterface $builder, array $options): void

    {

        $builder

            ->add('size', NumberType::class)

            ->add('price', MoneyType::class, [

                'currency' => 'USD',

            ])

        ;

    }

}

```

Attention

The currency needed for `MoneyType` in the proposed implementation hardcoded just for testing reasons. In a real application,

you should get the proper currency code from the repository, context or some configuration file.

```

# config/services.yml

services:

 # ...

 App\ShippingCalculator\ParcelCalculator:

 tags:

 -

 {

 name: sylius.shipping\_calculator,

 calculator: "parcel",

 label: "Parcel",

 form\_type: App\Form\Type\ParcelShippingCalculatorType

 }

```

That’s it! You should now be able to select your shipping calculator during the creation or edition of a shipping method.

[![_images/shipping-calculator.png](_images/shipping-calculator.png)](_images/shipping-calculator.png)

  

You can also see the results of your customization on checkout shipping step, how the shipping fee changes depending on how

many products you have in the cart.

For 1 product:

[![_images/shipping-cost-1.png](_images/shipping-cost-1.png)](_images/shipping-cost-1.png)

  

For 4 products:

[![_images/shipping-cost-2.png](_images/shipping-cost-2.png)](_images/shipping-cost-2.png)

  

This customization will also work when using unified API without any extra steps:

First, you need to pick up a new cart:

```

curl -X POST "https://master.demo.sylius.com/api/v2/shop/orders" -H "accept: application/ld+json"

```

With body:

```

{

 "localeCode": "string"

}

```

Note

The `localeCode` value is optional in the body of cart pickup. This means that if you won’t provide it, the default locale from the channel will be used.

This should return a response with `tokenValue` which we would need for the next API calls:

```

{

 //...

 "shippingState": "string",

 "tokenValue": "CART\_TOKEN",

 "id": 123,

 //...

}

```

Then we need to add a product to the cart but first, it would be good to have any. You can use this call to retrieve some products:

```

curl -X GET "https://master.demo.sylius.com/api/v2/shop/products?page=1&itemsPerPage=30" -H "accept: application/ld+json"

```

And choose any product variant IRI that you would like to add to your cart:

```

curl --location --request PATCH 'https://master.demo.sylius.com/api/v2/shop/orders/CART\_TOKEN/items' -H 'Content-Type: application/merge-patch+json'

```

With a chosen product variant in the body:

```

{

 "productVariant": "/api/v2/shop/product-variants/PRODUCT\_VARIANT\_CODE",

 "quantity": 1

}

```

This should return a response with the cart that should contain a `shippingTotal`:

```

{

 //...

 "taxTotal": 0,

 "shippingTotal": 500,

 "orderPromotionTotal": 0,

 //...

}

```

Attention

API returns costs in decimal numbers that’s why in response it is 500 currency unit shipping total (which stands for 5 USD in this case).

Now let’s change the quantity of our product variant. We can do it by calling the endpoint above once again, or by utilizing the `changeQuantity` endpoint:

```

curl --location --request PATCH 'https://master.demo.sylius.com/api/v2/shop/orders/CART\_TOKEN/items/ORDER\_ITEM\_ID' -H 'Content-Type: application/merge-patch+json'

```

With new quantity in the body:

```

{

 "quantity": 4

}

```

Which should return a response with the cart:

```

{

 //...

 "taxTotal": 0,

 "shippingTotal": 1000,

 "orderPromotionTotal": 0,

 //...

}

```

Amazing job! You’ve just provided your own logic into a Sylius-based system. Therefore, your store can provide a unique

experience for your Customers. Basing on this knowledge, you’re ready to customize your shop even more and make it as suitable

to your business needs as possible.

###### Learn more[¶](#learn-more "Permalink to this headline")

* [Customizations](index.html#document-customization/index)

* [Shipments](index.html#document-book/orders/shipments)

* [Checkout](index.html#document-book/orders/checkout)

* [Orders](index.html#document-book/orders/orders)

* [Adjustments](index.html#document-book/orders/adjustments)

* [Unified API](index.html#document-book/api/index)

#### Using API[¶](#using-api "Permalink to this headline")

Since sylius 1.8 we allow you to use our new API based on ApiPlatform.

Here are some examples of basic usage for a shop implementation.

##### Register a customer[¶](#register-a-customer "Permalink to this headline")

To register a customer all we need to do is a single POST request:

```

curl -X 'POST' \

 'https://master.demo.sylius.com/api/v2/shop/customers' \

 -H 'accept: \*/\*' \

 -H 'Content-Type: application/ld+json' \

 -d '{

 "firstName": "shop",

 "lastName": "user",

 "email": "shop.user@example.com",

 "password": "pa$$word",

 "subscribedToNewsletter": true

}'

```

If we get response code 204, it means our customer has been registered successfully.

![_images/api_platform_shop_customer_post.png](_images/api_platform_shop_customer_post.png)

##### Login to the shop[¶](#login-to-the-shop "Permalink to this headline")

Once the shop customer has been registered, we can now create a login request to get the authentication token, which will allow us to use more shop endpoints.

To get this token, lets create a simple login request:

```

curl -X 'POST' \

 'https://master.demo.sylius.com/api/v2/shop/authentication-token' \

 -H 'accept: application/json' \

 -H 'Content-Type: application/json' \

 -d '{

 "email": "shop.user@example.com",

 "password": "pa$$word"

}'

```

![_images/api_platform_shop_customer_post.png](_images/api_platform_shop_customer_post.png)

As a response we should get code 200, along with token and customer iri.

```

{

 "token": "string",

 "customer": "iri"

}

```

Note

If your shop has been customized and user first need to authenticate by email authentication, the token won’t be returned.

With this token we are able to authenticate ourselves to use full shop potential of sylius API.

```

curl -X 'METHOD' \

 'api-url' \

 -H 'accept: application/ld+json' \

 -H 'Authorization: Bearer token'

```

##### Utilising most typical operations[¶](#utilising-most-typical-operations "Permalink to this headline")

With our customer created and authorized we are now ready to use our shop API. As we are in world of e-commerce,

there should be no doubt that most important operations are related with products, carts and orders.

In this chapter of Using API we will get to know with these operations on API.

##### Adding product to cart[¶](#adding-product-to-cart "Permalink to this headline")

Let’s make a simple process of checking out in shop API, it is possible to complete it as guest (not authenticated customer)

or as authorized shop user. For example purposes we will use the logged in shop user.

First step is to pickup a new cart:

```

curl -X 'POST' \

 'https://master.demo.sylius.com/api/v2/shop/orders' \

 -H 'accept: application/ld+json' \

 -H 'Content-Type: application/ld+json' \

 -H 'Authorization: Bearer token' \

 -d '{

 # "localeCode": "string" (optional)

 }'

```

Note

You can checkout your cart in different locale if needed. If no localeCode is provided, the channels default will be added automatically.

As a response we should get code 201, along with default cart values and tokenValue which we need for next operations.

![_images/api_platform_shop_orders_post.png](_images/api_platform_shop_orders_post.png)

Now let’s add some product to this cart, first we need the ProductVariant IRI, we can get some of the variants from

```

curl -X 'GET' \

 'https://master.demo.sylius.com/api/v2/shop/product-variants?page=1&itemsPerPage=30' \

 -H 'accept: application/ld+json' \

 -H 'Authorization: Bearer token'

```

and let’s take first variant @id from list:

```

// ...

{

 "@id": "/api/v2/shop/product-variants/Everyday\_white\_basic\_T\_Shirt-variant-0",

 "@type": "ProductVariant",

 "id": 123889,

 "code": "Everyday\_white\_basic\_T\_Shirt-variant-0",

 "product": "/api/v2/shop/products/Everyday\_white\_basic\_T\_Shirt",

 "optionValues": [

 "/api/v2/shop/product-option-values/t\_shirt\_size\_s"

 ],

 "translations": {

 "en\_US": {

 "@id": "/api/v2/shop/product-variant-translations/123889",

 "@type": "ProductVariantTranslation",

 "id": 123889,

 "name": "S",

 "locale": "en\_US"

 }

 },

 "price": 6420,

 "originalPrice": 6420,

 "inStock": true

}

// ...

```

Then going back to cart - let’s add the variant to our cart with:

```

curl -X 'PATCH' \

 'https://master.demo.sylius.com/api/v2/shop/orders/rl1KwtiSLA/items' \

 -H 'accept: application/ld+json' \

 -H 'Authorization: Bearer token' \

 -H 'Content-Type: application/merge-patch+json' \

 -d '{

 "productVariant": "/api/v2/shop/product-variants/Everyday\_white\_basic\_T\_Shirt-variant-0",

 "quantity": 1

}'

```

![_images/api_platform_shop_orders_items_patch.png](_images/api_platform_shop_orders_items_patch.png)

And after this call the response should has code 200, and in body we can see that the product variant has been added:

```

{

 # Rest of orders body

 "items": [

 {

 "@id": "/api/v2/shop/order-items/59782",

 "@type": "OrderItem",

 "variant": "/api/v2/shop/product-variants/Everyday\_white\_basic\_T\_Shirt-variant-0",

 "productName": "Everyday white basic T-Shirt",

 "id": 59782,

 "quantity": 1,

 "unitPrice": 6420,

 "total": 6869,

 "subtotal": 6420

 }

 ],

 # Rest of orders body

}

```

##### Changing product quantity[¶](#changing-product-quantity "Permalink to this headline")

In this example we will use the product variant from example above. We will change the quantity of this item.

We need from the last response an id of product variant (it is 59782 in this example). Let’s populate the

fields needed for request and call it:

```

curl -X 'PATCH' \

 'https://master.demo.sylius.com/api/v2/shop/orders/OPzFiAWefi/items/59782' \

 -H 'accept: application/ld+json' \

 -H 'Authorization: Bearer token' \

 -H 'Content-Type: application/merge-patch+json' \

 -d '{

 "quantity": 3

}'

```

![_images/api_platform_shop_orders_change_quantity.png](_images/api_platform_shop_orders_change_quantity.png)

And the response should return status code 200 and in items, the quantity as well as total price of this product variant should be changed:

```

{

 # Rest of orders body

 "items": [

 {

 "@id": "/api/v2/shop/order-items/59782",

 "@type": "OrderItem",

 "variant": "/api/v2/shop/product-variants/Everyday\_white\_basic\_T\_Shirt-variant-0",

 "productName": "Everyday white basic T-Shirt",

 "id": 59782,

 "quantity": 3,

 "unitPrice": 6420,

 "total": 19538,

 "subtotal": 19260

 }

 ],

 # Rest of orders body

}

```

![_images/api_platform_shop_orders_change_quantity_response.png](_images/api_platform_shop_orders_change_quantity_response.png)

##### Completing the order[¶](#completing-the-order "Permalink to this headline")

So, we have our cart with items that we want to buy. Let’s finish our order by completing and placing it.

There are just few more steps to do it:

**1.** Addressing order

```

curl -X 'PATCH' \

 'https://master.demo.sylius.com/api/v2/shop/orders/rl1KwtiSLA/address' \

 -H 'accept: application/ld+json' \

 -H 'Authorization: Bearer token' \

 -H 'Content-Type: application/merge-patch+json' \

 -d '{

 "email": "shop.user@example.com",

 "billingAddress": {

 "city": "California",

 "street": "Coral str",

 "postcode": "90210",

 "countryCode": "US",

 "firstName": "David",

 "lastName": "Copperfield"

 }

}'

```

![_images/api_platform_shop_orders_addressing.png](_images/api_platform_shop_orders_addressing.png)

Note

The shippingAddress field is optional - if no shippingAddress is provided the field will be cloned from billingAddress

Which will response with addressed cart:

![_images/api_platform_shop_orders_addressing_response.png](_images/api_platform_shop_orders_addressing_response.png)

**2.** Selecting Shipping/Payment Method

In case of both shipment and payment first we need to get their corresponding id and method.

We can get it from the address response or call once again all data about order by calling:

```

curl -X 'GET' \

 'https://master.demo.sylius.com/api/v2/shop/orders/rl1KwtiSLA' \

 -H 'accept: application/ld+json' \

 -H 'Authorization: Bearer token'

```

In response we need to find payment and shipment where everyone of them has the required fields.

```

"payments": [

 {

 "@id": "/api/v2/shop/payments/20446",

 "@type": "Payment",

 "id": 20446,

 "method": "/api/v2/shop/payment-methods/cash\_on\_delivery"

 }

],

"shipments": [

 {

 "@id": "/api/v2/shop/shipments/17768",

 "@type": "Shipment",

 "id": 17768,

 "method": "/api/v2/shop/shipping-methods/ups"

 }

],

```

Let’s make a shipment in this example. The required fields are shipmentId in URL query and shipmentMethod IRI in body:

```

curl -X 'PATCH' \

 'https://master.demo.sylius.com/api/v2/shop/orders/rl1KwtiSLA/shipments/17768' \

 -H 'accept: application/ld+json' \

 -H 'Authorization: Bearer token' \

 -H 'Content-Type: application/merge-patch+json' \

 -d '{

 "shippingMethod": "/api/v2/shop/shipping-methods/ups"

}'

```

![_images/api_platform_shop_orders_choose_shipping.png](_images/api_platform_shop_orders_choose_shipping.png)

which should respond with 200 status code and data about order.

And for the Payment the process is the same just change the:

1. Endpoint api/v2/shop/orders/tokenValue/shipments/shipmentId => api/v2/shop/orders/tokenValue/payments/paymentId

2. Body shippingMethod => paymentMethod (with method from possible payments)

Now with all the data fulfilled, let’s proceed to last step.

**3.** Completing Cart

This is the last step of checkout process. Just simply call this endpoint and if you want, you can add a note to your order:

```

curl -X 'PATCH' \

 'https://master.demo.sylius.com/api/v2/shop/orders/rl1KwtiSLA/complete' \

 -H 'accept: application/ld+json' \

 -H 'Authorization: Bearer token' \

 -H 'Content-Type: application/merge-patch+json' \

 -d '{

 "notes": "your note"

}'

```

![_images/api_platform_shop_orders_completed.png](_images/api_platform_shop_orders_completed.png)

It will respond with 200 status code and data about order where checkoutState should be changed to completed:

```

{

 # Orders body

 "currencyCode": "USD",

 "localeCode": "en\_US",

 "checkoutState": "completed",

 "paymentState": "awaiting\_payment",

 "shippingState": "ready",

 # Orders body

}

```

And here we go - full checkout process completed with new API on Sylius. With this basic usage you should be able to

create fully functional shop frontend based on Sylius backend and logic.

#### Plugin installation[¶](#plugin-installation "Permalink to this headline")

Sylius is easy to customize to your business needs, but not all of the customizations you have to do on your own!

Sylius supports creating plugins, that are the best way to extend its functionality and share these new features with the Community.

You can already benefit from some plugins developed by us (Sylius Core team) or the Community.

All of the plugins officially approved are listed on [our website](https://sylius.com/plugins/), but even more of them can be seen in the Sylius ecosystem.

To give you a quick overview of how easy-to-use and powerful plugins can be, let’s install the **SyliusCmsPlugin** (developed

by **BitBag**), one of the most popular extensions to Sylius.

Plugin’s installation instruction is widely explained [in plugin’s documentation](https://github.com/BitBagCommerce/SyliusCmsPlugin/blob/master/doc/installation.md)

and consists of standard steps, that you can see in most of Sylius plugins:

* plugin installation with composer

* configuration (including routing importing)

* database update

* some extra steps (installing CKEditor, in this plugin’s case)

After the installation you should be able to use plugin’s features in your shop:

[![_images/plugin-installed.png](_images/plugin-installed.png)](_images/plugin-installed.png)

  

Usage of the plugin is one of the quickest ways to customize the store to your needs. We already have lots of plugins

in the ecosystem, and their number is growing, so remember to check for the existing solution before implementing it on your own.

There is no need to reinvent the wheel :)

##### Learn more[¶](#learn-more "Permalink to this headline")

* [Plugins](index.html#document-book/plugins/index)

* [Plugin development guide](index.html#document-book/plugins/guide/index)

* [Official plugins](index.html#document-book/plugins/official-plugins)

#### Deployment[¶](#deployment "Permalink to this headline")

Development usually takes most of the time in project implementation, but we should not forget about what’s at the end of this process -

application deployment into the server. We believe, that it should be as easy and understandable as possible.

Check out our deployment cookbooks:

Tip

* 👉 [How to deploy Sylius to Platform.sh?](index.html#document-cookbook/deployment/platform-sh)

* 👉 [How to deploy Sylius to Symfony Cloud?](index.html#document-cookbook/deployment/symfonycloud)

* 👉 [How to deploy Sylius to Artifakt?](index.html#document-cookbook/deployment/artifakt)

* 👉 [How to deploy Sylius to Cloudways?](index.html#document-cookbook/deployment/cloudways)

* 🐳 [How to deploy Sylius with Docker](index.html#document-cookbook/deployment/docker)

##### Learn more about the deployment platforms[¶](#learn-more-about-the-deployment-platforms "Permalink to this headline")

* [Platform.sh](https://docs.platform.sh)

* [Symfony Cloud](https://symfony.com/cloud/)

* [Artifakt.com](https://docs.artifakt.com/)

* [Cloudways PHP Hosting](https://support.cloudways.com/en/)

* [Docker](https://docker.com/)

#### Summary[¶](#summary "Permalink to this headline")

We hope you’ve enjoyed your first journey with Sylius. Basing on the already gathered knowledge you have now plenty of

possibilities to use Sylius, customize it and add new functionalities with your own or Community’s code.

There are a few tips at the end of this tutorial:

* if you want to improve your knowledge about Sylius features, take a look at [The Book](index.html#document-book/index)

* if you’re interested in making more customizations, you should read some chapters from [The Customization Guide](index.html#document-book/index)

* if you want to share your work with the Community, check out the [Sylius Plugins](index.html#document-book/plugins/index) chapter

And the most important - if you want to become a part of our collectivity, join our [Slack](https://sylius.com/slack),

[Forum](https://forum.sylius.com/) and follow [our repository](https://github.com/Sylius/Sylius) to be always up-to-date

with the newest Sylius releases. If you have any ideas about how to make Sylius better and want to support us on catalyzing

trade with technology - open issues, pull requests and join discussions on Github. Sylius is only strong with the Community :)

Good luck!

* [Installation](index.html#document-getting-started-with-sylius/installation)

* [Basic configuration](index.html#document-getting-started-with-sylius/basic-configuration)

* [Shipping & Payment](index.html#document-getting-started-with-sylius/shipping-and-payment)

* [First product](index.html#document-getting-started-with-sylius/first-product)

* [Shop Customizations](index.html#document-getting-started-with-sylius/shop-customizations)

* [Custom business logic](index.html#document-getting-started-with-sylius/custom-business-logic)

* [Using API](index.html#document-getting-started-with-sylius/using-api)

* [Plugin installation](index.html#document-getting-started-with-sylius/plugin-installation)

* [Deployment](index.html#document-getting-started-with-sylius/deployment)

* [Summary](index.html#document-getting-started-with-sylius/summary)

Warning

Be aware, that this guide is written for developers! To understand every chapter correctly, you need to have

at least a foggy idea about object-oriented programming and PHP language.

[Symfony](https://symfony.com/doc/current/index.html) experience will also be handy.

* [Installation](index.html#document-getting-started-with-sylius/installation)

* [Basic configuration](index.html#document-getting-started-with-sylius/basic-configuration)

* [Shipping & Payment](index.html#document-getting-started-with-sylius/shipping-and-payment)

* [First product](index.html#document-getting-started-with-sylius/first-product)

* [Shop Customizations](index.html#document-getting-started-with-sylius/shop-customizations)

* [Custom business logic](index.html#document-getting-started-with-sylius/custom-business-logic)

* [Using API](index.html#document-getting-started-with-sylius/using-api)

* [Plugin installation](index.html#document-getting-started-with-sylius/plugin-installation)

* [Deployment](index.html#document-getting-started-with-sylius/deployment)

* [Summary](index.html#document-getting-started-with-sylius/summary)

The Book[¶](#the-book "Permalink to this headline")

---------------------------------------------------

The Developer’s guide to leveraging the flexibility of Sylius. Here you will find all the concepts used in the Sylius platform.

[The Book](index.html#document-book/index) helps to understand how Sylius works.

### The Book[¶](#the-book "Permalink to this headline")

The Developer’s guide to leveraging the flexibility of Sylius. Here you will find all the concepts used in Sylius.

The Books helps to understand how Sylius works.

#### Introduction[¶](#introduction "Permalink to this headline")

Introduction aims to describe the philosophy of Sylius. It will also teach you about environments before you start installing it.

##### Introduction[¶](#introduction "Permalink to this headline")

This is the beginning of the journey with Sylius. We will start with a basic insight into terms that we use in Sylius Documentation.

###### Introduction to Sylius[¶](#introduction-to-sylius "Permalink to this headline")

Sylius is a game-changing e-commerce solution for PHP, based on the Symfony framework.

####### Philosophy[¶](#philosophy "Permalink to this headline")

Sylius is completely open source (MIT license) and free, maintained by a diverse and creative community of developers and companies.

What are our core values and what makes us different from other solutions?

* Components based approach

* Unlimited flexibility and simple customization

* Developer-friendly, using latest technologies

* Developed using best practices and BDD approach

* [Highest quality of code](https://scrutinizer-ci.com/g/Sylius/Sylius/)

And much more, but we will let you discover it yourself.

######## Sylius Plus[¶](#sylius-plus "Permalink to this headline")

There exists a commercial edition of Sylius, which is called [Sylius Plus](https://sylius.com/plus/).

Sylius Plus gives you all the power of Open Source and much more. It comes with a set of enterprise-grade features

and technical support from its creators. As the state-of-the-art eCommerce platform, it reduces risks and increases your ROI.

In this documentation you will find also chapters dedicated to features introduced by the Sylius Plus edition, they will

be marked with a frame just like this section.

####### The Three Natures of Sylius[¶](#the-three-natures-of-sylius "Permalink to this headline")

Sylius is constructed from fully decoupled and flexible e-commerce components for PHP. It is also a set of Symfony bundles, which integrate the components into the full-stack framework.

On top of that, Sylius is also a complete e-commerce platform crafted from all these building blocks.

It is your choice how to use Sylius, you can benefit from the components with any framework, integrate selected bundles into existing or new Symfony app or built your application on top of Sylius platform.

####### Sylius Platform[¶](#sylius-platform "Permalink to this headline")

This book is about our **full-stack e-commerce platform**, which is a standard Symfony application providing the most common webshop and a foundation for custom systems.

####### Leveraging Symfony Bundles[¶](#leveraging-symfony-bundles "Permalink to this headline")

If you prefer to build your very custom system step by step and from scratch, you can integrate the standalone Symfony bundles. For the installation instructions, please refer to the appropriate bundle documentation.

####### E-Commerce Components for PHP[¶](#e-commerce-components-for-php "Permalink to this headline")

If you use a different framework than Symfony, you are welcome to use Sylius components, which will make it much easier to implement a webshop with any PHP application and project.

They provide you with default models, services and logic for all aspects of e-commerce, completely separated and ready to use.

####### Roadmap[¶](#roadmap "Permalink to this headline")

Are you wondering about Sylius plans for the next releases? If so then you should follow our [Roadmap](https://sylius.com/roadmap).

Through our [Slack and Forum](index.html#document-book/support/index) you can contribute by conversation and votes on the most desired features and improvements.

####### Final Thoughts[¶](#final-thoughts "Permalink to this headline")

Depending on how you want to use Sylius, continue reading The Book, which

covers the usage of the full stack solution, browse the Bundles Reference or learn about The Components.

###### Understanding Environments[¶](#understanding-environments "Permalink to this headline")

Every Sylius application is the combination of code and a set of configuration that dictates how that code should function. The configuration may define the database being used, whether or not something should be cached, or how verbose logging should be. In Symfony, the idea of “environments” is the idea that the same codebase can be run using multiple different configurations. For example, the dev environment should use configuration that makes development easy and friendly, while the prod environment should use a set of configuration optimized for speed.

####### Development[¶](#development "Permalink to this headline")

Development environment or `dev`, as the name suggests, should be used for development purposes. It is much slower than production, because it uses much less aggressive caching and does a lot of processing on every request.

However, it allows you to add new features or fix bugs quickly, without worrying about clearing the cache after every change.

Sylius console runs in `dev` environment by default. You can access the website in dev mode via the `/index.php` file in the `public/` directory. (under your website root)

####### Production[¶](#production "Permalink to this headline")

Production environment or `prod` is your live website environment. It uses proper caching and is much faster than other environments. It uses live APIs and sends out all e-mails.

To run Sylius console in `prod` environment, add the following parameters to every command call:

```

bin/console --env=prod --no-debug cache:clear

```

You can access the website in production mode via the `/index.php` file in your website root (`public/`) or just `/` path. (on Apache)

####### Test[¶](#test "Permalink to this headline")

Test environment or `test` is used for automated testing. Most of the time you will not access it directly.

To run Sylius console in `test` environment, add the following parameters to every command call:

```

bin/console --env=test cache:clear

```

####### Final Thoughts[¶](#final-thoughts "Permalink to this headline")

You can read more about Symfony environments in [this cookbook article](https://symfony.com/doc/current/cookbook/configuration/environments.html).

* [Introduction to Sylius](index.html#document-book/introduction/introduction)

* [Understanding Environments](index.html#document-book/introduction/environments)

* [Introduction to Sylius](index.html#document-book/introduction/introduction)

* [Understanding Environments](index.html#document-book/introduction/environments)

#### Installation[¶](#installation "Permalink to this headline")

The installation chapter is of course a comprehensive guide to installing Sylius on your machine, but it also provides

a general instruction on upgrading Sylius in your project.

##### Installation[¶](#installation "Permalink to this headline")

The process of installing Sylius together with the requirements to run it efficiently.

###### System Requirements[¶](#system-requirements "Permalink to this headline")

Here you will find the list of system requirements that have to be adhered to be able to use **Sylius**.

First of all have a look at the [requirements for running Symfony](https://symfony.com/doc/current/reference/requirements.html).

Read about the [LAMP stack](https://en.wikipedia.org/wiki/LAMP_(software_bundle)) and the [MAMP stack](https://en.wikipedia.org/wiki/MAMP).

####### Operating Systems[¶](#operating-systems "Permalink to this headline")

The recommended operating systems for running Sylius are the Unix systems - **Linux, MacOS**.

####### Web server and configuration[¶](#web-server-and-configuration "Permalink to this headline")

In the production environment we do recommend using Apache web server ≥ 2.2.

While developing the recommended way to work with your Symfony application is to use PHP’s built-in web server.

[Go there](https://symfony.com/doc/current/cookbook/configuration/web_server_configuration.html) to see the full reference to the web server configuration.

####### PHP required modules and configuration[¶](#php-required-modules-and-configuration "Permalink to this headline")

**PHP version**:

| PHP | ^8.0 |

**PHP extensions**:

| [gd](https://php.net/manual/en/book.fileinfo.php) | No specific configuration |

| [exif](https://php.net/manual/en/book.exif.php) | No specific configuration |

| [fileinfo](https://php.net/manual/en/book.fileinfo.php) | No specific configuration |

| [intl](https://php.net/manual/en/book.intl.php) | No specific configuration |

**PHP configuration settings**:

| memory\_limit | ≥1024M |

| date.timezone | Europe/Warsaw |

Warning

Use your local timezone, for example America/Los\_Angeles or Europe/Berlin. See <https://php.net/manual/en/timezones.php> for the list of all available timezones.

####### Database[¶](#database "Permalink to this headline")

By default, the database connection is pre-configured to work with a following MySQL configuration:

| MySQL | 5.7+, 8.0+ |

Note

You might also use any other RDBMS (like PostgreSQL), but our database migrations support MySQL only.

####### NPM Package Manager[¶](#npm-package-manager "Permalink to this headline")

Sylius Frontend depends on [npm packages](https://docs.npmjs.com/about-npm) for it to run you need to have Node.js installed.

Current minimum supported version of Node.js is:

| Node.js | 14.x |

####### Access rights[¶](#access-rights "Permalink to this headline")

Most of the application folders and files require only read access, but a few folders need also the write access for the Apache/Nginx user:

* `var/cache`

* `var/log`

* `public/media`

You can read how to set these permissions in the [Symfony - setting up permissions](https://symfony.com/doc/current/setup/file_permissions.html) section.

###### Installation[¶](#installation "Permalink to this headline")

The Sylius main application can serve as an end-user app, as well as a foundation

for your custom e-commerce application.

To create your Sylius-based application, first make sure you use PHP 8.0 or higher

and have [Composer](https://packagist.org) installed.

Note

In order to inform you about newest Sylius releases and be aware of shops based on Sylius,

the Core Team uses an internal statistical service called GUS.

The only data that is collected and stored in its database are hostname, user agent, locale,

environment (test, dev or prod), current Sylius version and the date of last contact.

If you do not want your shop to send requests to GUS, please visit [this guide](index.html#document-cookbook/configuration/disabling-admin-notifications)

for further instructions.

####### Initiating A New Sylius Project[¶](#initiating-a-new-sylius-project "Permalink to this headline")

To begin creating your new project, run this command:

```

composer create-project sylius/sylius-standard acme

```

Note

Make sure to use PHP ^8.0. Using an older PHP version will result in installing an older version of Sylius.

This will create a new Symfony project in the `acme` directory. Next, move to the project directory:

```

cd acme

```

Sylius uses environment variables to configure the connection with database and mailer services.

You can look up the default values in `.env` file and customise them by creating `.env.local` with variables you want to override.

For example, if you want to change your database name from the default `sylius\_%kernel.environment%` to `my\_custom\_sylius\_database`,

the contents of that new file should look like the following snippet:

```

DATABASE_URL=mysql://username:password@host/my_custom_sylius_database

```

Warning

Specific Sylius versions may support various Symfony versions. To make sure the correct Symfony version will be

installed (Symfony 6.0 for example) use:

```

composer config extra.symfony.require "^6.0"

composer update

```

Otherwise, you may face the problem of having Symfony components of the wrong version installed.

After everything is in place, run the following command to install Sylius:

```

php bin/console sylius:install

```

Warning

During the `sylius:install` command you will be asked to provide important information, but also its execution ensures

that the default **currency** (USD) and the default **locale** (English - US) are set.

They can be changed later, respectively in the “Configuration > Channels” section of the admin and in the `config/services.yaml` file. If you want

to change these before running the installation command, set the `locale` and `sylius\_installer\_currency` parameters in the `config/services.yaml` file.

From now on all the prices will be stored in the database in USD as integers, and all the products will have to be added with a base american english name translation.

####### Configuring Mailer[¶](#configuring-mailer "Permalink to this headline")

In order to send emails you need to configure Mailer Service. Basically you need to:

1. **Create an account on a mailing service.**

2. **In your** `.env` **file modify the** `MAILER\_URL` **variable.**

```

MAILER_URL=gmail://username:password@local

```

Note

Email delivery is disabled for test, dev and staging environments by default. The prod environment has delivery turned on.

You can learn more about configuring mailer service in [How to configure mailer?](index.html#document-cookbook/emails/mailer)

####### Installing assets[¶](#installing-assets "Permalink to this headline")

In order to see a fully functional frontend you will need to install its assets.

**Sylius** uses [Webpack](https://webpack.js.org/) to build frontend assets using [Yarn](https://yarnpkg.com/lang/en/) as a JavaScript package manager.

Note

If you want to read more, you can read a [chapter of our Book devoted to the Sylius’ frontend](index.html#document-book/frontend/index).

Having Yarn installed, go to your project directory to install the dependencies:

```

yarn install

```

Then build the frontend assets by running:

```

yarn build

```

####### Accessing the Shop[¶](#accessing-the-shop "Permalink to this headline")

We strongly recommend using the Symfony Local Web Server by running the `symfony server:start`

command and then accessing `https://127.0.0.1:8000` in your web browser to see the shop.

Note

Get to know more about using Symfony Local Web Server [in the Symfony server documentation](https://symfony.com/doc/current/setup/symfony_server.html).

If you are using a built-in server check [here](https://symfony.com/doc/current/cookbook/web_server/built_in.html).

You can log to the administrator panel located at `/admin` with the credentials you have provided during the installation process.

####### How to start developing? - Project Structure[¶](#how-to-start-developing-project-structure "Permalink to this headline")

After you have successfully gone through the installation process of **Sylius-Standard** you are probably going to start developing within the framework of Sylius.

In the root directory of your project you will find these important subdirectories:

* `config/` - here you will be adding the yaml configuration files including routing, security, state machines configurations etc.

* `var/log/` - these are the logs of your application

* `var/cache/` - this is the cache of you project

* `src/` - this is where you will be adding all you custom logic in the `App`

* `public/` - there you will be placing assets of your project

Tip

As it was mentioned before we are basing on Symfony, that is why we’ve adopted its approach to architecture. Read more [in the Symfony documentation](https://symfony.com/doc/current/quick_tour/the_architecture.html).

Read also about the [best practices while structuring your project](https://symfony.com/doc/current/best_practices/creating-the-project.html#structuring-the-application).

####### Running asynchronous tasks[¶](#running-asynchronous-tasks "Permalink to this headline")

To enable asynchronous tasks (for example for Catalog Promotions), remember about running messenger consumer in a separate process,

use the command: php bin/console messenger:consume main

For production environments, we suggest usage of more robust solution like Supervisor,

which will ensure that the process is still running even if some failure will occur.

For more information, please visit [Symfony documentation](https://symfony.com/doc/current/messenger.html#supervisor-configuration).

You can learn more about Catalog Promotions [Here](index.html#document-book/products/catalog_promotions)

####### Contributing[¶](#contributing "Permalink to this headline")

If you would like to contribute to Sylius - please go to the [Contribution Guide](index.html#document-book/contributing/index)

###### Installation with Docker[¶](#installation-with-docker "Permalink to this headline")

####### Docker[¶](#docker "Permalink to this headline")

Docker is an open-sourced platform for developing, delivering, and running applications. Docker allows you to separate your

application from your infrastructure, simplifies software delivery. Docker allows you to manage infrastructure in the

same way that applications are managed. Implementing the platform methodology, enables fast code delivery,

testing, and implementation. Docker significantly reduces the delay between writing the code and running it in the production environment.

Note

Make sure you have [Docker](https://docs.docker.com/get-docker/) installed on your local machine.

####### Project Setup[¶](#project-setup "Permalink to this headline")

Clone Sylius-Standard repository or if you are using GitHub you can use the *Use this template* button that will create new repository

with Sylius-Standard content.

```

git clone git@github.com:Sylius/Sylius-Standard.git your_project_name

```

####### Development[¶](#development "Permalink to this headline")

[Sylius Standard](https://github.com/Sylius/Sylius-Standard) comes with the [multi-stage build](https://docs.docker.com/develop/develop-images/multistage-build/).

You can execute it via the `docker compose up -d` command in your favorite terminal. Please note that the speed of building images

and initializing containers depends on your local machine and internet connection - it may take some time. Then enter `localhost` in your browser or execute `open localhost` in your terminal.

```

docker compose up -d

open localhost

```

Tip

[Learn how to deploy Sylius-Standard production ready Docker Compose configuration](index.html#document-cookbook/deployment/docker)

###### Sylius Plus Installation[¶](#sylius-plus-installation "Permalink to this headline")

Sylius Plus is an advanced extension to Sylius applications that adds new features and experience.

As it is a private package it cannot be installed by every Sylius user, but only by those having the license.

####### Installing Sylius Plus as a plugin to a Sylius application[¶](#installing-sylius-plus-as-a-plugin-to-a-sylius-application "Permalink to this headline")

**Important Requirements**

| PHP | ^8.0 |

| sylius/sylius | ~1.11.2 || ~1.12.0 |

| Symfony | ^5.4 || ^6.0 |

**0.** Prepare project:

Tip

If it is a new project you are initiating, then first install Sylius-Standard in **version ^1.11 or ^1.12** according to

[these instructions](index.html#document-book/installation/installation).

If you’re installing Plus package to an existing project, then make sure you’re upgraded to `sylius/sylius ^1.11` or `sylius/sylius ^1.12`.

**1.** Configure access to the private Packagist package in composer by using the Access Token you have been given with your license.

```

composer config --global --auth http-basic.sylius.repo.packagist.com token YOUR_TOKEN

```

**2.** Configure the repository with Sylius Plus for your organisation, require it and then run `composer update`:

```

composer config repositories.plus composer https://sylius.repo.packagist.com/ShortNameOfYourOrganization/

composer require "sylius/plus:^1.0.0@beta" --no-update

composer update --no-scripts

composer sync-recipes

```

**3.** Configure Sylius Plus in `config/bundles.php`:

```

// config/bundles.php

return [

   //...

   Sylius\Plus\SyliusPlusPlugin::class => ['all' => true],

];

```

**4.** Import Sylius Plus configuration files:

```

# config/packages/\_sylius.yaml

imports:

# ...

 - { resource: "@SyliusPlusPlugin/Resources/config/config.yaml" }

```

Warning

Make sure you are importing Sylius Plus configuration as the last resource. You can encounter problems with customization when it is not the last imported resource. Especially when Invoicing Plugin or Refund Plugin is installed.

**5.** Configure Shop, Admin and Admin API routing:

```

# config/routes/sylius\_shop.yaml

# ...

sylius\_plus\_shop:

 resource: "@SyliusPlusPlugin/Resources/config/shop\_routing.yaml"

 prefix: /{\_locale}

 requirements:

 \_locale: ^[a-z]{2}(?:\_[A-Z]{2})?$

```

```

# config/routes/sylius\_admin.yaml:

# ...

sylius\_plus\_admin:

 resource: "@SyliusPlusPlugin/Resources/config/admin\_routing.yaml"

 prefix: /admin

```

Warning

Skip sylius\_plus\_shop if you are not using SyliusShopBundle

and sylius\_plus\_admin if you are not using SyliusAdminBundle.

**6.** Update security providers in `config/packages/security.yaml`:

```

# config/packages/security.yaml

providers:

 # ...

 sylius\_shop\_user\_provider:

 id: Sylius\Plus\CustomerPools\Infrastructure\Provider\UsernameAndCustomerPoolProvider

 sylius\_api\_shop\_user\_provider:

 id: Sylius\Plus\CustomerPools\Infrastructure\Provider\UsernameAndCustomerPoolProvider

```

**7.** Add traits that enhance Sylius models:

* AdminUser

* Channel

* Customer

* Order

* ProductVariant

* Shipment

```

// src/Entity/User/AdminUser.php

<?php

declare(strict\_types=1);

namespace App\Entity\User;

use Doctrine\Common\Collections\ArrayCollection;

use Doctrine\ORM\Mapping as ORM;

use Sylius\Component\Core\Model\AdminUser as BaseAdminUser;

use Sylius\Component\Core\Model\AdminUserInterface;

use Sylius\Plus\ChannelAdmin\Domain\Model\AdminChannelAwareTrait;

use Sylius\Plus\Entity\LastLoginIpAwareInterface;

use Sylius\Plus\Entity\LastLoginIpAwareTrait;

use Sylius\Plus\Rbac\Domain\Model\AdminUserInterface as RbacAdminUserInterface;

use Sylius\Plus\Rbac\Domain\Model\RoleableTrait;

use Sylius\Plus\Rbac\Domain\Model\ToggleablePermissionCheckerTrait;

/\*\*

 \* @ORM\Entity

 \* @ORM\Table(name="sylius\_admin\_user")

 \*/

class AdminUser extends BaseAdminUser implements AdminUserInterface, RbacAdminUserInterface, LastLoginIpAwareInterface

{

    use AdminChannelAwareTrait;

    use LastLoginIpAwareTrait;

    use RoleableTrait;

    use ToggleablePermissionCheckerTrait;

    public function \_\_construct()

    {

        parent::\_\_construct();

        $this->rolesResources = new ArrayCollection();

    }

}

```

```

// src/Entity/Channel/Channel.php

<?php

declare(strict\_types=1);

namespace App\Entity\Channel;

use Doctrine\ORM\Mapping as ORM;

use Sylius\Component\Core\Model\Channel as BaseChannel;

use Sylius\Component\Core\Model\ChannelInterface;

use Sylius\Plus\BusinessUnits\Domain\Model\BusinessUnitAwareTrait;

use Sylius\Plus\BusinessUnits\Domain\Model\ChannelInterface as BusinessUnitsChannelInterface;

use Sylius\Plus\CustomerPools\Domain\Model\ChannelInterface as CustomerPoolsChannelInterface;

use Sylius\Plus\CustomerPools\Domain\Model\CustomerPoolAwareTrait;

use Sylius\Plus\Returns\Domain\Model\ChannelInterface as ReturnsChannelInterface;

use Sylius\Plus\Returns\Domain\Model\ReturnRequestsAllowedAwareTrait;

/\*\*

 \* @ORM\Entity

 \* @ORM\Table(name="sylius\_channel")

 \*/

class Channel extends BaseChannel implements ChannelInterface, ReturnsChannelInterface, BusinessUnitsChannelInterface, CustomerPoolsChannelInterface

{

    use BusinessUnitAwareTrait;

    use CustomerPoolAwareTrait;

    use ReturnRequestsAllowedAwareTrait;

}

```

```

// src/Entity/Customer/Customer.php

<?php

declare(strict\_types=1);

namespace App\Entity\Customer;

use Doctrine\ORM\Mapping as ORM;

use Sylius\Component\Core\Model\Customer as BaseCustomer;

use Sylius\Component\Core\Model\CustomerInterface;

use Sylius\Plus\CustomerPools\Domain\Model\CustomerInterface as CustomerPoolsCustomerInterface;

use Sylius\Plus\CustomerPools\Domain\Model\CustomerPoolAwareTrait;

use Sylius\Plus\Loyalty\Domain\Model\CustomerInterface as LoyaltyCustomerInterface;

use Sylius\Plus\Loyalty\Domain\Model\LoyaltyAwareTrait;

/\*\*

 \* @ORM\Entity

 \* @ORM\Table(name="sylius\_customer")

 \*/

class Customer extends BaseCustomer implements CustomerInterface, CustomerPoolsCustomerInterface, LoyaltyCustomerInterface

{

    use CustomerPoolAwareTrait;

    use LoyaltyAwareTrait;

}

```

```

// src/Entity/Order/Order.php

<?php

declare(strict\_types=1);

namespace App\Entity\Order;

use Doctrine\ORM\Mapping as ORM;

use Sylius\Component\Core\Model\Order as BaseOrder;

use Sylius\Component\Core\Model\OrderInterface;

use Sylius\Plus\Returns\Domain\Model\OrderInterface as ReturnsOrderInterface;

use Sylius\Plus\Returns\Domain\Model\ReturnRequestAwareTrait;

/\*\*

 \* @ORM\Entity

 \* @ORM\Table(name="sylius\_order")

 \*/

class Order extends BaseOrder implements OrderInterface, ReturnsOrderInterface

{

    use ReturnRequestAwareTrait;

}

```

```

// src/Entity/Product/ProductVariant.php

<?php

declare(strict\_types=1);

namespace App\Entity\Product;

use Doctrine\ORM\Mapping as ORM;

use Sylius\Component\Core\Model\ProductVariant as BaseProductVariant;

use Sylius\Component\Core\Model\ProductVariantInterface;

use Sylius\Component\Product\Model\ProductVariantTranslationInterface;

use Sylius\Plus\Inventory\Domain\Model\InventorySourceStocksAwareTrait;

use Sylius\Plus\Inventory\Domain\Model\ProductVariantInterface as InventoryProductVariantInterface;

/\*\*

 \* @ORM\Entity()

 \* @ORM\Table(name="sylius\_product\_variant")

 \*/

class ProductVariant extends BaseProductVariant implements ProductVariantInterface, InventoryProductVariantInterface

{

    use InventorySourceStocksAwareTrait {

        \_\_construct as private initializeProductVariantTrait;

    }

    public function \_\_construct()

    {

        parent::\_\_construct();

        $this->initializeProductVariantTrait();

    }

    protected function createTranslation(): ProductVariantTranslationInterface

    {

        return new ProductVariantTranslation();

    }

}

```

```

// src/Entity/Shipping/Shipment.php

<?php

declare(strict\_types=1);

namespace App\Entity\Shipping;

use Doctrine\ORM\Mapping as ORM;

use Sylius\Component\Core\Model\Shipment as BaseShipment;

use Sylius\Component\Core\Model\ShipmentInterface;

use Sylius\Plus\Inventory\Domain\Model\InventorySourceAwareTrait;

use Sylius\Plus\Inventory\Domain\Model\ShipmentInterface as InventoryShipmentInterface;

/\*\*

 \* @ORM\Entity()

 \* @ORM\Table(name="sylius\_shipment")

 \*/

class Shipment extends BaseShipment implements ShipmentInterface, InventoryShipmentInterface

{

    use InventorySourceAwareTrait;

}

```

**8.** Install wkhtmltopdf binary:

Default configuration assumes enabled PDF file generator. If you don’t want to use that feature change your app configuration:

```

# config/packages/sylius\_plus.yaml

sylius\_plus:

 pdf\_generator:

 enabled: false

```

Warning

Sylius Plus uses both the Sylius Invoicing and Sylius Refund plugins which have their own configuration for disabling PDF Generator.

Check if you have wkhtmltopdf binary.

If not, you can download it [here](https://wkhtmltopdf.org/downloads.html).

By default wkhtmltopdf is installed in `/usr/local/bin/wkhtmltopdf` directory.

Tip

If you not sure if you have already installed wkhtmltopdf and where it is located, write the following command in the terminal:

`which wkhtmltopdf`

In case wkhtmltopdf is not located in `/usr/local/bin/wkhtmltopdf`, add the following snippet at the end of

your application’s `.env` file:

```

###> knplabs/knp-snappy-bundle ###

WKHTMLTOPDF\_PATH=/your-path

###< knplabs/knp-snappy-bundle ###

```

**9.** Update the database using migrations:

```

bin/console doctrine:migrations:migrate

```

**10.** Install Sylius with Sylius Plus fixtures:

```

bin/console sylius:install -s plus

```

Tip

If you want to completely (re)install the application, you can run this command with the no interaction flag `-n`.

```

bin/console sylius:install -s plus -n

```

**11.** Install JS libraries using Yarn:

```

yarn install

yarn build

bin/console assets:install --ansi

```

**12.** Rebuild cache for proper display of all translations:

```

bin/console cache:clear

bin/console cache:warmup

```

**13.** For more details check the installation guides for all plugins installed as dependencies with Sylius Plus.

* [Sylius/InvoicingPlugin](https://github.com/Sylius/InvoicingPlugin/blob/master/README.md#installation)

* [Sylius/RefundPlugin](https://github.com/Sylius/RefundPlugin/blob/master/README.md#installation)

**Phew! That’s all, you can now run the application just like you usually do with Sylius (using Symfony Server for example).**

####### Upgrading Sylius Plus[¶](#upgrading-sylius-plus "Permalink to this headline")

To upgrade Sylius Plus in an existing application, please follow upgrade instructions from

[Sylius/PlusInformationCenter](https://github.com/Sylius/PlusInformationCenter) repository.

[![_images/banner4.png](_images/banner4.png)](https://sylius.com/plus/?utm_source=docs)

###### Upgrading[¶](#upgrading "Permalink to this headline")

Sylius regularly releases new versions according to our [Release Cycle](index.html#document-book/organization/release-cycle).

Each minor release comes with an `UPGRADE.md` file, which is meant to help in the upgrading process.

1. **Update the Sylius library version constraint by modifying the ``composer.json`` file:**

> 

> 

> ```

> {

>  "require": {

>  "sylius/sylius": "^1.7"

>  }

> }

> 

> ```

> 

> 

>

2. **Upgrade dependencies by running a Composer command:**

> 

> 

> ```

> composer update sylius/sylius --with-all-dependencies

> 

> ```

> 

> 

> If this does not help, it is a matter of debugging the conflicting versions and working out how your `composer.json` should look after the upgrade.

> You can check what version of Sylius is installed by running `composer show sylius/sylius` command.

> 

> 

>

3. **Follow the instructions found in the ``UPGRADE.md`` file for a given minor release.**

###### Upgrading Sylius Plus[¶](#upgrading-sylius-plus "Permalink to this headline")

Sylius regularly releases new versions, usually every two weeks.

Each release comes with an `UPGRADE.md` file, which is meant to help in the upgrading process.

1. **Update the Sylius Plus version constraint by modifying the ``composer.json`` file:**

> 

> 

> ```

> {

>  "require": {

>  "sylius/plus": "^1.0.0@beta"

>  }

> }

> 

> ```

> 

> 

>

2. **Upgrade dependencies by running a Composer command:**

> 

> 

> ```

> composer update sylius/plus --with-all-dependencies

> 

> ```

> 

> 

> If this does not help, it is a matter of debugging the conflicting versions and working out how your `composer.json`

> should look after the upgrade.

> You can check what version of Sylius is installed by running `composer show sylius/plus` command.

> 

> 

>

3. **Follow the instructions found in the ``UPGRADE.md`` file for a given minor release.**

Note

As Sylius Plus is a private repository its README files (and CHANGELOG) have been exposed in a separate public

repository which can be found here: <https://github.com/Sylius/PlusInformationCenter>

[![_images/banner4.png](_images/banner4.png)](https://sylius.com/plus/?utm_source=docs)

* [System Requirements](index.html#document-book/installation/requirements)

* [Installation](index.html#document-book/installation/installation)

* [Installation with Docker](index.html#document-book/installation/installation_with_docker)

* [Sylius Plus Installation](index.html#document-book/installation/sylius_plus_installation)

* [Upgrading](index.html#document-book/installation/upgrading)

* [Upgrading Sylius Plus](index.html#document-book/installation/sylius_plus_upgrading)

* [System Requirements](index.html#document-book/installation/requirements)

* [Installation](index.html#document-book/installation/installation)

* [Installation with Docker](index.html#document-book/installation/installation_with_docker)

* [Sylius Plus Installation](index.html#document-book/installation/sylius_plus_installation)

* [Upgrading](index.html#document-book/installation/upgrading)

* [Upgrading Sylius Plus](index.html#document-book/installation/sylius_plus_upgrading)

#### Architecture[¶](#architecture "Permalink to this headline")

The key to understanding principles of Sylius internal organization. Here you will learn about the Resource layer,

state machines, events and general non e-commerce concepts adopted in the platform, like E-mails or Fixtures.

##### Architecture[¶](#architecture "Permalink to this headline")

Before we dive separately into every Sylius concept, you need to have an overview of how our main application is structured.

In this chapter we will sketch this architecture and our basic, cornerstone concepts, but also some supportive approaches,

that you need to notice.

###### Architecture Overview[¶](#architecture-overview "Permalink to this headline")

Before we dive separately into every Sylius concept, you need to have an overview of how our main application is structured.

####### Architectural drivers[¶](#architectural-drivers "Permalink to this headline")

All architectural decisions need to be backed by a valid reason. The fundamental signposts we use to take such choices, are explained

in [Architectural Drivers section](index.html#document-book/architecture/drivers).

Specific decisions we make during the development are often explained using Architectural Decision Records. They’re stored

in the [main Sylius repository](https://github.com/Sylius/Sylius/tree/1.11/adr) for better visibility.

####### Architecture[¶](#architecture "Permalink to this headline")

On the below image you can see the symbolic representation of Sylius architecture.

[![_images/architecture_overview.png](_images/architecture_overview.png)](_images/architecture_overview.png)

  

Keep on reading this chapter to learn more about each of its parts: Shop, Admin, API, Core, Components and Bundles.

####### Division into Components, Bundles, Platform[¶](#division-into-components-bundles-platform "Permalink to this headline")

You already know that Sylius is built from components and Symfony bundles, which are integration layers with the framework.

All bundles share the same conventions for naming things and the way of data persistence.

######## Components[¶](#components "Permalink to this headline")

Every single component of Sylius can be used standalone. Taking the `Taxation` component as an example,

its only responsibility is to calculate taxes, it does not matter whether these will be taxes for products or anything else, it is fully decoupled.

In order to let the Taxation component operate on your objects you need to have them implementing the `TaxableInterface`.

Since then they can have taxes calculated.

Such approach is true for every component of Sylius.

Besides components that are strictly connected to the e-commerce needs, we have plenty of components that are more general. For instance Attribute, Mailer, Locale etc.

All the components are packages available via [Packagist](https://packagist.org/).

[Read more about the Components](index.html#document-components_and_bundles/components/index).

######## Bundles[¶](#bundles "Permalink to this headline")

These are the Symfony Bundles - therefore if you are a Symfony Developer, and you would like to use the Taxation component in your system,

but you do not want to spend time on configuring forms or services in the container. You can include the `TaxationBundle` in your application

with minimal or even no configuration to have access to all the services, models, configure tax rates, tax categories and use that for any taxes you will need.

[Read more about the Bundles](index.html#document-components_and_bundles/bundles/index).

######## Platform[¶](#platform "Permalink to this headline")

This is a fullstack Symfony Application, based on Symfony Standard. Sylius Platform gives you the classic, quite feature rich webshop.

Before you start using Sylius you will need to decide whether you will need a full platform with all the features we provide, or maybe you will use decoupled bundles and components

to build something very custom, maybe smaller, with different features.

But of course the platform itself is highly flexible and can be easily customized to meet all business requirements you may have.

####### Division into Core, Admin, Shop, Api[¶](#division-into-core-admin-shop-api "Permalink to this headline")

######## Core[¶](#core "Permalink to this headline")

The Core is another component that integrates all the other components. This is the place where for example the `ProductVariant` finally learns that it has a `TaxCategory`.

The Core component is where the `ProductVariant` implements the `TaxableInterface` and other interfaces that are useful for its operation.

Sylius has here a fully integrated concept of everything that is needed to run a webshop.

To get to know more about concepts applied in Sylius - keep on reading [The Book](index.html#document-book/index).

######## Admin[¶](#admin "Permalink to this headline")

In every system with the security layer the functionalities of system administration need to be restricted to only some users with a certain role - Administrator.

This is the responsibility of our `AdminBundle` although if you do not need it, you can turn it off. Views have been built using the [SemanticUI](https://semantic-ui.com/).

######## Shop[¶](#shop "Permalink to this headline")

Our `ShopBundle` is basically a standard B2C interface for everything that happens in the system.

It is made mainly of yaml configurations and templates.

Also here views have been built using the [SemanticUI](https://semantic-ui.com/).

######## API[¶](#api "Permalink to this headline")

When we created our API based on API Platform framework we have done everything to offer API as easy as possible to use by developer.

The most important features of our API:

> 

> * All operations are grouped by shop and admin context (two prefixes)

> * Developers can enable or disable entire API by changing single parameter (check [this](index.html#document-book/api/introduction) chapter)

> * We create all endpoints implementing the REST principles and we are using http verbs (POST, GET, PUT, PATCH, DELETE)

> * Returned responses contain minimal information (developer should extend serialization if need more data)

> * Entire business logic is separated from API - if it necessary we dispatch command instead mixing API logic with business logic

> 

> 

> 

###### Architectural Drivers[¶](#architectural-drivers "Permalink to this headline")

Architectural Drivers are the key factors that influence all the decisions we take during the application development.

Historically, a lot of them were taken unconsciously, but, happily, resulted in good decisions that we can undoubtedly justify

today. All of them have a significant influence on the Sylius as an application - they can and should be used to guide us

during the development, to take the best decision for the product.

####### Technical constraints[¶](#technical-constraints "Permalink to this headline")

######## Programming language[¶](#programming-language "Permalink to this headline")

**PHP**

Due to the decision to base Sylius on the **Symfony** framework (see below), **PHP** was the only possible option as

a programming language. Nevertheless, a good decision! This language is dynamically developing for the last few years and

still power up most of the websites and application in the world wide web.

Currently supported PHP versions can be seen in [this chapter](https://docs.sylius.com/en/1.11/book/installation/requirements.html#php-required-modules-and-configuration).

######## Main frameworks and libraries[¶](#main-frameworks-and-libraries "Permalink to this headline")

**Fullstack Symfony**

[![_images/symfonyfs.png](_images/symfonyfs.png)](_images/symfonyfs.png)

Sylius is based on Symfony, which is a leading PHP framework to create web applications. Using Symfony allows

developers to work better and faster by providing them with certainty of developing an application that is fully compatible

with the business rules, that is structured, maintainable and upgradable. Also it allows to save time by providing generic re-usable modules.

[Learn more about Symfony](https://symfony.com/what-is-symfony).

**Doctrine**

![_images/doctrine.png](_images/doctrine.png)

Sylius, by default, uses the Doctrine ORM for managing all entities. Doctrine is a family of PHP libraries focused on providing data persistence layer.

The most important are the object-relational mapper (ORM) and the database abstraction layer (DBAL).

One of Doctrine’s key features is the possibility to write database queries in Doctrine Query Language (DQL) - an object-oriented dialect of SQL.

For deeper understanding of how Doctrine works, please refer to the [excellent documentation on their official website](http://doctrine-orm.readthedocs.org/en/latest/).

**Twig**

[![_images/twig.png](_images/twig.png)](_images/twig.png)

Twig is a modern template engine for PHP that is really fast, secure and flexible. Twig is being used by Symfony.

To read more about Twig, [go here](http://twig.sensiolabs.org/).

**API Platform**

[![_images/api_platform.png](_images/api_platform.png)](_images/api_platform.png)

API Platform is a modern solution for developing high quality APIs. API Platform works by default with Symfony and depends on its components.

**Third Party Libraries**

Sylius uses a lot of libraries for various tasks:

* [Payum](https://github.com/Payum/Payum) for payments

* [KnpMenu](https://symfony.com/doc/current/bundles/KnpMenuBundle/index.html) - for shop and admin menus

* [Flysystem](https://github.com/thephpleague/flysystem) for filesystem abstraction (store images locally, Amazon S3 or external server)

* [Imagine](https://github.com/liip/LiipImagineBundle) for images processing, generating thumbnails and cropping

* [Pagerfanta](https://github.com/whiteoctober/Pagerfanta) for pagination

* [Winzou State Machine](https://github.com/winzou/StateMachineBundle) - for the state machines handling

####### Functional requirements[¶](#functional-requirements "Permalink to this headline")

All of the functionality provided by default with Sylius is described as user stories using Behat scenarios. Take a look

[here](https://github.com/Sylius/Sylius/tree/1.11/features) to browse them.

####### Quality attributes[¶](#quality-attributes "Permalink to this headline")

Sylius focuses a lot on the software quality since its very beginning. We use test-driven methodologies like

[TDD and BDD](index.html#document-bdd/index) to ensure reliability of the provided functionalities. Moreover, as Sylius is not the

end-project (it rarely used in a *vanilla* version), but serves as the base for the actual applications, it’s crucial

to take care about its ability to fulfill such a role.

######## Extendability[¶](#extendability "Permalink to this headline")

Sylius offers a lot of standard e-commerce features, that could and should be used as a base to introduce more advanced

and business-specific functionalities.

**Question to be asked:** is it possible to easily add new, more advanced functionality to the module/class/service I implement?

**Examples:**

* promotions [actions](https://github.com/Sylius/Sylius/blob/1.11/src/Sylius/Bundle/CoreBundle/Resources/config/services/promotion.xml#L65) and

[rules](https://github.com/Sylius/Sylius/blob/1.11/src/Sylius/Bundle/PromotionBundle/Resources/config/services.xml#L39) registered with tags

* state machine [callbacks](https://github.com/Sylius/Sylius/blob/1.11/src/Sylius/Bundle/CoreBundle/Resources/config/app/state_machine/sylius_order.yml#L22)

* resource [events](https://github.com/Sylius/SyliusResourceBundle/blob/1.10/src/Bundle/Controller/ResourceController.php#L175)

######## Customizability[¶](#customizability "Permalink to this headline")

Seemingly similar to the previous one, but essentially different. Focuses on making it possible to override the standard functionality

with a different one, while still keeping the whole process working. The most important (but not the only) steps to reach it is

using interfaces with small, focused and granular services.

Customizability should be kept on all levels - from the single service, to the whole module/component.

**Question to be asked:** is it possible to replace this functionality and do not break the whole process?

**Examples:**

* service for [calculating variant price](https://github.com/Sylius/Sylius/blob/1.11/src/Sylius/Component/Core/Calculator/ProductVariantPriceCalculator.php) that

can be overridden to provide more advances pricing strategies

* [resource configuration](https://github.com/Sylius/SyliusResourceBundle/blob/1.10/docs/reference.md#configuration-reference), that gives possibility to configure

any service as resource-specific controller/factory/repository etc.

######## Testability[¶](#testability "Permalink to this headline")

As mentioned before, Sylius embraces test-driven methodologies from its very beginning. Therefore, every class (with some exceptions) should be described with

unit tests, every functionality should be designed through Behat acceptance scenarios. Highly tested code is crucial to ensure other, also important

driver, which is **reliability** of the software.

**Question to be asked:** is my module/class easy to be tested, to protect it from the potential regression?

As history has shown, if something is difficult to be tested, there is a huge chance it’s not designed or written properly.

####### Sources and inspirations[¶](#sources-and-inspirations "Permalink to this headline")

This chapter was created inspired by the following sources:

* [Architectural Drivers in Modern Software Architecture](https://medium.com/@janerikfra/architectural-drivers-in-modern-software-architecture-cb7a42527bf2) by Erik Franzen

* [Modular Monolith: Architectural Drivers](http://www.kamilgrzybek.com/design/modular-monolith-architectural-drivers/) by Kamil Grzybek

* [PL] [Droga Nowoczesnego Architekta](https://droganowoczesnegoarchitekta.pl/) - online course for software architects and engineers

###### Resource Layer[¶](#resource-layer "Permalink to this headline")

We created an abstraction on top of Doctrine, in order to have a consistent and flexible way to manage all the resources. By “resource” we understand every model in the application.

Simplest examples of Sylius resources are “product”, “order”, “tax\_category”, “promotion”, “user”, “shipping\_method” and so on…

There are two types of resources in **Sylius**:

* registered by default - their names begin with `sylius.\*` for example: `sylius.product`

* custom resources, from your application which have a separate convention. We place them under `sylius\_resource:` `resource\_name:` in the `config.yml`. For these we recommend using the naming convention of `app.\*` for instance `app.my\_entity`.

Sylius resource management system lives in the **SyliusResourceBundle** and can be used in any Symfony project.

####### Services[¶](#services "Permalink to this headline")

For every resource you have four essential services available:

* Factory

* Manager

* Repository

* Controller

Let us take the “product” resource as an example. By default, it is represented by an object of a class that implements the `Sylius\Component\Core\Model\ProductInterface`.

####### Factory[¶](#factory "Permalink to this headline")

The factory service gives you an ability to create new default objects. It can be accessed via the *sylius.factory.product* id (for the Product resource of course).

```

<?php

public function myAction()

{

    $factory = $this->container->get('sylius.factory.product');

    /\*\* @var ProductInterface $product \*\*/

    $product = $factory->createNew();

}

```

Note

Creating resources via this factory method makes the code more testable, and allows you to change the model class easily.

####### Manager[¶](#manager "Permalink to this headline")

The manager service is just an alias to appropriate Doctrine’s [ObjectManager](https://www.doctrine-project.org/projects/doctrine-persistence/en/latest/reference/index.html#object-manager) and can be accessed via the *sylius.manager.product* id.

API is exactly the same and you are probably already familiar with it:

```

<?php

public function myAction()

{

    $manager = $this->container->get('sylius.manager.product');

    // Assuming that the $product1 exists in the database we can perform such operations:

    $manager->remove($product1);

    // If we have created the $product2 using a factory, we can persist it in the database.

    $manager->persist($product2);

    // Before performing a flush, the changes we have made, are not saved. There is only the $product1 in the database.

    $manager->flush(); // Saves changes in the database.

    //After these operations we have only $product2 in the database. The $product1 has been removed.

}

```

####### Repository[¶](#repository "Permalink to this headline")

Repository is defined as a service for every resource and shares the API with standard Doctrine *ObjectRepository*. It contains two additional methods for creating a new object instance and a paginator provider.

The repository service is available via the *sylius.repository.product* id and can be used like all the repositories you have seen before.

```

<?php

public function myAction()

{

    $repository = $this->container->get('sylius.repository.product');

    $product = $repository->find(4); // Get product with id 4, returns null if not found.

    $product = $repository->findOneBy(['slug' => 'my-super-product']); // Get one product by defined criteria.

    $products = $repository->findAll(); // Load all the products!

    $products = $repository->findBy(['special' => true]); // Find products matching some custom criteria.

}

```

Tip

An important feature of the repositories are the `add($resource)` and `remove($resource)` methods,

which take a resource as an argument and perform the adding/removing action with a flush inside.

These actions can be used when the performance of operations is negligible. If you want

to perform operations on large sets of data we recommend using the manager instead.

Every Sylius repository supports paginating resources. To create a [Pagerfanta instance](https://github.com/whiteoctober/Pagerfanta) use the `createPaginator` method:

```

<?php

public function myAction(Request $request)

{

    $repository = $this->container->get('sylius.repository.product');

    $products = $repository->createPaginator();

    $products->setMaxPerPage(3);

    $products->setCurrentPage($request->query->get('page', 1));

    // Now you can return products to template and iterate over it to get products from current page.

}

```

Paginator can be created for a specific criteria and with desired sorting:

```

<?php

public function myAction(Request $request)

{

    $repository = $this->container->get('sylius.repository.product');

    $products = $repository->createPaginator(['foo' => true], ['createdAt' => 'desc']);

    $products->setMaxPerPage(3);

    $products->setCurrentPage($request->query->get('page', 1));

}

```

####### Controller[¶](#controller "Permalink to this headline")

This service is the most important for every resource and provides a format agnostic CRUD controller with the following actions:

* [GET] showAction() for getting a single resource

* [GET] indexAction() for retrieving a collection of resources

* [GET/POST] createAction() for creating new resource

* [GET/PUT] updateAction() for updating an existing resource

* [DELETE] deleteAction() for removing an existing resource

As you see, these actions match the common operations in any REST API and yes, they are format agnostic.

This means, all Sylius controllers can serve HTML, JSON or XML, depending on what you request.

Additionally, all these actions are very flexible and allow you to use different templates, forms, repository methods per route.

The bundle is very powerful and allows you to register your own resources as well. To give you some idea of what is possible, here are some examples!

Displaying a resource with a custom template and repository methods:

```

# config/routes.yaml

app\_product\_show:

 path: /products/{slug}

 methods: [GET]

 defaults:

 \_controller: sylius.controller.product:showAction

 \_sylius:

 template: AppStoreBundle:Product:show.html.twig # Use a custom template.

 repository:

 method: findForStore # Use a custom repository method.

 arguments: [$slug] # Pass the slug from the url to the repository.

```

Creating a product using custom form and a redirection method:

```

# config/routes.yaml

app\_product\_create:

 path: /my-stores/{store}/products/new

 methods: [GET, POST]

 defaults:

 \_controller: sylius.controller.product:createAction

 \_sylius:

 form: AppStoreBundle/Form/Type/CustomFormType # Use this form type!

 template: AppStoreBundle:Product:create.html.twig # Use a custom template.

 factory:

 method: createForStore # Use a custom factory method to create a product.

 arguments: [$store] # Pass the store name from the url.

 redirect:

 route: app\_product\_index # Redirect the user to their products.

 parameters: [$store]

```

All other methods have the same level of flexibility and are documented in the [SyliusResourceBundle](https://github.com/Sylius/SyliusResourceBundle/blob/master/docs/index.md).

###### State Machine[¶](#state-machine "Permalink to this headline")

In **Sylius** we are using the [Winzou StateMachine Bundle](https://github.com/winzou/StateMachineBundle).

State Machines are an approach to handling changes occurring in the system frequently, that is extremely flexible and very well organised.

Every state machine will have a predefined set of states, that will be stored on an entity that is being controlled by it.

These states will have a set of defined transitions between them, and a set of callbacks - a kind of events, that will happen on defined transitions.

####### States[¶](#states "Permalink to this headline")

States of a state machine are defined as constants on the model of an entity that the state machine is controlling.

How to configure states? Let’s see on the example from **Checkout** state machine.

```

# CoreBundle/Resources/config/app/state\_machine/sylius\_order\_checkout.yml

winzou\_state\_machine:

 sylius\_order\_checkout:

 # list of all possible states:

 states:

 cart: ~

 addressed: ~

 shipping\_selected: ~

 payment\_selected: ~

 completed: ~

```

####### Transitions[¶](#transitions "Permalink to this headline")

On the graph it would be the connection between two states, defining that you can move from one state to another subsequently.

How to configure transitions? Let’s see on the example of our **Checkout** state machine.

Having states configured we can have a transition between the `cart` state to the `addressed` state.

```

# CoreBundle/Resources/config/app/state\_machine/sylius\_order\_checkout.yml

winzou\_state\_machine:

 sylius\_order\_checkout:

 transitions:

 address:

 from: [cart, addressed, shipping\_selected, payment\_selected] # here you specify which state is the initial

 to: addressed # there you specify which state is final for that transition

```

####### Callbacks[¶](#callbacks "Permalink to this headline")

Callbacks are used to execute some code before or after applying transitions. Winzou StateMachineBundle adds the ability to use Symfony services in the callbacks.

How to configure callbacks?

Having a configured transition, you can attach a callback to it either before or after the transition. Callback is simply a method of a service you want to be executed.

```

# CoreBundle/Resources/config/app/state\_machine/sylius\_order\_checkout.yml

winzou\_state\_machine:

 sylius\_order\_checkout:

 callbacks:

 # callbacks may be called before or after specified transitions, in the checkout state machine we've got callbacks only after transitions

 after:

 sylius\_process\_cart:

 on: ["address", "select\_shipping", "select\_payment"]

 do: ["@sylius.order\_processing.order\_processor", "process"]

 args: ["object"]

```

####### Configuration[¶](#configuration "Permalink to this headline")

In order to use a state machine, you have to define a graph beforehand.

A graph is a definition of states, transitions and optionally callbacks - all attached on an object from your domain.

Multiple graphs may be attached to the same object.

In **Sylius** the best example of a state machine is the one from checkout. It has five states available:

`cart`, `addressed`, `shipping\_selected`, `payment\_selected` and `completed` - which can be achieved by applying some transitions to the entity.

For example, when selecting a shipping method during the shipping step of checkout we should apply the `select\_shipping` transition, and after that the state

would become `shipping\_selected`.

```

# CoreBundle/Resources/config/app/state\_machine/sylius\_order\_checkout.yml

winzou\_state\_machine:

 sylius\_order\_checkout:

 class: "%sylius.model.order.class%" # class of the domain object - in our case Order

 property\_path: checkoutState

 graph: sylius\_order\_checkout

 state\_machine\_class: "%sylius.state\_machine.class%"

 # list of all possible states:

 states:

 cart: ~

 addressed: ~

 shipping\_selected: ~

 payment\_selected: ~

 completed: ~

 # list of all possible transitions:

 transitions:

 address:

 from: [cart, addressed, shipping\_selected, payment\_selected] # here you specify which state is the initial

 to: addressed # there you specify which state is final for that transition

 select\_shipping:

 from: [addressed, shipping\_selected, payment\_selected]

 to: shipping\_selected

 select\_payment:

 from: [payment\_selected, shipping\_selected]

 to: payment\_selected

 complete:

 from: [payment\_selected]

 to: completed

 # list of all callbacks:

 callbacks:

 # callbacks may be called before or after specified transitions, in the checkout state machine we've got callbacks only after transitions

 after:

 sylius\_process\_cart:

 on: ["address", "select\_shipping", "select\_payment"]

 do: ["@sylius.order\_processing.order\_processor", "process"]

 args: ["object"]

 sylius\_create\_order:

 on: ["complete"]

 do: ["@sm.callback.cascade\_transition", "apply"]

 args: ["object", "event", "'create'", "'sylius\_order'"]

 sylius\_hold\_inventory:

 on: ["complete"]

 do: ["@sylius.inventory.order\_inventory\_operator", "hold"]

 args: ["object"]

 sylius\_assign\_token:

 on: ["complete"]

 do: ["@sylius.unique\_id\_based\_order\_token\_assigner", "assignTokenValueIfNotSet"]

 args: ["object"]

```

####### Learn more[¶](#learn-more "Permalink to this headline")

* [Winzou StateMachine Bundle](https://github.com/winzou/StateMachineBundle)

* [Customization guide: State machines](index.html#document-customization/state_machine)

###### Translations[¶](#translations "Permalink to this headline")

Sylius uses the approach of personal translations - where each entity is bound with a translation entity, that has it’s

own table (instead of keeping all translations in one table for the whole system).

This results in having the `ProductTranslation` class and `sylius\_product\_translation` table for the `Product` entity.

The logic of handling translations in Sylius is in the **ResourceBundle**

The fields of an entity that are meant to be translatable are saved on the translation entity, only their getters and setters

are also on the original model.

Let’s see an example:

Assuming that we would like to have a translatable model of a `Supplier`, we need a Supplier class and a SupplierTranslation class.

```

<?php

namespace App\Entity;

use Sylius\Component\Resource\Model\AbstractTranslation;

class SupplierTranslation extends AbstractTranslation

{

    /\*\*

 \* @var string

 \*/

    protected $name;

    /\*\*

 \* @return string

 \*/

    public function getName()

    {

        return $this->name;

    }

    /\*\*

 \* @param string $name

 \*/

    public function setName($name)

    {

        $this->name = $name;

    }

}

```

The actual entity has access to its translation by using the `TranslatableTrait` which provides the `getTranslation()` method.

Warning

Remember that the **Translations collection** of the entity

(from the TranslatableTrait) has to be initialized in the constructor!

```

<?php

namespace App\Entity;

use Sylius\Component\Resource\Model\TranslatableInterface;

use Sylius\Component\Resource\Model\TranslatableTrait;

class Supplier implements TranslatableInterface

{

    use TranslatableTrait {

        \_\_construct as private initializeTranslationsCollection;

    }

    public function \_\_construct()

    {

        $this->initializeTranslationsCollection();

    }

    /\*\*

 \* @return string

 \*/

    public function getName()

    {

        return $this->getTranslation()->getName();

    }

    /\*\*

 \* @param string $name

 \*/

    public function setName($name)

    {

        $this->getTranslation()->setName($name);

    }

}

```

####### Fallback Translations[¶](#fallback-translations "Permalink to this headline")

The `getTranslation()` method gets a translation for the current locale, while we are in the shop, but we can also manually

impose the locale - `getTranslation('pl\_PL')` will return a polish translation **if there is a translation in this locale**.

But when the translation for the chosen locale is unavailable, instead the translation for the **fallback locale**

(the one that was either set in `config/services.yaml` or using the `setFallbackLocale()` method from the TranslatableTrait on the entity) is used.

####### How to add a new translation programmatically?[¶](#how-to-add-a-new-translation-programmatically "Permalink to this headline")

You can programmatically add a translation to any of the translatable resources in Sylius.

Let’s see how to do it on the example of a ProductTranslation.

```

// Find a product to add a translation to it

/\*\* @var ProductInterface $product \*/

$product = $this->container->get('sylius.repository.product')->findOneBy(['code' => 'radiohead-mug-code']);

// Create a new translation of product, give it a translated name and slug in the chosen locale

/\*\* @var ProductTranslation $translation \*/

$translation = new ProductTranslation();

$translation->setLocale('pl\_PL');

$translation->setName('Kubek Radiohead');

$translation->setSlug('kubek-radiohead');

// Add the translation to your product

$product->addTranslation($translation);

// Remember to save the product after adding the translation

$this->container->get('sylius.manager.product')->flush();

```

####### Learn more[¶](#learn-more "Permalink to this headline")

* [Resource - translations documentation](https://github.com/Sylius/SyliusResourceBundle/blob/master/docs/index.md)

* [Locales - concept documentation](index.html#document-book/configuration/locales)

###### E-Mails[¶](#e-mails "Permalink to this headline")

Sylius is sending various e-mails and this chapter is a reference about all of them. Continue reading to learn what e-mails are sent, when and how to customize the templates.

To understand how e-mail sending works internally, please refer to [SyliusMailerBundle documentation](https://github.com/Sylius/SyliusMailerBundle/blob/master/docs/index.md).

And to learn more about mailer services configuration, read [the dedicated cookbook](index.html#document-cookbook/emails/mailer).

####### User Confirmation[¶](#user-confirmation "Permalink to this headline")

Every time a customer registers via the registration form, a user registration e-mail is sent to them.

**Code**: `user\_registration`

**The default template**: `@SyliusShop/Email/userRegistration.html.twig`

You also have the following parameters available:

* `user`: Instance of the user model

* `channel`: Currently used channel

* `localeCode`: Currently used locale code

####### Email Verification[¶](#email-verification "Permalink to this headline")

When a customer registers via the registration form, besides the User Confirmation an Email Verification is sent.

**Code**: `verification\_token`

**The default template**: `@SyliusShop/Email/verification.html.twig`

You also have the following parameters available:

* `user`: Instance of the user model

* `channel`: Currently used channel

* `localeCode`: Currently used locale code

####### Password Reset[¶](#password-reset "Permalink to this headline")

This e-mail is used when the user requests to reset their password in the login form.

**Code**: `reset\_password\_token`

**The default template**: `@SyliusShop/Email/passwordReset.html.twig`

You also have the following parameters available:

* `user`: Instance of the user model

* `channel`: Currently used channel

* `localeCode`: Currently used locale code

####### Order Confirmation[¶](#order-confirmation "Permalink to this headline")

This e-mail is sent when order is placed.

**Code**: `order\_confirmation`

**The default template**: `@SyliusShop/Email/orderConfirmation.html.twig`

You also have the following parameters available:

* `order`: Instance of the order, with all its data

* `channel`: Channel in which an order was placed

* `localeCode`: Locale code in which an order was placed

####### Shipment Confirmation[¶](#shipment-confirmation "Permalink to this headline")

This e-mail is sent when the order’s shipping process has started.

**Code**: `shipment\_confirmation`

**The default template**: `@SyliusAdmin/Email/shipmentConfirmation.html.twig`

You have the following parameters available:

* `shipment`: Shipment instance

* `order`: Instance of the order, with all its data

* `channel`: Channel in which an order was placed

* `localeCode`: Locale code in which an order was placed

####### Contact Request[¶](#contact-request "Permalink to this headline")

This e-mail is sent when a customer validates contact form.

**Code**: `contact\_request`

**The default template**: `@SyliusShop/Email/contactRequest.html.twig`

You have the following parameters available:

* `data`: An array of submitted data from form

* `channel`: Channel in which an order was placed

* `localeCode`: Locale code in which an order was placed

####### Return Requests Emails[¶](#return-requests-emails "Permalink to this headline")

Hint

What are Return Requests? [Check here](index.html#document-book/orders/returns)!

######## Return Request Confirmation[¶](#return-request-confirmation "Permalink to this headline")

This email is sent after return request has been created by a customer.

**Code**: `sylius\_plus\_return\_request\_confirmation`

**The default template**:

`@SyliusPlusPlugin/Returns/Infrastructure`

`/Resources/views/Emails/returnRequestConfirmation.html.twig`

Parameters:

* `order` - for which the return request has been created

######## Return Request Acceptation[¶](#return-request-acceptation "Permalink to this headline")

This email is sent when the administrator accepts a return request.

**Code**: `sylius\_plus\_return\_request\_accepted`

**The default template**:

`@SyliusPlusPlugin/Returns/Infrastructure`

`/Resources/views/Emails/returnRequestAcceptedNotification.html.twig`

Parameters:

* `returnRequest` which has been accepted

* `order` of the accepted return request

######## Return Request Rejection[¶](#return-request-rejection "Permalink to this headline")

This email is sent when the administrator rejects a return request.

**Code**: `sylius\_plus\_return\_request\_rejected`

**The default template**:

`@SyliusPlusPlugin/Returns/Infrastructure`

`/Resources/views/Emails/returnRequestRejectedNotification.html.twig`

Parameters:

* `returnRequest` which has been rejected

* `order` of the rejected return request

######## Return Request Resolution Change[¶](#return-request-resolution-change "Permalink to this headline")

This email is sent when the administrator changes return request’s resolution proposed by a customer.

**Code**: `sylius\_plus\_return\_request\_resolution\_changed`

**The default template**:

`@SyliusPlusPlugin/Returns/Infrastructure`

`/Resources/views/Emails/returnRequestResolutionChangedNotification.html.twig`

Parameters:

* `returnRequest` whose resolution has been changed

* `order` of the modified return request

######## Return Request: Repaired Items Sent[¶](#return-request-repaired-items-sent "Permalink to this headline")

This email is sent when the administrator marks that a return request’s repaired items have been sent back to the Customer.

**Code**: `sylius\_plus\_return\_request\_repaired\_items\_sent`

**The default template**:

`@SyliusPlusPlugin/Returns/Infrastructure`

`/Resources/views/Emails/returnRequestRepairedItemsSentNotification.html.twig`

Parameters:

* `returnRequest` of which the items were sent

* `order` of the return request

[![_images/banner.png](_images/banner.png)](https://sylius.com/plus/?utm_source=docs)

####### How to send an Email programmatically?[¶](#how-to-send-an-email-programmatically "Permalink to this headline")

For sending emails **Sylius** is using a dedicated service - **Sender**. Additionally we have **EmailManagers**

for Order Confirmation([OrderEmailManager](https://github.com/Sylius/Sylius/blob/master/src/Sylius/Bundle/ShopBundle/EmailManager/OrderEmailManager.php))

and for Shipment Confirmation([ShipmentEmailManager](https://github.com/Sylius/Sylius/blob/master/src/Sylius/Bundle/AdminBundle/EmailManager/ShipmentEmailManager.php)).

Tip

While using **Sender** you have the available emails of Sylius available under constants in:

* [Core - Emails](https://github.com/Sylius/Sylius/blob/master/src/Sylius/Bundle/CoreBundle/Mailer/Emails.php)

* [User - Emails](https://github.com/Sylius/Sylius/blob/master/src/Sylius/Bundle/UserBundle/Mailer/Emails.php)

Example using **Sender**:

```

/\*\* @var SenderInterface $sender \*/

$sender = $this->container->get('sylius.email\_sender');

$sender->send(\Sylius\Bundle\UserBundle\Mailer\Emails::EMAIL\_VERIFICATION\_TOKEN, ['sylius@example.com'], ['user' => $user, 'channel' => $channel, 'localeCode' => $localeCode]);

```

Example using **EmailManager**:

```

/\*\* @var OrderEmailManagerInterface $sender \*/

$orderEmailManager = $this->container->get('sylius.email\_manager.order');

$orderEmailManager->sendConfirmationEmail($order);

```

####### Learn more[¶](#learn-more "Permalink to this headline")

* [Mailer - Documentation](https://github.com/Sylius/SyliusMailerBundle/blob/master/docs/index.md)

###### Contact[¶](#contact "Permalink to this headline")

The functionality of contacting the shop support/admin is in Sylius very basic. Each **Channel** of your shop may have

a `contactEmail` configured on it. This will be the email address to support.

####### Contact form[¶](#contact-form "Permalink to this headline")

The contact form can be found on the `/contact` route.

Note

When the `contactEmail` is not configured on the channel, the customer will see the following flash message:

![_images/contact_request_error.png](_images/contact_request_error.png)

The form itself has only two fields `email` (which will be filled automatically for the logged in users) and `message`.

####### ContactEmailManager[¶](#contactemailmanager "Permalink to this headline")

The **ContactEmailManager** service is responsible for the sending of a contact request email.

It can be found under the `sylius.email\_manager.contact` service id.

####### ContactController[¶](#contactcontroller "Permalink to this headline")

The controller responsible for the request action handling is the **ContactController**.

It has the `sylius.controller.shop.contact` service id.

####### Configuration[¶](#configuration "Permalink to this headline")

The routing for contact can be found in the `Sylius/Bundle/ShopBundle/Resources/config/routing/contact.yml` file.

By overriding that routing you will be able to customize **redirect url, error flash, success flash, form and its template**.

You can also change the template of the email that is being sent by simply overriding it

in your project in the `templates/bundles/SyliusShopBundle/Email/contactRequest.html.twig` file.

####### Learn more[¶](#learn-more "Permalink to this headline")

* [Emails - Documentation](index.html#document-book/architecture/emails)

###### Fixtures[¶](#fixtures "Permalink to this headline")

Fixtures are used mainly for testing, but also for having your shop in a certain state, having defined data

- they ensure that there is a fixed environment in which your application is working.

Note

The way Fixtures are designed in Sylius is well described in the [FixturesBundle documentation](https://github.com/Sylius/SyliusFixturesBundle/blob/master/docs/index.md).

####### What are the available fixtures in Sylius?[¶](#what-are-the-available-fixtures-in-sylius "Permalink to this headline")

To check what fixtures are defined in Sylius run:

```

php bin/console sylius:fixtures:list

```

####### How to load Sylius fixtures?[¶](#how-to-load-sylius-fixtures "Permalink to this headline")

The recommended way to load the predefined set of Sylius fixtures is here:

```

php bin/console sylius:fixtures:load

```

####### What data is loaded by fixtures in Sylius?[¶](#what-data-is-loaded-by-fixtures-in-sylius "Permalink to this headline")

All files that serve for loading fixtures of Sylius are placed in the `Sylius/Bundle/CoreBundle/Fixture/\*` directory.

And the specified data for fixtures is stored in the

[Sylius/Bundle/CoreBundle/Resources/config/app/fixtures.yml](https://github.com/Sylius/Sylius/blob/master/src/Sylius/Bundle/CoreBundle/Resources/config/app/fixtures.yml) file.

####### Available configuration options[¶](#available-configuration-options "Permalink to this headline")

######## locale[¶](#locale "Permalink to this headline")

| Configuration key | Function |

| --- | --- |

| load\_default\_locale | Determine if default shop locale (defined as %locale%) parameter will be loaded. True by default. |

| locales | Array of locale codes, which will be loaded. Empty by default. |

####### Learn more[¶](#learn-more "Permalink to this headline")

* [FixturesBundle documentation](https://github.com/Sylius/SyliusFixturesBundle/blob/master/docs/index.md)

###### Events[¶](#events "Permalink to this headline")

Tip

You can learn more about events in general in [the Symfony documentation](https://symfony.com/doc/current/event_dispatcher.html).

####### What is the naming convention of Sylius events?[¶](#what-is-the-naming-convention-of-sylius-events "Permalink to this headline")

The events that are designed for the entities have a general naming convention: `sylius.entity\_name.event\_name`.

The examples of such events are: `sylius.product.pre\_update`, `sylius.shop\_user.post\_create`, `sylius.taxon.pre\_create`.

######## Events reference[¶](#events-reference "Permalink to this headline")

All Sylius bundles are using [SyliusResourceBundle](https://github.com/Sylius/SyliusResourceBundle/blob/master/docs/index.md), which has some built-in events.

| Event | Description |

| --- | --- |

| sylius.<resource>.pre\_create | Before persist |

| sylius.<resource>.post\_create | After flush |

| sylius.<resource>.pre\_update | Before flush |

| sylius.<resource>.post\_update | After flush |

| sylius.<resource>.pre\_delete | Before remove |

| sylius.<resource>.post\_delete | After flush |

| sylius.<resource>.initialize\_create | Before creating view |

| sylius.<resource>.initialize\_update | Before creating view |

######## CRUD events rules[¶](#crud-events-rules "Permalink to this headline")

As you should already know, every resource controller is represented by

the `sylius.controller.<resource\_name>` service. Several useful events are dispatched during execution of every default action

of this controller. When creating a new resource via the `createAction` method, 2 events occur.

First, before the `persist()` is called on the resource, the `sylius.<resource\_name>.pre\_create` event is dispatched.

And after the data storage is updated, `sylius.<resource\_name>.post\_create` is triggered.

The same set of events is available for the `update` and `delete` operations.

All the dispatches are using the `GenericEvent` class and return the resource object by the `getSubject` method.

######## Checkout events rules[¶](#checkout-events-rules "Permalink to this headline")

To dispatch checkout steps the events names are overloaded.

See \_sylius.event in src/Sylius/Bundle/ShopBundle/Resources/config/routing/checkout.yml

| Event | Description |

| --- | --- |

| sylius.order.initialize\_address | Before creating address view |

| sylius.order.initialize\_select\_shipping | Before creating shipping view |

| sylius.order.initialize\_payment | Before creating payment view |

| sylius.order.initialize\_complete | Before creating complete view |

####### What events are already used in Sylius?[¶](#what-events-are-already-used-in-sylius "Permalink to this headline")

Even though Sylius has events as entry points to each resource only some of these points are already used in our usecases.

The events already used in Sylius are described in the Book alongside the concepts they concern.

Tip

What is more you can easily check all the Sylius events in your application by using this command:

```

php bin/console debug:event-dispatcher | grep sylius

```

####### Customizations[¶](#customizations "Permalink to this headline")

Note

**Customizing logic via Events vs. State Machines**

The logic in which Sylius operates can be customized in two ways. First of them is using the state machines: what is

really useful when you need to modify business logic for instance modify the flow of the checkout,

and the second is listening on the kernel events related to the entities, which is helpful for modifying the HTTP responses

visible directly to the user, like displaying notifications, sending emails.

####### Learn more[¶](#learn-more "Permalink to this headline")

* [Sylius Documentation: The Book](index.html#document-book/index)

* [Architecture Overview](index.html#document-book/architecture/architecture)

* [Architectural Drivers](index.html#document-book/architecture/drivers)

* [Resource Layer](index.html#document-book/architecture/resource_layer)

* [State Machine](index.html#document-book/architecture/state_machine)

* [Translations](index.html#document-book/architecture/translations)

* [E-Mails](index.html#document-book/architecture/emails)

* [Contact](index.html#document-book/architecture/contact)

* [Fixtures](index.html#document-book/architecture/fixtures)

* [Events](index.html#document-book/architecture/events)

* [Architecture Overview](index.html#document-book/architecture/architecture)

* [Architectural Drivers](index.html#document-book/architecture/drivers)

* [Resource Layer](index.html#document-book/architecture/resource_layer)

* [State Machine](index.html#document-book/architecture/state_machine)

* [Translations](index.html#document-book/architecture/translations)

* [E-Mails](index.html#document-book/architecture/emails)

* [Contact](index.html#document-book/architecture/contact)

* [Fixtures](index.html#document-book/architecture/fixtures)

* [Events](index.html#document-book/architecture/events)

#### Configuration[¶](#configuration "Permalink to this headline")

Having knowledge about basics of our architecture we will introduce the three most important concepts - Channels, Locales and Currencies.

These things have to be configured before you will have a Sylius application up and running.

##### Configuration[¶](#configuration "Permalink to this headline")

Having knowledge about basics of our [architecture](index.html#document-book/architecture/index) we will introduce

the three most important concepts - Channels, Locales and Currencies. These things have to be configured before you will have a Sylius application up and running.

###### Channels[¶](#channels "Permalink to this headline")

In the modern world of e-commerce your website is no longer the only point of sale for your goods.

**Channel** model represents a single sales channel, which can be one of the following things:

* Webstore

* Mobile application

* Cashier in your physical store

Or pretty much any other channel type you can imagine.

**What may differ between channels?** Particularly anything from your shop configuration:

* products,

* currencies,

* locales (language),

* countries,

* themes,

* hostnames,

* taxes,

* payment and shipping methods,

* menu.

A **Channel** has a `code`, a `name` and a `color`.

In order to make the system more convenient for the administrator - there is just one, shared admin panel. Also users are shared among the channels.

Tip

In the dev environment you can easily check what channel you are currently on in the Symfony debug toolbar.

![_images/channel_toolbar.png](_images/channel_toolbar.png)

####### Different menu root[¶](#different-menu-root "Permalink to this headline")

By default, Sylius will render the same menu for all channels defined in the store, which will be all the children of the

taxon with the code category. You can customize this behaviour by specifying a menu taxon in the “Look & feel” section

of desired channel.

![_images/channel_menu_taxon.png](_images/channel_menu_taxon.png)

With this configuration, this particular channel will expose a menu starting from the children of the chosen taxon

(T-Shirt taxon in this example):

![_images/channel_menu_with_menu_taxon.png](_images/channel_menu_with_menu_taxon.png)

Rest of the channels will still render only children of category taxon.

**How to get the current channel?**

You can get the current channel from the channel context.

```

$channel = $this->container->get('sylius.context.channel')->getChannel();

```

Warning

Beware! When using multiple channels, remember to configure `hostname` for **each** of them.

If missing, default context would not be able to provide appropriate channel and it will result in an error.

Note

The channel is by default determined basing on the hostname, but you can customize that behaviour.

To do that you have to implement the `Sylius\Component\Channel\Context\ChannelContextInterface`

and register it as a service under the `sylius.context.channel` tag. You should also add a `priority="64"`

since the default ChannelContext has a `priority="0"` (and by default a `priority="0"` is assigned).

Note

Moreover if the channel depends mainly on the request you can implement the `Sylius\Component\Channel\Context\RequestBased\RequestResolverInterface`

with its `findChannel(Request $request)` method and register it under the `sylius.context.channel.request\_based.resolver` tag.

####### Shop Billing Data[¶](#shop-billing-data "Permalink to this headline")

For [Invoicing](index.html#document-book/orders/invoices) and [Credit Memo](index.html#document-book/orders/refunds) purposes Channels are

supplied with a section named Shop Billing Data, which is editable on the Channel create/update form.

![_images/shop_billing_data.png](_images/shop_billing_data.png)

######## Business Units[¶](#business-units "Permalink to this headline")

Sylius Plus is supplied with an enhanced version of Shop Billing Data from open source edition. It is also used for Invoicing and Refunds purposes,

but it is a separate entity, that you can create outside of the Channel and then just pick a previously created Business Unit

on the Channel form.

![_images/business_units.png](_images/business_units.png)

![_images/business_unit_form.png](_images/business_unit_form.png)

![_images/channel_business_unit.png](_images/channel_business_unit.png)

[![_images/banner1.png](_images/banner1.png)](https://sylius.com/plus/?utm_source=docs)

####### Learn more[¶](#learn-more "Permalink to this headline")

* [Channel - Component Documentation](index.html#document-components_and_bundles/components/Channel/index).

Note

In order to add a new locale to your store you have to assign it to a channel.

###### Locales[¶](#locales "Permalink to this headline")

To support multiple languages we are using **Locales** in **Sylius**. Locales are language codes standardized by the ISO 15897.

Tip

In the dev environment you can easily check what locale you are currently using in the Symfony debug toolbar:

![_images/toolbar.png](_images/toolbar.png)

####### Base Locale[¶](#base-locale "Permalink to this headline")

During the [installation](index.html#document-book/installation/installation) you provided a default base locale. This is the language in which everything

in your system will be saved in the database - all the product names, texts on website, e-mails etc.

####### Locale Context[¶](#locale-context "Permalink to this headline")

To manage the currently used language, we use the **LocaleContext**. You can always access it with the ID `sylius.context.locale` in the container.

```

<?php

public function fooAction()

{

    $locale = $this->get('sylius.context.locale')->getLocaleCode();

}

```

The locale context can be injected into any of your services and give you access to the currently used locale.

####### Available Locales Provider[¶](#available-locales-provider "Permalink to this headline")

The Locale Provider service (`sylius.locale\_provider`) is responsible for returning all languages available for the current user. By default, returns all configured locales.

You can easily modify this logic by overriding this service.

```

<?php

public function fooAction()

{

    $locales = $this->get('sylius.locale\_provider')->getAvailableLocalesCodes();

    foreach ($locales as $locale) {

        echo $locale;

    }

}

```

To get all languages configured in the store, regardless of your availability logic, use the locales repository:

```

<?php

$locales = $this->get('sylius.repository.locale')->findAll();

```

####### Learn more[¶](#learn-more "Permalink to this headline")

* [Locale - Component Documentation](index.html#document-components_and_bundles/components/Locale/index).

###### Currencies[¶](#currencies "Permalink to this headline")

Sylius supports multiple currencies per store and makes it very easy to manage them.

There are several approaches to processing several currencies, but we decided to use the simplest solution

we are storing all money values in the **base currency per channel** and convert them to other currencies with exchange rates.

Note

The **base currency** to the first channel is set during the installation of Sylius and it has the **exchange rate** equal to “1.000”.

Tip

In the dev environment you can easily check the base currency in the Symfony debug toolbar:

![_images/toolbar.png](_images/toolbar.png)

####### Currency Context[¶](#currency-context "Permalink to this headline")

By default, user can switch the current currency in the frontend of the store.

To manage the currently used currency, we use the **CurrencyContext**. You can always access it through the `sylius.context.currency` id.

```

<?php

public function fooAction()

{

    $currency = $this->get('sylius.context.currency')->getCurrency();

}

```

####### Getting the list of available currencies for a channel[¶](#getting-the-list-of-available-currencies-for-a-channel "Permalink to this headline")

If you want to get a list of currently available currencies for a given channel,

you can get them from the `Channel`.

You can also get the current `Channel` from the container.

```

<?php

public function fooAction()

{

    // If you don't have it, you can get the current channel from container

    $channel = $this->container->get('sylius.context.channel')->getChannel();

    $currencies = $channel->getCurrencies();

}

```

Note

If you want to learn more about `Channels`, what they represent, and how they work; read the previous chapter [Channels](index.html#document-book/configuration/channels)

####### Currency Converter[¶](#currency-converter "Permalink to this headline")

The `Sylius\Component\Currency\Converter\CurrencyConverter` is a service available under the `sylius.currency\_converter` id.

It allows you to convert money values from one currency to another.

This solution is used for displaying an *approximate* value of price when the desired currency is different from the base currency of the current channel.

####### Switching Currency of a Channel[¶](#switching-currency-of-a-channel "Permalink to this headline")

We may of course change the currency used by a channel. For that we have the `sylius.storage.currency` service, which implements

the `Sylius\Component\Core\Currency\CurrencyStorageInterface` with methods

`->set(ChannelInterface $channel, $currencyCode)` and `->get(ChannelInterface $channel)`.

```

$container->get('sylius.storage.currency')->set($channel, 'PLN');

```

####### Displaying Currencies in the templates[¶](#displaying-currencies-in-the-templates "Permalink to this headline")

There are some useful helpers for rendering money values in the front end.

Simply import the money macros of the `ShopBundle` in your twig template and use the functions to display the value:

```

..

{% import "@SyliusShop/Common/Macro/money.html.twig" as money %}

..

<span class="price">{{ money.format(price, 'EUR') }}</span>

```

Sylius provides you with some handy [Global Twig variables](index.html#document-customization/template) to facilitate displaying money values even more.

####### Learn more[¶](#learn-more "Permalink to this headline")

* [Currency - Component Documentation](index.html#document-components_and_bundles/components/Currency/index)

* [Pricing Concept Documentation](index.html#document-book/products/pricing)

* [Channels](index.html#document-book/configuration/channels)

* [Locales](index.html#document-book/configuration/locales)

* [Currencies](index.html#document-book/configuration/currencies)

* [Channels](index.html#document-book/configuration/channels)

* [Locales](index.html#document-book/configuration/locales)

* [Currencies](index.html#document-book/configuration/currencies)

#### Customers[¶](#customers "Permalink to this headline")

This chapter will tell you more about the way Sylius handles users, customers and admins.

There is also a subchapter dedicated to addresses of your customers.

##### Customers[¶](#customers "Permalink to this headline")

This chapter will tell you more about the way Sylius handles users, customers and admins.

There is also a subchapter dedicated to addresses of your customers.

###### Customer and ShopUser[¶](#customer-and-shopuser "Permalink to this headline")

For handling customers of your system **Sylius** is using a combination of two entities - **Customer** and **ShopUser**.

The difference between these two entities is simple:

the **Customer** is a guest in your shop and the **ShopUser** is a user registered in the system - they have an account.

####### Customer[¶](#customer "Permalink to this headline")

The Customer entity was created to collect data about non-registered guests of the system - ones that has been buying without having an account

or that have somehow provided us their e-mail.

######## How to create a Customer programmatically?[¶](#how-to-create-a-customer-programmatically "Permalink to this headline")

As usual, use a factory. The only required field for the Customer entity is `email`, provide it before adding it to the repository.

```

/\*\* @var CustomerInterface $customer \*/

$customer = $this->container->get('sylius.factory.customer')->createNew();

$customer->setEmail('customer@test.com');

$this->container->get('sylius.repository.customer')->add($customer);

```

The Customer entity can of course hold other information besides an email, it can be for instance `firstName`, `lastName` or `birthday`.

Note

The relation between the Customer and ShopUser is bidirectional. Both entities hold a reference to each other.

####### ShopUser[¶](#shopuser "Permalink to this headline")

ShopUser entity is designed for customers that have registered in the system - they have an account with both e-mail and password.

They can visit and modify their account.

While creating new account the existence of the provided email in the system is checked - if the email was present - it will already have a Customer

therefore the existing one will be assigned to the newly created ShopUser, if not - a new Customer will be created together with the ShopUser.

Note

Please note that if a newly created ShopUser has been assigned to an existing Customer, they will have access

to previously placed orders as a guest.

######## How to create a ShopUser programmatically?[¶](#how-to-create-a-shopuser-programmatically "Permalink to this headline")

Assuming that you have a Customer (either retrieved from the repository or a newly created one) - use a factory to create

a new ShopUser, assign the existing Customer and a password via the `setPlainPassword()` method.

```

/\*\* @var ShopUserInterface $user \*/

$user = $this->container->get('sylius.factory.shop\_user')->createNew();

// Now let's find a Customer by their e-mail:

/\*\* @var CustomerInterface $customer \*/

$customer = $this->container->get('sylius.repository.customer')->findOneBy(['email' => 'customer@test.com']);

// and assign it to the ShopUser

$user->setCustomer($customer);

$user->setPlainPassword('pswd');

$this->container->get('sylius.repository.shop\_user')->add($user);

```

######## Changing the ShopUser password[¶](#changing-the-shopuser-password "Permalink to this headline")

The already set password of a **ShopUser** can be easily changed via the `setPlainPassword()` method.

```

$user->getPassword(); // returns encrypted password - 'pswd'

$user->setPlainPassword('resu1');

// the password will now be 'resu1' and will become encrypted while saving the user in the database

```

####### Customer related events[¶](#customer-related-events "Permalink to this headline")

| Event id | Description |

| --- | --- |

| `sylius.customer.post\_register` | dispatched when a new Customer is registered |

| `sylius.customer.pre\_update` | dispatched when a Customer is updated |

| `sylius.oauth\_user.post\_create` | dispatched when an OAuthUser is created |

| `sylius.oauth\_user.post\_update` | dispatched when an OAuthUser is updated |

| `sylius.shop\_user.post\_create` | dispatched when a User is created |

| `sylius.shop\_user.post\_update` | dispatched when a User is updated |

| `sylius.shop\_user.pre\_delete` | dispatched before a User is deleted |

| `sylius.user.email\_verification.token` | dispatched when a verification token is requested |

| `sylius.user.password\_reset.request.token` | dispatched when a reset password token is requested |

| `sylius.user.pre\_password\_change` | dispatched before user password is changed |

| `sylius.user.pre\_password\_reset` | dispatched before user password is reset |

| `sylius.user.security.implicit\_login` | dispatched when an implicit login is done |

| `security.interactive\_login` | dispatched when an interactive login is done |

####### Learn more[¶](#learn-more "Permalink to this headline")

* [User - Component Documentation](index.html#document-components_and_bundles/components/User/index)

###### Customer Pools[¶](#customer-pools "Permalink to this headline")

Customer Pool is a collection of Customers that is assigned to a specific channel. Thanks to this concept, if you have two

channels, each of them has a separate customer pool, then customers that have accounts in channel A, and have not registered in channel B,

will not be able to log in to channel B with credentials they have specified in channel A (which is the behaviour happening in

Sylius open source edition). This feature allows you to sell via multiple channels, creating a illusion of shopping in

completely different stores, while you still have one administration panel.

####### Managing Customer Pools[¶](#managing-customer-pools "Permalink to this headline")

Customer pools management is available in the administration panel in the Customers section.

![_images/customer_pools_menu.png](_images/customer_pools_menu.png)

New customer pools can be added through the admin UI in the section Customer pools or via fixtures,

as it is configured in `src/Resource/config/fixtures.yaml` for the `plus` fixture suite.

The configuration looks like that:

```

sylius\_fixtures:

 suites:

 default:

 fixtures:

 customer\_pool:

 priority: 1

 options:

 custom:

 default:

 name: "Default"

 code: "default"

```

Customer Pool can be assigned to a Channel, but only during its \_creation\_ in Admin panel. Currently it is not possible

to modify the User Pool after the channel is created, as it would lead to certain edge cases with customers losing access to channels,

after improper admin operations.

There is also a possibility to choose a specific customer pool during channel or shop customer creation in fixtures

(remember to create a customer pool before assigning it to a channel):

```

sylius\_fixtures:

 suites:

 default:

 fixtures:

 channel:

 options:

 custom:

 mobile:

 name: "Mobile"

 code: "mobile"

 locales:

 - "en\_US"

 currencies:

 - "USD"

 customer\_pool: "default"

 enabled: true

 shop\_user:

 options:

 custom:

 -

 email: "plus@example.com"

 first\_name: "John"

 last\_name: "Doe"

 password: "sylius"

 customer\_pool: "default"

```

####### How to create a Customer Pool programmatically?[¶](#how-to-create-a-customer-pool-programmatically "Permalink to this headline")

As usual, use a factory. The only required fields for the CustomerPool entity are `code` and `name`, provide them

before adding it to the repository.

```

/\*\* @var CustomerPoolInterface $customerPool \*/

$customerPool = $this->container->get('sylius\_plus.factory.customer\_pool')->createNew();

$customerPool->setCode('HOME\_POOL');

$customerPool->setName('Home Pool');

$this->container->get('sylius\_plus.repository.customer\_pool')->add($customerPool);

```

In order to assign a Customer Pool to a Channel programmatically use this simple trick:

```

// given that you have a $channel from repository, and a $customerPool just created above

$channel->setCustomerPool($customerPool);

```

####### Learn more[¶](#learn-more "Permalink to this headline")

* [Channels - Concept Documentation](index.html#document-book/configuration/channels)

[![_images/banner2.png](_images/banner2.png)](https://sylius.com/plus/?utm_source=docs)

###### AdminUser[¶](#adminuser "Permalink to this headline")

The **AdminUser** entity extends the **User** entity. It is created to enable administrator accounts that have access to the administration panel.

####### How to create an AdminUser programmatically?[¶](#how-to-create-an-adminuser-programmatically "Permalink to this headline")

The **AdminUser** is created just like every other entity, it has its own factory. By default it will have an administration **role** assigned.

```

/\*\* @var AdminUserInterface $admin \*/

$admin = $this->container->get('sylius.factory.admin\_user')->createNew();

$admin->setEmail('administrator@test.com');

$admin->setPlainPassword('pswd');

$this->container->get('sylius.repository.admin\_user')->add($admin);

```

####### Administration Security[¶](#administration-security "Permalink to this headline")

In **Sylius** by default you have got the administration panel routes (`/admin/\*`) secured by a firewall - its configuration

can be found in the [security.yaml](https://github.com/Sylius/Sylius/blob/master/config/packages/security.yaml) file.

Only the logged in **AdminUsers** are eligible to enter these routes.

####### Access Control via Administrator Roles (ACL/RBAC)[¶](#access-control-via-administrator-roles-acl-rbac "Permalink to this headline")

RBAC (*Role Based Access Control*) or ACL (*Access Control Layer*) is an approach to restricting system access for users

using roles system. It is required by the majority of companies on the enterprise level, thus it is provided in the Sylius Plus edition.

A Role is a set of permissions to perform certain operations within the system, which is assigned to a chosen Administrator.

In Sylius Plus implementation of this system, we are basing on routing to determine what kind of permissions are there to be assigned.

This allows us to for example give a role access to only show actions of a chosen entity (like Products or Orders).

It is important to know that one Administrator can have multiple roles assigned.

The RBAC system in Sylius Plus let’s you also to temporarily disable the Permission Checker for a chosen Administrator.

Tip

You can disable permission checker for administrator not only via the UI, but also with a Symfony command:

```

bin/console sylius-plus-rbac:disable-admin-permission-checker <email>

```

The Sylius Plus fixture suite provides a few roles as examples on how you can shape the roles in your system:

* `SUPER\_ADMIN` with access to everything including roles management

* `PRODUCT\_MANAGER` with access to product catalog management with inventory, associations, options, taxons etc.

* `FULFILLMENT\_WORKER` with access to order management, product catalog show, inventory management and shipments

######## Customizing the permissions tree[¶](#customizing-the-permissions-tree "Permalink to this headline")

######### How to add a new permission?[¶](#how-to-add-a-new-permission "Permalink to this headline")

Let’s assume that you would like to add a new permission to ACL. You will need to add these few lines to the `config.yml`:

```

# config/packages/\_sylius.yaml

# ...

sylius\_plus:

 permissions:

 # Each permission must have a unique id, if you want the route to be protected, as id you need to enter the name route.

 app\_admin\_product\_import:

 parent: data\_transfer # Here, specify parent in the permission tree.

 label: product\_import # Here, specify the name that will be displayed in the admin panel.

 enabled: true # Here you specify whether the permission is to be active, this field is not required, by default is set to true.

```

You can also add permission while defining the route. However, this will not work when you have defined or

imported permissions with the same id in the `config.yml`:

```

# config/routes/sylius\_admin.yaml

# ...

app\_admin\_product\_import:

 path: /admin/products/import

 methods: [GET]

 defaults:

 \_sylius\_plus\_rbac:

 parent: data\_transfer

 label: product\_import

 enabled: true

```

For this permission you will need to add translations:

```

sylius\_plus.rbac.parent.data\_transfer

sylius\_plus.rbac.action.product\_import

```

######### How to modify a permission?[¶](#how-to-modify-a-permission "Permalink to this headline")

If you would like to modify an existing permission of for example the permission to payment complete:

```

# config/packages/\_sylius.yaml

# ...

sylius\_plus:

 permissions:

 sylius\_admin\_order\_payment\_complete:

 parent: orders\_shop

 label: order\_payment\_complete

```

You can also modify the permission on the route is overwritten, only this will not work when you have defined or imported permissions with the same id in config.yml:

```

# config/routes/sylius\_admin.yaml

# ...

sylius\_admin\_order\_payment\_complete:

 path: /admin/orders/{orderId}/payments/{id}/complete

 methods: [PUT]

 defaults:

 # ...

 \_sylius\_plus\_rbac:

 parent: orders\_shop

 label: order\_payment\_complete

```

You can find the default configuration of some permissions in the `src/Resources/config/permissions.yaml` file.

######### How to delete a permission?[¶](#how-to-delete-a-permission "Permalink to this headline")

If you want to remove a permission, you have to overwrite the permission configuration and and set the enabled field to false:

```

# config/packages/\_sylius.yaml

# ...

sylius\_plus:

 permissions:

 sylius\_admin\_order\_payment\_complete:

 enabled: false

```

or for overwriting a route, although this will not work when you have defined or imported permissions with the same

id in the `config.yml`:

```

# config/routes/sylius\_admin.yaml

# ...

sylius\_admin\_order\_payment\_complete:

 path: /admin/orders/{orderId}/payments/{id}/complete

 methods: [PUT]

 defaults:

 # ...

 \_sylius\_plus\_rbac:

 enabled: false

```

######### “Access denied” view customization[¶](#access-denied-view-customization "Permalink to this headline")

When an administrator does not have access to a given route, the Twig’s `path()` and `url()` functions will return `ACCESS\_DENIED`.

You can adjust the view using the css and javascript selectors. For example:

```

a[href="ACCESS\_DENIED"].button {

 display: none !important;

}

```

More examples can be found in the `src/Resources/public/\*` path.

You can also use a twig function:

```

{% if sylius\_plus\_rbac\_has\_permission("sylius\_admin\_order\_payment\_complete") %}

 {# ... #}

{% endif %}

```

######### Administrators per Channel[¶](#administrators-per-channel "Permalink to this headline")

It is a possible to choose a channel to which an Administrator has access. It is done on the Administrator’s configuration page.

If a channel is not chosen on an Administrator then they will have access to all channels.

Having chosen a channel on an Administrator, their access will get restricted within the Sales section of the main menu

in the Admin Panel. Thus they will see only orders, payments, shipments, return requests, invoices and credit memos

from the channel they have access to.

####### Additional Admin User Fixtures[¶](#additional-admin-user-fixtures "Permalink to this headline")

Three new fields have been added to the Admin User fixtures: `channel`, `roles` and `enable\_permission\_checker`.

They can be configured as below:

```

sylius\_fixtures:

 suites:

 default:

 fixtures:

 channel:

 options:

 custom:

 - email: 'sylius@example.com'

 username: 'sylius'

 password: 'sylius'

 channel: 'DEFAULT\_CHANNEL\_CODE'

 roles:

 - 'SUPER\_ADMIN\_CODE'

 enable\_permission\_checker: true

```

[![_images/banner2.png](_images/banner2.png)](https://sylius.com/plus/?utm_source=docs)

####### Learn more[¶](#learn-more "Permalink to this headline")

* [Customer & ShopUser - Documentation](index.html#document-book/customers/customer_and_shopuser)

###### Addresses[¶](#addresses "Permalink to this headline")

####### Countries[¶](#countries "Permalink to this headline")

**Countries** are a part of the [Addressing](index.html#document-book/customers/addresses/addresses) concept.

The **Country** entity represents a real country that your shop is willing to sell its goods in (for example the UK).

It has an ISO code to be identified easily ([ISO 3166-1 alpha-2](https://www.iso.org/iso/country_codes)).

Countries might also have **Provinces**, which is in fact a general name for an administrative division, within a country.

Therefore we understand provinces as states of the USA, voivodeships of Poland or Bundesländer of Germany.

######## How to add a country?[¶](#how-to-add-a-country "Permalink to this headline")

To give you a better insight into Countries, let’s have a look on how to prepare and add a Country to the system programmatically.

We will do it with a province at once.

You will need factories for countries and provinces in order to create them:

```

/\*\* @var CountryInterface $country \*/

$country = $this->container->get('sylius.factory.country')->createNew();

/\*\* @var ProvinceInterface $province \*/

$province = $this->container->get('sylius.factory.province')->createNew();

```

To the newly created objects assign codes.

```

// US - the United States of America

$country->setCode('US');

// US\_CA - California

$province->setCode('US\_CA');

```

Provinces may be added to a country via a collection. Create one and add the province object to it

and using the prepared collection add the province to the Country.

```

$provinces = new ArrayCollection();

$provinces->add($province);

$country->setProvinces($provinces);

```

You can of course simply add single province:

```

$country->addProvince($province);

```

Finally you will need a repository for countries to add the country to your system.

```

/\*\* @var RepositoryInterface $countryRepository \*/

$countryRepository = $this->get('sylius.repository.country');

$countryRepository->add($country);

```

From now on the country will be available to use in your system.

######## Learn more[¶](#learn-more "Permalink to this headline")

* [Addressing - Bundle Documentation](index.html#document-components_and_bundles/bundles/SyliusAddressingBundle/index)

* [Addressing - Component Documentation](index.html#document-components_and_bundles/components/Addressing/index)

####### Zones[¶](#zones "Permalink to this headline")

**Zones** are a part of the [Addressing](index.html#document-book/customers/addresses/addresses) concept.

######## Zones and ZoneMembers[¶](#zones-and-zonemembers "Permalink to this headline")

**Zones** consist of **ZoneMembers**. It can be any kind of zone you need -

for instance if you want to have all the EU countries in one zone, or just a few chosen countries that have the same taxation system in one zone,

or you can even distinguish zones by the ZIP code ranges in the USA.

Three different types of zones are available:

* **country** zone, which consists of countries.

* **province** zone, which is constructed from provinces.

* **zone**, which is a group of other zones.

######## How to add a Zone?[¶](#how-to-add-a-zone "Permalink to this headline")

Let’s see how you can add a Zone to your system programmatically.

Firstly you will need a factory for zones - There is a specific one.

```

/\*\* @var ZoneFactoryInterface $zoneFactory \*/

$zoneFactory = $this->container->get('sylius.factory.zone');

```

Using the ZoneFactory create a new zone with its members. Let’s take the UK as an example.

```

/\*\* @var ZoneInterface $zone \*/

$zone = $zoneFactory->createWithMembers(['GB\_ENG', 'GB\_NIR', 'GB\_SCT'. 'GB\_WLS']);

```

Now give it a code, name and type:

```

$zone->setCode('GB');

$zone->setName('United Kingdom');

// available types are the type constants from the ZoneInterface

$zone->setType(ZoneInterface::TYPE\_PROVINCE);

```

Finally get the zones repository from the container and add the newly created zone to the system.

```

/\*\* @var RepositoryInterface $zoneRepository \*/

$zoneRepository = $this->container->get('sylius.repository.zone');

$zoneRepository->add($zone);

```

######## Matching a Zone[¶](#matching-a-zone "Permalink to this headline")

Zones are not very useful alone, but they can be a part of a complex taxation/shipping or any other system.

A service implementing the ZoneMatcherInterface is responsible for matching the **Address** to a specific **Zone**.

```

/\*\* @var ZoneMatcherInterface $zoneMatcher \*/

$zoneMatcher = $this->get('sylius.zone\_matcher');

$zone = $zoneMatcher->match($user->getAddress());

```

ZoneMatcher can also return all matching zones. (not only the most suitable one)

```

/\*\* @var ZoneMatcherInterface $zoneMatcher \*/

$zoneMatcher = $this->get('sylius.zone\_matcher');

$zones = $zoneMatcher->matchAll($user->getAddress());

```

Internally, Sylius uses this service to define the shipping and billing zones of an *Order*, but you can use it for many different things and it is totally up to you.

######## Learn more[¶](#learn-more "Permalink to this headline")

* [Addressing - Bundle Documentation](index.html#document-components_and_bundles/bundles/SyliusAddressingBundle/index)

* [Addressing - Component Documentation](index.html#document-components_and_bundles/components/Addressing/index)

####### Addresses[¶](#addresses "Permalink to this headline")

Every address in Sylius is represented by the **Address** model.

It has a few important fields:

* `firstName`

* `lastName`

* `phoneNumber`

* `company`

* `countryCode`

* `provinceCode`

* `street`

* `city`

* `postcode`

Note

The Address has a relation to a **Customer** - which is really useful during the [Checkout addressing step](index.html#document-book/orders/checkout).

######## How to create an Address programmatically?[¶](#how-to-create-an-address-programmatically "Permalink to this headline")

In order to create a new address, use a factory. Then complete your address with required data.

```

/\*\* @var AddressInterface $address \*/

$address = $this->container->get('sylius.factory.address')->createNew();

$address->setFirstName('Harry');

$address->setLastName('Potter');

$address->setCompany('Ministry of Magic');

$address->setCountryCode('UK');

$address->setProvinceCode('UKJ');

$address->setCity('Little Whinging');

$address->setStreet('4 Privet Drive');

$address->setPostcode('000001');

// and finally having the address you can assign it to any Order

$order->setShippingAddress($address);

```

######## Learn more[¶](#learn-more "Permalink to this headline")

* [Addressing - Component Documentation](index.html#document-components_and_bundles/components/Addressing/index)

* [Addressing - Bundle Documentation](index.html#document-components_and_bundles/bundles/SyliusAddressingBundle/index)

####### Address Book[¶](#address-book "Permalink to this headline")

The Address Book concept is a very convenient solution for the customers of your shop, that come back.

Once they provide an address it is saved in the system and can be reused the next time.

**Sylius** handles the address book in a not complex way:

######## The Addresses Collection on a Customer[¶](#the-addresses-collection-on-a-customer "Permalink to this headline")

On the Customer entity we are holding a collection of addresses:

```

class Customer {

    /\*\*

 \* @var Collection|AddressInterface[]

 \*/

    protected $addresses;

}

```

We can operate on it as usual - by adding and removing objects.

Besides the Customer entity has a **default address** field that is the default address used both for shipping and billing,

the one that will be filling the form fields by default.

######## How to add an address to the address book manually?[¶](#how-to-add-an-address-to-the-address-book-manually "Permalink to this headline")

If you would like to add an address to the collection of Addresses of a chosen customer that’s all what you should do:

Create a new address:

```

/\*\* @var AddressInterface $address \*/

$address = $this->container->get('sylius.factory.address')->createNew();

$address->setFirstName('Ronald');

$address->setLastName('Weasley');

$address->setCompany('Ministry of Magic');

$address->setCountryCode('UK');

$address->setProvinceCode('UKJ');

$address->setCity('Otter St Catchpole');

$address->setStreet('The Burrow');

$address->setPostcode('000001');

```

Then find a customer to which you would like to assign it, and add the address.

```

$customer = $this->container->get('sylius.repository.customer')->findOneBy(['email' => 'ron.weasley@magic.com']);

$customer->addAddress($address);

```

Remember to flush the customer’s manager to save this change.

```

$this->container->get('sylius.manager.customer')->flush();

```

######## Learn more[¶](#learn-more "Permalink to this headline")

* [Customer & ShopUser Concept Documentation](index.html#document-book/customers/customer_and_shopuser)

* [Addressing - Component Documentation](index.html#document-components_and_bundles/components/Addressing/index)

* [Addressing - Bundle Documentation](index.html#document-components_and_bundles/bundles/SyliusAddressingBundle/index)

* [Countries](index.html#document-book/customers/addresses/countries)

* [Zones](index.html#document-book/customers/addresses/zones)

* [Addresses](index.html#document-book/customers/addresses/addresses)

* [Address Book](index.html#document-book/customers/addresses/address_book)

* [Customer and ShopUser](index.html#document-book/customers/customer_and_shopuser)

* [Customer Pools](index.html#document-book/customers/customer_pools)

* [AdminUser](index.html#document-book/customers/admin_user)

* [Addresses](index.html#document-book/customers/addresses/index)

* [Customer and ShopUser](index.html#document-book/customers/customer_and_shopuser)

* [Customer Pools](index.html#document-book/customers/customer_pools)

* [AdminUser](index.html#document-book/customers/admin_user)

* [Addresses](index.html#document-book/customers/addresses/index)

#### Products[¶](#products "Permalink to this headline")

This is a guide to understanding products handling in Sylius together with surrounding concepts. Read about

Associations, Reviews, Attributes, Taxons etc.

##### Products[¶](#products "Permalink to this headline")

This is a guide to understanding products handling in Sylius together with surrounding concepts.

###### Products[¶](#products "Permalink to this headline")

**Product** model represents unique products in your Sylius store.

Every product can have different variations and attributes.

Warning

Each product has to have at least one variant to be sold in the shop.

####### How to create a Product?[¶](#how-to-create-a-product "Permalink to this headline")

Before we learn how to create products that can be sold, let’s see how to create a product without its complex dependencies.

```

/\*\* @var ProductFactoryInterface $productFactory \*\*/

$productFactory = $this->get('sylius.factory.product');

/\*\* @var ProductInterface $product \*/

$product = $productFactory->createNew();

```

Creating an empty product is not enough to save it in the database. It needs to have a `name`, a `code` and a `slug`.

```

$product->setName('T-Shirt');

$product->setCode('00001');

$product->setSlug('t-shirt');

/\*\* @var RepositoryInterface $productRepository \*/

$productRepository = $this->get('sylius.repository.product');

$productRepository->add($product);

```

After being added via the repository, your product will be in the system. But the customers won’t be able to buy it.

###### Variants[¶](#variants "Permalink to this headline")

**ProductVariant** represents a unique kind of product and can have its own pricing configuration, inventory tracking etc.

Variants may be created out of Options of the product, but you are also able to use product variations system without the options at all.

####### Virtual Product Variants, that do not require shipping[¶](#virtual-product-variants-that-do-not-require-shipping "Permalink to this headline")

Tip

On the ProductVariant there is a possibility to make a product virtual - by setting its `shippingRequired` property to `false`.

In such a way you can have products that will be downloadable or installable for instance.

####### How to create a Product with a Variant?[¶](#how-to-create-a-product-with-a-variant "Permalink to this headline")

You may need to sell product in different Variants - for instance you may need to have books both in hardcover and in paperback.

Just like before, use a factory, create the product, save it in the Repository.

And then using the ProductVariantFactory create a variant for your product.

```

/\*\* @var ProductVariantFactoryInterface $productVariantFactory \*\*/

$productVariantFactory = $this->get('sylius.factory.product\_variant');

/\*\* @var ProductVariantInterface $productVariant \*/

$productVariant = $productVariantFactory->createNew();

```

Having created a Variant, provide it with the required attributes and attach it to your Product.

```

$productVariant->setName('Hardcover');

$productVariant->setCode('1001');

$productVariant->setPosition(1);

$productVariant->setProduct($product);

```

Finally save your Variant in the database using a repository.

```

/\*\* @var RepositoryInterface $productVariantRepository \*/

$productVariantRepository = $this->get('sylius.repository.product\_variant');

$productVariantRepository->add($productVariant);

```

###### Options[¶](#options "Permalink to this headline")

In many cases, you will want to have product with different variations.

The simplest example would be a piece of clothing, like a T-Shirt available in different sizes and colors

or a glass available in different shapes or colors.

In order to automatically generate appropriate variants, you need to define options.

Every option type is represented by **ProductOption** and references multiple **ProductOptionValue** entities.

For example you can have two options - Size and Color. Each of them will have their own values.

* Size

	+ S

	+ M

	+ L

	+ XL

	+ XXL

* Color

	+ Red

	+ Green

	+ Blue

After defining possible options for a product let’s move on to **Variants** which are in fact combinations of options.

####### How to create a Product with Options and Variants?[¶](#how-to-create-a-product-with-options-and-variants "Permalink to this headline")

Firstly let’s learn how to prepare an exemplary Option and its values.

```

/\*\* @var ProductOptionInterface $option \*/

$option = $this->get('sylius.factory.product\_option')->createNew();

$option->setCode('t\_shirt\_color');

$option->setName('T-Shirt Color');

// Prepare an array with values for your option, with codes, locale code and option values.

$valuesData = [

    'OV1' => ['locale' => 'en\_US', 'value' => 'Red'],

    'OV2' => ['locale' => 'en\_US', 'value' => 'Blue'],

    'OV3' => ['locale' => 'en\_US', 'value' => 'Green'],

];

foreach ($valuesData as $code => $values) {

    /\*\* @var ProductOptionValueInterface $optionValue \*/

    $optionValue = $this->get('sylius.factory.product\_option\_value')->createNew();

    $optionValue->setCode($code);

    $optionValue->setFallbackLocale($values['locale']);

    $optionValue->setCurrentLocale($values['locale']);

    $optionValue->setValue($values['value']);

    $option->addValue($optionValue);

}

```

After you have an Option created and you keep it as `$option` variable let’s add it to the Product and generate **Variants**.

```

// Assuming that you have a basic product let's add the previously created option to it.

$product->addOption($option);

// Having option of a product you can generate variants. Sylius has a service for that operation.

/\*\* @var ProductVariantGeneratorInterface $variantGenerator \*/

$variantGenerator = $this->get('sylius.generator.product\_variant');

$variantGenerator->generate($product);

// And finally add the product, with its newly generated variants to the repository.

/\*\* @var RepositoryInterface $productRepository \*/

$productRepository = $this->get('sylius.repository.product');

$productRepository->add($product);

```

####### Learn more:[¶](#learn-more "Permalink to this headline")

* [Product - Bundle Documentation](index.html#document-components_and_bundles/bundles/SyliusProductBundle/index)

* [Product - Component Documentation](index.html#document-components_and_bundles/components/Product/index)

###### Product Reviews[¶](#product-reviews "Permalink to this headline")

Product Reviews are a marketing tool that let your customers give opinions about the products they buy in your shop.

They have a `rating` and `comment`.

####### Rating[¶](#rating "Permalink to this headline")

The rating of a product review is required and must be between 1 and 5.

####### Product review state machine[¶](#product-review-state-machine "Permalink to this headline")

When you look inside the `CoreBundle/Resources/config/app/state\_machine/sylius\_product\_review.yml` you will find out that a Review can have

3 different states:

* `new`,

* `accepted`,

* `rejected`

There are only two possible transitions: `accept` (from `new` to `accepted`) and `reject` (from `new` to `rejected`).

[![_images/sylius_product_review.png](_images/sylius_product_review.png)](_images/sylius_product_review.png)

When a review is accepted **the average rating of a product is updated**.

####### How is the average rating calculated?[¶](#how-is-the-average-rating-calculated "Permalink to this headline")

The average rating is updated by

the [AverageRatingUpdater](https://github.com/Sylius/Sylius/blob/master/src/Sylius/Bundle/ReviewBundle/Updater/AverageRatingUpdater.php) service.

It wraps the [AverageRatingCalculator](https://github.com/Sylius/Sylius/blob/master/src/Sylius/Component/Review/Calculator/AverageRatingCalculator.php),

and uses it inside the `updateFromReview` method.

####### How to add a ProductReview programmatically?[¶](#how-to-add-a-productreview-programmatically "Permalink to this headline")

Create a new review using a factory:

```

/\*\* @var ReviewInterface $review \*/

$review = $this->container->get('sylius.factory.product\_review')->createNew();

```

Fill the content of your review.

```

$review->setTitle('My Review');

$review->setRating(5);

$review->setComment('This product is really great');

```

Then get a customer from the repository, which you would like to make an author of this review.

```

$customer = $this->container->get('sylius.repository.customer')->findOneBy(['email' => 'john.doe@test.com']);

$review->setAuthor($customer);

```

Remember to set the object that is the subject of your review and then add the review to the repository.

```

$review->setReviewSubject($product);

$this->container->get('sylius.repository.product\_review')->add($review);

```

####### Learn more[¶](#learn-more "Permalink to this headline")

* [Product - Bundle Documentation](index.html#document-components_and_bundles/bundles/SyliusProductBundle/index)

* [Product - Component Documentation](index.html#document-components_and_bundles/components/Product/index)

###### Product Associations[¶](#product-associations "Permalink to this headline")

Associations of products can be used as a marketing tool for suggesting your customers, what products to buy together with

the one they are currently considering.

Associations can increase your shop’s efficiency. You choose what strategy you prefer. They are fully configurable.

####### Association Types[¶](#association-types "Permalink to this headline")

The type of an association can be different. If you sell food you can suggest inspiring ingredients, if you sell products

for automotive you can suggest buying some tools that may be useful for a home car mechanic.

Exemplary association types can be: `up-sell`, `cross-sell`, `accessories`, `alternatives` and whatever you imagine.

######## How to create a new Association Type?[¶](#how-to-create-a-new-association-type "Permalink to this headline")

Create a new Association Type using a dedicated factory. Give the association a `code` and a `name` to easily recognize the type.

```

/\*\* @var ProductAssociationTypeInterface $associationType \*/

$associationType = $this->container->get('sylius.factory.product\_association\_type')->createNew();

$associationType->setCode('accessories');

$associationType->setName('Accessories');

```

To have the new association type in the system add it to the repository.

```

$this->container->get('sylius.repository.product\_association\_type')->add($associationType);

```

####### How to add a new Association to a Product?[¶](#how-to-add-a-new-association-to-a-product "Permalink to this headline")

Find in your system a product to which you would like to add an association. We will use a Go Pro camera as an example.

```

$product = $this->container->get('sylius.repository.product')->findOneBy(['code' => 'go-pro-camera']);

```

Next create a new Association which will connect our camera with its accessories. Such an association needs the AssociationType we have created

in the previous step above.

```

/\*\* @var ProductAssociationInterface $association \*/

$association = $this->container->get('sylius.factory.product\_association')->createNew();

/\*\* @var ProductAssociationTypeInterface $associationType \*/

$associationType = $this->container->get('sylius.repository.product\_association\_type')->findOneBy(['code' => 'accessories']);

$association->setType($associationType);

```

Let’s add all products from a certain taxon to the association we have created.

To do that find a desired taxon by code and get all its products. Perfect accessories for a camera will be SD cards.

```

/\*\* @var TaxonInterface $taxon \*/

$taxon = $this->container->get('sylius.repository.taxon')->findOneBy(['code' => 'sd-cards']);

$associatedProducts = $this->container->get('sylius.repository.product')->findByTaxon($taxon);

```

Having a collection of products from the SD cards taxon iterate over them and add them one by one to the association.

```

foreach ($associatedProducts as $associatedProduct) {

    $association->addAssociatedProduct($associatedProduct);

}

```

Finally add the created association with SD cards to our Go Pro camera product.

```

$product->addAssociation($association);

```

And to save everything in the database you need to add the created association to the repository.

```

$this->container->get('sylius.repository.product\_association')->add($association);

```

In the previous example we used a custom query in the product repository, here is the implementation:

```

use Sylius\Bundle\CoreBundle\Doctrine\ORM\ProductRepository as BaseProductRepository;

class ProductRepository extends BaseProductRepository

{

    public function findByTaxon(Taxon $taxon): array

    {

        return $this->createQueryBuilder('p')

            ->join('p.productTaxons', 'pt')

            ->where('pt.taxon = :taxon')

            ->setParameter('taxon', $taxon)

            ->getQuery()

            ->getResult()

         ;

    }

}

```

####### Learn more:[¶](#learn-more "Permalink to this headline")

* [Product - Concept Documentation](index.html#document-book/products/products)

###### Attributes[¶](#attributes "Permalink to this headline")

Attributes in Sylius are used to describe traits shared among entities. The best example are products, that may be of

the same category and therefore they will have many similar attributes such as **number of pages for a book**,

**brand of a T-shirt** or simply **details of any product**.

####### Attribute[¶](#attribute "Permalink to this headline")

The **Attribute** model has a translatable name (like for instance `Book pages`), code (`book\_pages`) and type (`integer`).

There are a few available types of an Attribute:

* text (*default*)

* checkbox

* integer

* percent

* textarea

* date

* datetime

* select

What these types may be useful for?

* text - brand of a T-Shirt

* checkbox - show whether a T-Shirt is made of cotton or not

* integer - number of elements when a product is a set of items.

* percent - show how much cotton is there in a piece of clothing

* textarea - display more detailed information about a product

* date - release date of a movie

* datetime - accurate date and time of an event

* select - genre(s) of a book. one or more of them can be selected

####### Non-translatable attribute[¶](#non-translatable-attribute "Permalink to this headline")

Some attributes (dates, author name) don’t need a different value in each locale. For those attributes, we introduced the possibility to disable translation.

Shop Owner declares values only once and regardless of the chosen locale customer will see a proper attribute value.

Warning

Once the attribute has disabled translatability it will erase attribute values in all locales for this attribute.

####### How to create an Attribute?[¶](#how-to-create-an-attribute "Permalink to this headline")

To give you a better insight into Attributes, let’s have a look how to prepare and add an Attribute with a Product to the system programatically.

To assign Attributes to Products firstly you will need a factory for ProductAttributes.

The AttributeFactory has a special method createTyped($type), where $type is a string.

The Attribute needs a `code` and a `name` before it can be saved in the repository.

```

/\*\* @var AttributeFactoryInterface $attributeFactory \*/

$attributeFactory = $this->container->get('sylius.factory.product\_attribute');

/\*\* @var AttributeInterface $attribute \*/

$attribute = $attributeFactory->createTyped('text');

$attribute->setName('Book cover');

$attribute->setCode('book\_cover');

$this->container->get('sylius.repository.product\_attribute')->add($attribute);

```

In order to assign value to your Attribute you will need a factory of ProductAttributeValues,

use it to create a new value object.

```

/\*\* @var FactoryInterface $attributeValueFactory \*/

$attributeValueFactory = $this->container->get('sylius.factory.product\_attribute\_value');

/\*\* @var AttributeValueInterface $hardcover \*/

$hardcover = $attributeValueFactory->createNew();

```

Attach the new AttributeValue to your Attribute and set its `value`, which is what will be rendered in frontend.

```

$hardcover->setAttribute($attribute);

$hardcover->setValue('hardcover');

```

Finally let’s find a product that will have your newly created attribute.

```

/\*\* @var ProductInterface $product \*/

$product = $this->container->get('sylius.repository.product')->findOneBy(['code' => 'code']);

$product->addAttribute($hardcover);

```

Now let’s see what has to be done if you would like to add an attribute of `integer` type. Let’s find such a one in the repository,

it will be for example the `BOOK-PAGES` attribute.

```

/\*\* @var AttributeInterface $bookPagesAttribute \*/

$bookPagesAttribute = $this->container->get('sylius.repository.product\_attribute')->findOneBy(['code' => 'BOOK-PAGES']);

/\*\* @var AttributeValueInterface $pages \*/

$pages = $attributeValueFactory->createNew();

$pages->setAttribute($bookPagesAttribute);

$pages->setValue(500);

$product->addAttribute($pages);

```

After adding attributes remember to **flush the product manager**.

```

$this->container->get('sylius.manager.product')->flush();

```

Your Product will now have two Attributes.

####### Learn more[¶](#learn-more "Permalink to this headline")

* [Attribute - Component Documentation](index.html#document-components_and_bundles/components/Attribute/index)

###### Pricing[¶](#pricing "Permalink to this headline")

Pricing is a part of Sylius responsible for providing the product prices per channel.

Note

All prices in Sylius are saved in the **base currency** of each channel separately.

####### Price and Original Price[¶](#price-and-original-price "Permalink to this headline")

Price - this is the current price of the product variant displayed in the catalog. It can be modified explicitly by i.e. catalog promotions.

Original price - this is the price of the product variant it is displayed as crossed-out in the catalog. It is used as the base for current price calculations. If this value is not defined, Catalog Promotion logic will copy value from Price to Original Price.

####### Minimum Price[¶](#minimum-price "Permalink to this headline")

Minimum Price is the price below which, any promotion can’t decrease price anymore.

It works with Catalog Promotion (it is minimum price in the Product Catalog) and with Cart Promotion (it is a minimum price of product unit in cart).

For example if product should have 5 different promotions, but after third product’s price will be below minimum price,

third promotion will decrease price only to minimum price, rest of promotions will not be applied at all.

####### Currency per Channel[¶](#currency-per-channel "Permalink to this headline")

As you already know Sylius operates on [Channels](index.html#document-book/configuration/channels).

Each channel has a **base currency** in which all prices are saved.

Note

Whenever you operate on concepts that have specified values per channel (like ProductVariant’s price, Promotion’s fixed discount etc.)

####### Exchange Rates[¶](#exchange-rates "Permalink to this headline")

Each currency defined in the system should have an ExchangeRate configured.

**ExchangeRate** is a separate entity that holds a relation between two currencies and specifies their exchange rate.

Exchange rates are used for viewing the *approximate* price in a currency different from the base currency of a channel.

####### Learn more[¶](#learn-more "Permalink to this headline")

* [Currency - Component Documentation](index.html#document-components_and_bundles/components/Currency/index)

* [Currencies Concept Documentation](index.html#document-book/configuration/currencies)

###### Catalog Promotions[¶](#catalog-promotions "Permalink to this headline")

The **Catalog Promotions** system in **Sylius** is a new way of dealing with promotions on multiple products.

If you get used to [Cart Promotions](index.html#document-book/orders/cart-promotions) this will be something familiar to you.

It is managed by combination of promotion scopes and actions, where you can specify on which e.g. products or taxons

you can specify the Catalog Promotion with your custom actions as well as actions like percentage discount.

It is possible to set start and end date for Catalog Promotions and their priority (Promotion will be applied on descending order of priority).

You can also set exclusiveness of promotion, in this case only one catalog promotion will be applied - exclusive one with highest priority.

You can assign the needed channels too.

Warning

Be aware that processing a big catalog of products can be time consuming.

Please consider a 2-10 minutes delay starting from the specified dates (it depends how big catalog you have).

####### Catalog Promotion Parameters[¶](#catalog-promotion-parameters "Permalink to this headline")

Catalog Promotion has a few basic parameters that represent it - a unique `code` and `name`:

Note

The parameter `code` should contain only letters, numbers, dashes and underscores (like all codes in Sylius).

We encourage you to use `snake\_case` codes.

```

{

 "code": "t\_shirt\_promotion" // unique

 "name": "T-shirt Promotion"

 // ...

}

```

Rest of the fields are used for configuration:

* **Channels** are used to define channels on which given promotion is applied:

```

{

 //...

 "channels": [

 "/api/v2/admin/channels/FASHION\_WEB", //IRI

 "/api/v2/admin/channels/HOME\_WEB"

 ]

 // ...

}

```

* **Scopes** are used to define scopes on which the catalog promotion will work:

```

{

 //...

 "scopes": [

 {

 "type": "for\_variants",

 "configuration": {

 "variants": [

 "Everyday\_white\_basic\_T\_Shirt-variant-1", //Variant Code

 "Everyday\_white\_basic\_T\_Shirt-variant-4"

 ]

 }

 }

 ]

 // ...

}

```

Note

The usage of Variant Code over IRI is in this case dictated by the kind of relationship.

Here it is a part of configuration, where e.g. channel is a relation to the resource.

For possible scopes see [Catalog Promotion Scopes configuration reference](#catalog-promotion-scopes-configuration-reference)

* **Actions** are used to defined what happens when the promotion is applied:

```

{

 //...

 "actions": [

 {

 "type": "percentage\_discount",

 "configuration": {

 "amount": 0.5 //float

 }

 }

 ]

 // ...

}

```

* **Translations** are used to define labels and descriptions for languages you are configuring:

```

{

 //...

 "translations": {

 "en\_US": {

 "label": "Summer discount",

 "description": "The grass so green, the sun so bright. Life seems a dream, no worries in sight.",

 "locale": "en\_US" //Locale Code

 }

 }

 }

 // ...

}

```

####### How to create a Catalog Promotion?[¶](#how-to-create-a-catalog-promotion "Permalink to this headline")

After we get to know with some basics of Catalog Promotion let’s see how we can create one:

* **API** The common use case is to make it through API, first you need to authorize yourself as an admin (you don’t want to let a guest create it - don’t you?).

Tip

Check this doc [Authorization](index.html#document-book/api/authorization) if you are having trouble with login in.

And let’s call the POST endpoint to create very basic catalog promotion:

```

curl -X 'POST' \

 'https://hostname/api/v2/admin/catalog-promotions' \

 -H 'accept: application/ld+json' \

 -H 'Authorization: Bearer authorizationToken' \

 -H 'Content-Type: application/ld+json' \

 -d '{

 "code": "t\_shirt\_promotion",

 "name": "T-shirt Promotion"

 }'

```

If everything was fine, the server will respond with 201 status code.

This means you have created a simple catalog promotion with `name` and `code` only.

You can check if the catalog promotion exists by using GET endpoint

```

curl -X 'GET' \

'https://hostname/api/v2/admin/catalog-promotions'

```

* **Programmatically** Similar to cart promotions you can use factory to create a new catalog promotion:

```

/\*\* @var CatalogPromotionInterface $promotion \*/

$promotion = $this->container->get('sylius.factory.catalog\_promotion')->createNew();

$promotion->setCode('t\_shirt\_promotion');

$promotion->setName('T-shirt Promotion');

```

Note

Take into account that both the API and Programmatically added catalog promotions in this shape are not really useful.

You need to add configurations to them so they make any business valued changes.

####### How to create a Catalog Promotion Scope and Action?[¶](#how-to-create-a-catalog-promotion-scope-and-action "Permalink to this headline")

The creation of Catalog Promotion was quite simple but at this shape it has no real functionality. Let’s add scope and action:

In API we will extend last command:

```

curl -X 'POST' \

 'https://hostname/api/v2/admin/catalog-promotions' \

 -H 'accept: application/ld+json' \

 -H 'Authorization: Bearer authorizationToken' \

 -H 'Content-Type: application/ld+json' \

 -d '{

 "code": "t\_shirt\_promotion",

 "name": "T-shirt Promotion",

 "channels": [

 "/api/v2/admin/channels/FASHION\_WEB"

 ],

 "scopes": [

 {

 "type": "for\_variants",

 "configuration": {

 "variants": ["Everyday\_white\_basic\_T\_Shirt-variant-1", "Everyday\_white\_basic\_T\_Shirt-variant-4"]

 }

 }

 ],

 "actions": [

 {

 "type": "percentage\_discount",

 "configuration": {

 "amount": 0.5

 }

 }

 ],

 "translations": {

 "en\_US": {

 "label": "T-shirt Promotion",

 "description": "T-shirt Promotion description",

 "locale": "en\_US"

 }

 }'

```

This will create a catalog promotions with relations to Scope `for\_variants`, Action `percentage\_discount` and also

translation for `en\_US` locale.

We can also make it programmatically:

```

/\*\* @var CatalogPromotionInterface $catalogPromotion \*/

$catalogPromotion = $this->container->get('sylius.factory.catalog\_promotion')->createNew();

$catalogPromotion->setCode('t\_shirt\_promotion');

$catalogPromotion->setName('T-shirt Promotion');

$catalogPromotion->setCurrentLocale('en\_US');

$catalogPromotion->setFallbackLocale('en\_US');

$catalogPromotion->setLabel('T-shirt Promotion');

$catalogPromotion->setDescription('T-shirt Promotion description');

$catalogPromotion->addChannel('FASHION\_WEB');

/\*\* @var CatalogPromotionScopeInterface $catalogPromotionScope \*/

$catalogPromotionScope = $this->container->get('sylius.factory.catalog\_promotion\_scope')->createNew();

$catalogPromotionScope->setCatalogPromotion($catalogPromotion);

$catalogPromotion->addScope($catalogPromotionScope);

/\*\* @var CatalogPromotionActionInterface $catalogPromotionAction \*/

$catalogPromotionAction = $this->container->get('sylius.factory.catalog\_promotion\_action')->createNew();

$catalogPromotionAction->setCatalogPromotion($catalogPromotion);

$catalogPromotion->addAction($catalogPromotionAction);

/\*\* @var MessageBusInterface $eventBus \*/

$eventBus = $this->container->get('sylius.event\_bus');

$eventBus->dispatch(new CatalogPromotionCreated($catalogPromotion->getCode()));

```

And now you should be able to see created Catalog Promotion. You can check if it exists like in the last example (with GET endpoint).

If you look into `product-variant` endpoint in shop you should see now that chosen variants have lowered price and added field `appliedPromotions`:

```

curl -X 'GET' \

'https://hostname/api/v2/shop/product-variant/Everyday\_white\_basic\_T\_Shirt-variant-1'

```

```

// response content

{

 "@context": "/api/v2/contexts/ProductVariant",

 "@id": "/api/v2/shop/product-variants/Everyday\_white\_basic\_T\_Shirt-variant-1",

 // ...

 "price": 2000,

 "originalPrice": 4000,

 "appliedPromotions": {

 "T-shirt Promotion": {

 "name": "T-shirt Promotion",

 "description": "T-shirt Promotion description"

 }

 },

 "inStock": true

}

```

Note

If you create a Catalog Promotion programmatically, remember to manually dispatch `CatalogPromotionCreated`

######## Catalog Promotion Scopes configuration reference[¶](#catalog-promotion-scopes-configuration-reference "Permalink to this headline")

| Scope type | Scope Configuration Array |

| --- | --- |

| `for\_products` | `['products' => [$productCode]]` |

| `for\_taxons` | `['taxons' => [$taxonCode]]` |

| `for\_variants` | `['variants' => [$variantCode]]` |

######## Catalog Promotion Actions configuration reference[¶](#catalog-promotion-actions-configuration-reference "Permalink to this headline")

| Action type | Action Configuration Array |

| --- | --- |

| `fixed\_discount` | `[$channelCode => ['amount' => $amountInteger]]` |

| `percentage\_discount` | `['amount' => $amountFloat]` |

####### Catalog Promotion asynchronicity[¶](#catalog-promotion-asynchronicity "Permalink to this headline")

Applying Catalog Promotion to the product catalog is an asynchronous operation.

It means that new prices will not be updated right after confirmation of creating or updating Catalog Promotion but after some time.

This delay depends on the size of the product catalog in the shop.

Another effect of this approach is the possibility to create Catalog Promotion with the future date (processing will start in given start date).

To make the Catalog Promotion application asynchronously we are using SymfonyMessenger and queue provided by Doctrine.

After changes in CatalogPromotion, we dispatch proper message with delay calculated from provided dates.

Warning

To enable asynchronous Catalog Promotion, remember about running messenger consumer in a separate process, use the command: `php bin/console messenger:consume main catalog\_promotion\_removal`

For more information check official [Symfony docs](https://symfony.com/doc/current/messenger.html#consuming-messages-running-the-worker)

Note

The reason why we use two transports is explained below in the Catalog Promotion removal section.

####### Catalog promotion synchronicity[¶](#catalog-promotion-synchronicity "Permalink to this headline")

TL;DR: There is no reason to use synchronous processing unless you have only a few products and manually activate and deactivate catalog promotions.

Since the main transport is asynchronous by default, in order to use synchronous processing, you need to override configuration by creating the config/packages/messenger.yaml file and pasting the following configuration:

```

framework:

 messenger:

 transports:

 main: 'sync://'

```

Synchronous processing means that, after submitting a catalog promotion form, one must wait for a server response until processing is complete.

This may cause a worse user experience if there are other catalog promotions as it will trigger their recalculation as well.

It is not possible to schedule the start of a catalog promotion on a given start date, it has to be started manually after that start date.

It can be done by creating or editing any promotion.

The same happens with the end date, so if the catalog promotion should have expired, it will not become inactive automatically, inactivation has to be triggered manually.

####### How the Catalog Promotions are applied?[¶](#how-the-catalog-promotions-are-applied "Permalink to this headline")

The Catalog Promotion application process utilises [API Platform events](https://api-platform.com/docs/core/events/) for an API

and [Resource events](/book/architecture/events) for UI. When a new Promotion is created there are services that listen

on proper events and dispatch `CatalogPromotionCreated` event to event bus. The behaviour looks similar when the existing

one is edited, then the `CatalogPromotionUpdated` event is dispatched to event bus.

This event is handled by [CatalogPromotionUpdateListener](https://github.com/Sylius/Sylius/blob/master/src/Sylius/Bundle/CoreBundle/Listener/CatalogPromotionUpdateListener.php) which resolves the appropriate `CatalogPromotion`.

With the needed data and configuration from `CatalogPromotion` we can now process the Product Catalog.

Any changes in Catalog Promotion cause recalculations of entire Product Catalog ([BatchedApplyCatalogPromotionsOnVariantsCommandDispatcher](https://github.com/Sylius/Sylius/blob/master/src/Sylius/Bundle/CoreBundle/CommandDispatcher/BatchedApplyCatalogPromotionsOnVariantsCommandDispatcher.php) is called, which dispatch events [ApplyCatalogPromotionsOnVariants](https://github.com/Sylius/Sylius/blob/master/src/Sylius/Bundle/CoreBundle/Command/ApplyCatalogPromotionsOnVariants.php))

Note

If you want to reapply Catalog Promotion manually you can refer to the [How to create a Catalog Promotion Scope and Action?](#how-to-create-a-catalog-promotion-scope-and-action) section

####### Removal of catalog promotion[¶](#removal-of-catalog-promotion "Permalink to this headline")

Removal of the catalog promotion consists in turning off the promotion, recalculation of the catalog

and the actual removal of the promotion resource. By using catalog promotions in asynchronous mode,

it is necessary to start the worker for two transports, the **main** transport is responsible for recalculation of the catalog

and the **catalog\_promotion\_removal** transport is self explanatory. The order of transports provided as the command arguments is important,

it is responsible for the priority of consumed messages:

```

php bin/console messenger:consume main catalog_promotion_removal

```

Each transport has its own failure transport, which is responsible for storing messages that haven’t been processed for some reason.

For the **main** transport it is the **main\_failed** transport and for the **catalog\_promotion\_removal** transport it is the **catalog\_promotion\_removal\_failed** transport.

You can read more about failure handling in the [Symfony Messenger documentation](https://symfony.com/doc/current/messenger.html#retries-failures).

For synchronous processing, no additional configuration is required.

####### How to manage catalog promotion priority?[¶](#how-to-manage-catalog-promotion-priority "Permalink to this headline")

The main feature of prioritizing catalog promotions is ensuring the uniqueness of priorities.

While it is obvious at first glance, there are some extreme cases that will be covered below

######## Creating a new catalog promotion while other catalog promotions already exist[¶](#creating-a-new-catalog-promotion-while-other-catalog-promotions-already-exist "Permalink to this headline")

* adding a catalog promotion with a priority higher than all existing catalog promotions does not change priority values

* adding a catalog promotion with a priority lower than all existing ones increases the priority value of other catalog promotions by 1

* adding a catalog promotion with a priority equal to one of the existing catalog promotions increases the priority value of all catalog promotions with a priority greater equal than created catalog promotion by 1, but has no effect on the others

* adding a catalog promotion with a priority equal -1 sets a priority value of the created promotion one greater than the current highest value

* adding a catalog promotion with some negative priority lower than -1 determines the position of the created catalog promotion starting count backward and if calculated index is already taken increases the priority value of all catalog promotions with a priority greater equal than calculated priority value by 1

######## Updating an existing catalog promotion[¶](#updating-an-existing-catalog-promotion "Permalink to this headline")

* updating a catalog promotion priority to one of the existing catalog promotions decreases its priority value by 1

####### Learn more[¶](#learn-more "Permalink to this headline")

* [Cart Promotions](index.html#document-book/orders/cart-promotions)

###### Taxons[¶](#taxons "Permalink to this headline")

We understand Taxons in Sylius as you would normally define categories.

Sylius gives you a possibility to categorize your products in a very flexible way, which is one of the most vital functionalities

of the modern e-commerce systems.

The Taxons system in Sylius works in a hierarchical way.

Let’s see exemplary categories trees:

```

Category

 |

 |\__ Clothes

 |         \_ T-Shirts

 |          \_ Shirts

 |           \_ Dresses

 |            \_ Shoes

 |

 \__ Books

         \_ Fantasy

          \_ Romance

           \_ Adventure

            \_ Other

Gender

 |

 \_ Male

  \_ Female

```

####### How to create a Taxon?[¶](#how-to-create-a-taxon "Permalink to this headline")

As always with Sylius resources, to create a new object you need a factory.

If you want to create a single, not nested category:

```

/\*\* @var FactoryInterface $taxonFactory \*/

$taxonFactory = $this->get('sylius.factory.taxon');

/\*\* @var TaxonInterface $taxon \*/

$taxon = $taxonFactory->createNew();

$taxon->setCode('category');

$taxon->setName('Category');

```

But if you want to have a tree of categories, create another taxon and add it as a **child** to the previously created one.

```

/\*\* @var TaxonInterface $childTaxon \*/

$childTaxon = $taxonFactory->createNew();

$childTaxon->setCode('clothes');

$childTaxon->setName('Clothes');

$taxon->addChild($childTaxon);

```

Finally **the parent taxon** has to be added to the system using a repository, all its child taxons will be added with it.

```

/\*\* @var TaxonRepositoryInterface $taxonRepository \*/

$taxonRepository = $this->get('sylius.repository.taxon');

$taxonRepository->add($taxon);

```

####### How to assign a Taxon to a Product?[¶](#how-to-assign-a-taxon-to-a-product "Permalink to this headline")

In order to categorize products you will need to assign your taxons to them - via the `addProductTaxon()` method on Product.

```

/\*\* @var ProductInterface $product \*/

$product = $this->container->get('sylius.factory.product')->createNew();

$product->setCode('product\_test');

$product->setName('Test');

/\*\* @var TaxonInterface $taxon \*/

$taxon = $this->container->get('sylius.factory.taxon')->createNew();

$taxon->setCode('food');

$taxon->setName('Food');

/\*\* @var RepositoryInterface $taxonRepository \*/

$taxonRepository = $this->container->get('sylius.repository.taxon');

$taxonRepository->add($taxon);

/\*\* @var ProductTaxonInterface $productTaxon \*/

$productTaxon = $this->container->get('sylius.factory.product\_taxon')->createNew();

$productTaxon->setTaxon($taxon);

$productTaxon->setProduct($product);

$product->addProductTaxon($productTaxon);

/\*\* @var EntityManagerInterface $productManager \*/

$productManager = $this->container->get('sylius.manager.product');

$productManager->persist($product);

$productManager->flush();

```

####### What is the mainTaxon of a Product?[¶](#what-is-the-maintaxon-of-a-product "Permalink to this headline")

The product entity in Sylius core has a field `mainTaxon`. This field is used, for instance, for breadcrumbs generation.

But you can also use it for your own logic, like for instance links generation.

To set it on your product you need to use the `setMainTaxon()` method.

####### Learn more[¶](#learn-more "Permalink to this headline")

* [Taxonomy - Bundle Documentation](index.html#document-components_and_bundles/bundles/SyliusTaxonomyBundle/index)

* [taxonomy - Component Documentation](index.html#document-components_and_bundles/components/Taxonomy/index)

###### Inventory[¶](#inventory "Permalink to this headline")

Sylius leverages a very simple approach to inventory management. The current stock of an item is stored on the **ProductVariant** entity as the `onHand` value.

####### InventoryUnit[¶](#inventoryunit "Permalink to this headline")

InventoryUnit has a relation to a [Stockable](https://github.com/Sylius/Sylius/blob/master/src/Sylius/Component/Inventory/Model/StockableInterface.php) on it,

in case of Sylius Core it will be a relation to the **ProductVariant** that implements the StockableInterface on the **OrderItemUnit** that implements the InventoryUnitInterface.

It represents a physical unit of the product variant that is in the shop.

####### Inventory On Hold[¶](#inventory-on-hold "Permalink to this headline")

Putting inventory items `onHold` is a way of reserving them before the customer pays for the order. Items are put on hold when the checkout is completed.

Tip

Putting items `onHold` does not remove them from `onHand` yet. If a customer buys 2 tracked items out of 5 being

in the inventory (`5 onHand`), after the checkout there will be `5 onHand` and `2 onHold`.

####### Availability Checker[¶](#availability-checker "Permalink to this headline")

There is a service that will help you check the availability of items in the inventory

- [AvailabilityChecker](https://github.com/Sylius/Sylius/blob/master/src/Sylius/Component/Inventory/Checker/AvailabilityChecker.php).

It has two methods `isStockAvailable` (is there at least one item available) and `isStockSufficient` (is there a given amount of items available).

Tip

There are two respective twig functions for checking inventory: `sylius\_inventory\_is\_available` and `sylius\_inventory\_is\_sufficient`.

####### OrderInventoryOperator[¶](#orderinventoryoperator "Permalink to this headline")

Inventory Operator is the service responsible for managing the stock amounts of every *ProductVariant* on an Order with the following methods:

* `hold` - is called when the order’s checkout is completed, it puts the inventory units onHold, while still not removing them from onHand,

* `sell` - is called when the order’s payment are assigned with the state `paid`. The inventory items are then removed from onHold and onHand,

* `release` - is a way of making onHold items of an order back to only onHand,

* `giveBack` - is a way of returning sold items back to the inventory onHand,

* `cancel` - this method works both when the order is paid and unpaid. It uses both `giveBack` and `release` methods.

####### How does Inventory work on examples?[¶](#how-does-inventory-work-on-examples "Permalink to this headline")

Tip

You can see all use cases we have designed in Sylius in our [Behat scenarios for inventory](https://github.com/Sylius/Sylius/tree/master/features/inventory).

####### Learn more[¶](#learn-more "Permalink to this headline")

* [[Plus] Multi-Source Inventory](index.html#document-book/products/multi_source_inventory)

* [Order concept documentation](index.html#document-book/orders/orders)

* [Inventory Bundle documentation](index.html#document-components_and_bundles/bundles/SyliusInventoryBundle/index)

* [Inventory Component documentation](index.html#document-components_and_bundles/components/Inventory/index)

###### Multi-Source Inventory[¶](#multi-source-inventory "Permalink to this headline")

Sylius Plus has a much more complex approach to inventory management than the open source version. Unlike the open source

version, that allows to specify one stock amount value for each variant, the Sylius Plus Multi-Source Inventory gives you

a possibility to create several Inventory Sources and specify different stock amounts of variants for different Inventory Sources.

Admin can create multiple Inventory Sources (IS). For each IS they can choose Channels it will be available for.

Let’s say you have 2 channels - “DACH” and “France”, and you have three magazines in Paris, in Berlin, and in Vienna,

so you’d probably want the “France” channel to be fulfilled from the Parisian magazine only.

The experience for the Customer is seamless, they are not aware of multiple magazines, their orders are fulfilled just

like it was. As an Administrator, you will additionally see which Inventory Source was chosen for the Order’s shipment

to be fulfilled from. The system is prepared to be supporting shipments splitting on one order and fulfilling from multiple

magazines in the near future.

####### Inventory Source[¶](#inventory-source "Permalink to this headline")

Inventory Source is the place from which a shipment of an order will be shipped from, it can be understood as a magazine,

a fulfillment centre, physical store etc.

Administrators can add, modify and remove Inventory Sources in the admin panel.

![_images/inventory_sources_index.png](_images/inventory_sources_index.png)

Each IS has its own inventory management page, where you can manage stock levels of all items available int his inventory.

![_images/inventory_source_stock_management.png](_images/inventory_source_stock_management.png)

In order to make a product tracked within an Inventory Source, you have to go to it’s ProductVariant’s Inventory tab on the edit page.

![_images/product_variant_stock_management.png](_images/product_variant_stock_management.png)

After an Order with tracked products is placed you will see from which IS its Shipment should be shipped.

![_images/inventory_source_shipment.png](_images/inventory_source_shipment.png)

####### InventorySourceStock on ProductVariant[¶](#inventorysourcestock-on-productvariant "Permalink to this headline")

Each simple Product and each Product Variant of a configurable Product can have stock in the Inventory Sources available

for channels the product is available in. You can specify stocks on products and then manage them also in the Inventory section,

where you will see the inventory of each IS separately.

`inventorySourceStock` is a property that behaves exactly like the `stockAmount` field you know from open source single-source

inventory, so it has both `onHand` and `onHold` values that are modified when Orders are placed and fulfilled in your shop.

The tracking of a Product can be disabled.

####### Inventory Source resolving[¶](#inventory-source-resolving "Permalink to this headline")

The current implementation provides one main resolver, that can use multiple inventory sources filters.

There are three filters provided by default:

* *Sufficient*, with `priority = 0`, that provides all inventory sources able to handle all ordered products;

* *EnabledChannel*, with `priority = 8`, that provides inventory sources enabled for the current channel;

* *Priority*, with `priority = -256`, that sorts filtered inventory sources based on their priority;

Filters always return an array of inventory sources, however resolver picks the first of them or throws an

`UnresolvedInventorySource` exception if no inventory source can be resolved.

It’s possible to add more inventory sources filter, with higher or lower priority. Such a service must implement `Sylius\Plus\Inventory\Application\Filter\InventorySourcesFilterInterface` and be registered with `sylius\_plus.inventory.inventory\_sources\_filter` tag, with priority attribute set.

Note

How to create a custom Inventory Sources Filter? Read [this Cookbook](index.html#document-cookbook/index).

####### Resolving InventorySourceStock for ordered products[¶](#resolving-inventorysourcestock-for-ordered-products "Permalink to this headline")

Warning

Standard Sylius distribution is releasing a stock inventory when the whole order is paid, while in Plus version,

it has been switched to be released after shipment has been shipped.

####### How does Multi-Source Inventory work on examples?[¶](#how-does-multi-source-inventory-work-on-examples "Permalink to this headline")

Tip

You can see all use cases we have designed in Sylius Plus by browsing the Behat scenarios for inventory in the vendor package

after installing Sylius Plus.

####### Multi-source inventory fixtures[¶](#multi-source-inventory-fixtures "Permalink to this headline")

######## Inventory Sources fixtures[¶](#inventory-sources-fixtures "Permalink to this headline")

This fixture creates Inventory Sources without products (empty) enabled in chosen channels:

```

hamburg\_warehouse:

 code: 'hamburg\_warehouse'

 name: 'Hamburg Warehouse'

 priority: 10

 channels:

 - 'HOME\_WEB'

 - 'FASHION\_WEB'

```

######## Inventory Source Stocks fixtures[¶](#inventory-source-stocks-fixtures "Permalink to this headline")

This fixture adds inventory source stock to chosen Inventory Source, you can choose which taxons and channels you want

to include in each inventory source.

When declaring both options, a union of sets will be resolved.

```

stocks\_in\_frankfurt\_warehouse:

 inventory\_source: 'frankfurt\_warehouse'

 products\_from:

 taxons\_codes:

 - 'caps'

 - 'dresses'

 channels\_codes:

 - 'HOME\_WEB'

 - 'FASHION\_WEB'

```

####### Learn more[¶](#learn-more "Permalink to this headline")

* [Cookbook: How to create a custom inventory sources filter?](index.html#document-cookbook/inventory/custom-inventory-sources-filter)

* [Order concept documentation](index.html#document-book/orders/orders)

* [Single Source Inventory concept documentation](index.html#document-book/products/inventory)

[![_images/banner7.png](_images/banner7.png)](https://sylius.com/plus/?utm_source=docs)

###### Search[¶](#search "Permalink to this headline")

Having a products search functionality in an eCommerce system is a very popular use case.

Sylius provides a products search functionality that is a grid filter.

####### Grid filters[¶](#grid-filters "Permalink to this headline")

For simple use cases of products search use the **filters of grids**.

For example, the shop’s categories each have a `search` filter in the products grid:

```

# Sylius/Bundle/ShopBundle/Resources/config/grids/product.yml

filters:

 search:

 type: string

 label: false

 options:

 fields: [translation.name]

 form\_options:

 type: contains

```

It searches by product names that contains a string that the user typed in the search bar.

The search bar looks like below:

![_images/search.png](_images/search.png)

######## Customizing search filter[¶](#customizing-search-filter "Permalink to this headline")

The search bar in many shops should be more sophisticated, than just a simple text search. You may need to add

searching by price, reviews, sizes or colors.

If you would like to extend this built-in functionality read

[this article about grids customizations](index.html#document-customization/grid), and [the GridBundle docs](https://github.com/Sylius/SyliusGridBundle/blob/master/docs/index.md).

####### ElasticSearch[¶](#elasticsearch "Permalink to this headline")

When the grids filtering is not enough for you, and your needs are more complex you should go for the

[ElasticSearch](https://www.elastic.co/products/elasticsearch) integration.

There is the [BitBagCommerce/SyliusElasticsearchPlugin](https://github.com/BitBagCommerce/SyliusElasticsearchPlugin) integration extension,

which you can use to extend Sylius functionalities with ElasticSearch.

All you have to do is require the plugin in your project via composer, install the ElasticSearch server, and configure ElasticSearch

in your application. Everything is well described in the BitBagCommerce/SyliusElasticsearchPlugin’s readme.

####### Learn more[¶](#learn-more "Permalink to this headline")

* [SyliusElasticSearchPlugin](https://github.com/BitBagCommerce/SyliusElasticsearchPlugin)

* [GridBundle documentation](https://github.com/Sylius/SyliusGridBundle/blob/master/docs/index.md)

* [Products](index.html#document-book/products/products)

* [Product Reviews](index.html#document-book/products/product_reviews)

* [Product Associations](index.html#document-book/products/product_associations)

* [Attributes](index.html#document-book/products/attributes)

* [Pricing](index.html#document-book/products/pricing)

* [Catalog Promotions](index.html#document-book/products/catalog_promotions)

* [Taxons](index.html#document-book/products/taxons)

* [Inventory](index.html#document-book/products/inventory)

* [Multi-Source Inventory](index.html#document-book/products/multi_source_inventory)

* [Search](index.html#document-book/products/search)

* [Products](index.html#document-book/products/products)

* [Product Reviews](index.html#document-book/products/product_reviews)

* [Product Associations](index.html#document-book/products/product_associations)

* [Attributes](index.html#document-book/products/attributes)

* [Pricing](index.html#document-book/products/pricing)

* [Catalog Promotions](index.html#document-book/products/catalog_promotions)

* [Taxons](index.html#document-book/products/taxons)

* [Inventory](index.html#document-book/products/inventory)

* [Multi-Source Inventory](index.html#document-book/products/multi_source_inventory)

* [Search](index.html#document-book/products/search)

#### Carts & Orders[¶](#carts-orders "Permalink to this headline")

In this chapter you will learn everything you need to know about orders in Sylius.

This concept comes together with a few additional ones, like promotions, payments, shipments or checkout in general.

You should also have a look here if you are looking for Cart, which is in Sylius an Order in the `cart` state.

##### Carts & Orders[¶](#carts-orders "Permalink to this headline")

In this chapter you will learn everything you need to know about orders in Sylius. This concept comes together

with a few additional ones, like cart promotions, payments, shipments or checkout in general.

Warning

**Cart** in Sylius is in fact an Order in the state `cart`.

###### Orders[¶](#orders "Permalink to this headline")

**Order** model is one of the most important in Sylius, where many concepts of e-commerce meet.

It represents an order that can be either placed or in progress (cart).

**Order** holds a collection of **OrderItem** instances, which represent products from the shop,

as its physical copies, with chosen variants and quantities.

Each Order is **assigned to the channel** in which it has been created as well as the **language** the customer was using

while placing the order. The order currency code will be the base currency of the current channel by default.

####### How to create an Order programmatically?[¶](#how-to-create-an-order-programmatically "Permalink to this headline")

To programmatically create an Order you will of course need a factory.

```

/\*\* @var FactoryInterface $orderFactory \*/

$orderFactory = $this->container->get('sylius.factory.order');

/\*\* @var OrderInterface $order \*/

$order = $orderFactory->createNew();

```

Then get a channel to which you would like to add your Order. You can get it from the context or from the repository by code for example.

```

/\*\* @var ChannelInterface $channel \*/

$channel = $this->container->get('sylius.context.channel')->getChannel();

$order->setChannel($channel);

```

Next give your order a locale code.

```

/\*\* @var string $localeCode \*/

$localeCode = $this->container->get('sylius.context.locale')->getLocaleCode();

$order->setLocaleCode($localeCode);

```

And a currency code:

```

$currencyCode = $this->container->get('sylius.context.currency')->getCurrencyCode();

$order->setCurrencyCode($currencyCode);

```

What is more the proper Order instance should also have the **Customer** assigned.

You can get it from the repository by email.

```

/\*\* @var CustomerInterface $customer \*/

$customer = $this->container->get('sylius.repository.customer')->findOneBy(['email' => 'shop@example.com']);

$order->setCustomer($customer);

```

A very important part of creating an Order is adding **OrderItems** to it.

Assuming that you have a **Product** with a **ProductVariant** assigned already in the system:

```

/\*\* @var ProductVariantInterface $variant \*/

$variant = $this->container->get('sylius.repository.product\_variant')->findOneBy([]);

// Instead of getting a specific variant from the repository

// you can get the first variant of off a product by using $product->getVariants()->first()

// or use the \*\*VariantResolver\*\* service - either the default one or your own.

// The default product variant resolver is available at id - 'sylius.product\_variant\_resolver.default'

/\*\* @var OrderItemInterface $orderItem \*/

$orderItem = $this->container->get('sylius.factory.order\_item')->createNew();

$orderItem->setVariant($variant);

```

In order to change the amount of items use the **OrderItemQuantityModifier**.

```

$this->container->get('sylius.order\_item\_quantity\_modifier')->modify($orderItem, 3);

```

You can also change **maximum order item quantity** parameter in `config/services.xml`.

```

<!-- config/services.xml -->

<parameters>

 <parameter key="sylius.order\_item\_quantity\_modifier.limit">9999</parameter> # by default it is 9999

</parameters>

```

Add the item to the order. And then call the **CompositeOrderProcessor** on the order to have

everything recalculated.

```

$order->addItem($orderItem);

$this->container->get('sylius.order\_processing.order\_processor')->process($order);

```

Warning

By default all the **OrderProcessors** only work on orders with state **cart** as the processing has been tailored to work in checkout.

If you’d like to use that logic for orders with different states you will need to change the **Order**’s *canBeProcessed* method and probably apply other customizations fitting your case.

Note

This **CompositeOrderProcessor** is one of the most powerful concepts. It handles whole order calculation logic and allows

for really granular operations over the order. It is called multiple times in the checkout process, and internally it works like this:

![_images/sylius_order_processor.png](_images/sylius_order_processor.png)

Finally you have to save your order using the repository.

```

/\*\* @var OrderRepositoryInterface $orderRepository \*/

$orderRepository = $this->container->get('sylius.repository.order');

$orderRepository->add($order);

```

####### The Order State Machine[¶](#the-order-state-machine "Permalink to this headline")

Order has also its own state, which can have the following values:

* `cart` - before the checkout is completed, it is the initial state of an Order,

* `new` - when checkout is completed the cart is transformed into a `new` order,

* `fulfilled` - when the order payments and shipments are completed,

* `cancelled` - when the order was cancelled.

[![_images/sylius_order.png](_images/sylius_order.png)](_images/sylius_order.png)

Tip

The state machine of order is an obvious extension to the [state machine of checkout](index.html#document-book/orders/checkout).

####### Shipments of an Order[¶](#shipments-of-an-order "Permalink to this headline")

An **Order** in Sylius holds a collection of Shipments on it. Each shipment in that collection has its own shipping method and has its own state machine.

This lets you divide an order into several different shipments that have own shipping states (like sending physical objects via DHL and sending a link to downloadable files via e-mail).

Tip

If you are not familiar with the shipments concept [check the documentation](index.html#document-book/orders/shipments).

######## State machine of Shipping in an Order[¶](#state-machine-of-shipping-in-an-order "Permalink to this headline")

[![_images/sylius_order_shipping.png](_images/sylius_order_shipping.png)](_images/sylius_order_shipping.png)

######## How to add a Shipment to an Order?[¶](#how-to-add-a-shipment-to-an-order "Permalink to this headline")

You will need to create a shipment, give it a desired shipping method and add it to the order.

Remember to process the order using order processor and then flush the order manager.

```

/\*\* @var ShipmentInterface $shipment \*/

$shipment = $this->container->get('sylius.factory.shipment')->createNew();

$shipment->setMethod($this->container->get('sylius.repository.shipping\_method')->findOneBy(['code' => 'UPS']));

$order->addShipment($shipment);

$this->container->get('sylius.order\_processing.order\_processor')->process($order);

$this->container->get('sylius.manager.order')->flush();

```

######## Shipping costs of an Order[¶](#shipping-costs-of-an-order "Permalink to this headline")

Shipping costs of an order are stored as Adjustments. When a new shipment is added to a cart the order processor assigns

a shipping adjustment to the order that holds the cost.

######## Shipping a Shipment with a state machine transition[¶](#shipping-a-shipment-with-a-state-machine-transition "Permalink to this headline")

Just like in every state machine you can execute its transitions manually. To **ship** a shipment of an order you have to apply

two transitions `request\_shipping` and `ship`.

```

$stateMachineFactory = $this->container->get('sm.factory');

$stateMachine = $stateMachineFactory->get($order, OrderShippingTransitions::GRAPH);

$stateMachine->apply(OrderShippingTransitions::TRANSITION\_REQUEST\_SHIPPING);

$stateMachine->apply(OrderShippingTransitions::TRANSITION\_SHIP);

$this->container->get('sylius.manager.order')->flush();

```

After that the `shippingState` of your order will be `shipped`.

####### Payments of an Order[¶](#payments-of-an-order "Permalink to this headline")

An **Order** in Sylius holds a collection of Payments on it. Each payment in that collection has its own payment method and has its own payment state.

It lets you to divide paying for an order into several different methods that have own payment states.

Tip

If you are not familiar with the Payments concept [check the documentation](index.html#document-book/orders/payments).

######## State machine of Payment in an Order[¶](#state-machine-of-payment-in-an-order "Permalink to this headline")

[![_images/sylius_order_payment.png](_images/sylius_order_payment.png)](_images/sylius_order_payment.png)

######## How to add a Payment to an Order?[¶](#how-to-add-a-payment-to-an-order "Permalink to this headline")

You will need to create a payment, give it a desired payment method and add it to the order.

Remember to process the order using order processor and then flush the order manager.

```

/\*\* @var PaymentInterface $payment \*/

$payment = $this->container->get('sylius.factory.payment')->createNew();

$payment->setMethod($this->container->get('sylius.repository.payment\_method')->findOneBy(['code' => 'offline']));

$payment->setCurrencyCode($currencyCode);

$order->addPayment($payment);

```

######## Completing a Payment with a state machine transition[¶](#completing-a-payment-with-a-state-machine-transition "Permalink to this headline")

Just like in every state machine you can execute its transitions manually. To **pay** for a payment of an order you have to apply

two transitions `request\_payment` and `pay`.

```

$stateMachineFactory = $this->container->get('sm.factory');

$stateMachine = $stateMachineFactory->get($order, OrderPaymentTransitions::GRAPH);

$stateMachine->apply(OrderPaymentTransitions::TRANSITION\_REQUEST\_PAYMENT);

$stateMachine->apply(OrderPaymentTransitions::TRANSITION\_PAY);

$this->container->get('sylius.manager.order')->flush();

```

**If it was the only payment assigned to that order** now the `paymentState` of your order will be `paid`.

######## Creating an Order via admin panel[¶](#creating-an-order-via-admin-panel "Permalink to this headline")

After installing the [Sylius/AdminOrderCreationPlugin](https://github.com/Sylius/AdminOrderCreationPlugin)

it is possible to create Orders for a chosen Customer from the administrator perspective.

You will be able to choose any products, assign custom prices for items, choose payment and shipping methods. Moreover

it is possible to reorder an order that has already been placed.

######## Customer Order operations: reorder & cancellation[¶](#customer-order-operations-reorder-cancellation "Permalink to this headline")

With the usage of other Sylius official plugins your Customers will be able to:

* cancel unpaid Orders in the “My Account” section -> [Customer Order Cancellation Plugin](https://github.com/Sylius/CustomerOrderCancellationPlugin)

* reorder one of their previously placed Orders -> [Customer Reorder Plugin](https://github.com/Sylius/CustomerReorderPlugin)

####### Learn more[¶](#learn-more "Permalink to this headline")

* [Order - Component Documentation](index.html#document-components_and_bundles/components/Order/index)

* [Order - Bundle Documentation](index.html#document-components_and_bundles/bundles/SyliusOrderBundle/index)

###### Cart flow[¶](#cart-flow "Permalink to this headline")

Picture the following situation - a user comes to a Sylius shop and they say:

**“Someone’s been using my cart! And they filled it all up with some items!”** Let’s avoid such moments of surprise

by shedding some light on Sylius cart flow, shall we?

**Cart** in Sylius represents an **Order** that is not placed yet.

It represents an order that is in progress (not placed yet).

Note

In Sylius each visitor has their own cart. It can be cleared either by placing an order, removing items manually

or using cart clearing command.

There are several cart flows, depending on the user being logged in or what items are currently placed in the cart.

First scenario:

```

Given there is a not logged in user

And this user adds a blue T-Shirt to the cart

And this user adds a red cap to the cart

And there is a customer identified by email "sylius@example.com" with not empty cart

When the not logged in user logs in using "sylius@example.com" email

Then the cart created by a not logged in user should be dropped

And the cart previously created by the user identified by "sylius@example.com" should be set as the current one

```

Second scenario:

```

Given there is a not logged in user

And this user adds a blue T-Shirt to the cart

And this user adds a red cap to the cart

And there is a customer identified by email "sylius@example.com" with an empty cart

When the not logged in user logs in using "sylius@example.com" email

Then the cart created by a not logged in user should not be dropped

And it should be set as the current cart

```

Third scenario:

```

Given there is a customer identified by email "sylius@example.com" with an empty cart

And this user adds a blue T-Shirt to the cart

And this user adds a red cap to the cart

When the user logs out

And views the cart

Then the cart should be empty

```

Note

The cart mentioned in the last scenario will we available once you log in again.

####### Learn more[¶](#learn-more "Permalink to this headline")

###### Taxation[¶](#taxation "Permalink to this headline")

Sylius’ taxation system allows you to apply appropriate taxes for different items, billing zones and using custom calculators.

####### Tax Categories[¶](#tax-categories "Permalink to this headline")

In order to process taxes in your store, you need to configure at least one **TaxCategory**, which represents a specific type of merchandise.

If all your items are taxed with the same rate, you can have a simple “Taxable Goods” category assigned to all items.

If you sell various products and some of them have different taxes applicable, you could create multiple categories. For example, “Clothing”, “Books” and “Food”.

Additionally to tax categories, you can have different zones, in order to apply correct taxes for customers coming from any country in the world.

######## How to create a TaxCategory programmatically?[¶](#how-to-create-a-taxcategory-programmatically "Permalink to this headline")

In order to create a TaxCategory use the dedicated factory. Your TaxCategory requires a `name` and a `code`.

```

/\*\* @var TaxCategoryInterface $taxCategory \*/

$taxCategory = $this->container->get('sylius.factory.tax\_category')->createNew();

$taxCategory->setCode('taxable\_goods');

$taxCategory->setName('Taxable Goods');

$this->container->get('sylius.repository.tax\_category')->add($taxCategory);

```

Since now you will have a new TaxCategory available.

######## How to set a TaxCategory on a ProductVariant?[¶](#how-to-set-a-taxcategory-on-a-productvariant "Permalink to this headline")

In order to have taxes calculated for your products you have to set TaxCategories for each ProductVariant you create.

Read more about Products and Variants [here](index.html#document-book/products/products).

```

/\*\* @var TaxCategoryInterface $taxCategory \*/

$taxCategory = $this->container->get('sylius.repository.tax\_category')->findOneBy(['code' => 'taxable\_goods']);

/\*\* @var ProductVariantInterface $variant \*/

$variant = $this->container->get('sylius.repository.product\_variant')->findOneBy(['code' => 'mug']);

$variant->setTaxCategory($taxCategory);

```

####### Tax Rates[¶](#tax-rates "Permalink to this headline")

A tax rate is essentially a percentage amount charged based on the sales price. Tax rates also contain other important information:

* Whether product prices are inclusive of this tax

* The zone in which the order address must fall within

* The tax category that a product must belong to in order to be considered taxable

* Calculator to use for computing the tax

######## TaxRates included in price[¶](#taxrates-included-in-price "Permalink to this headline")

The **TaxRate** entity has a field for configuring if you would like to have taxes included in the price of a subject or not.

If you have a TaxCategory with a 23% VAT TaxRate *includedInPrice* (`$taxRate->isIncludedInPrice()` returns `true`),

then the price shown on the ProductVariant in that TaxCategory will be increased by 23% all the time. See the Behat scenario below:

```

Given the store has included in price "VAT" tax rate of 23%

And the store has a product "T-Shirt" priced at "$10.00"

When I add product "T-Shirt" to my cart

Then my cart total should be "$10.00"

And my cart taxes should be "$1.87"

```

If the TaxRate *will not be included* (`$taxRate->isIncludedInPrice()` returns `false`)

then the price of ProductVariant will be shown without taxes, but when this ProductVariant will be added to cart taxes will be shown in the Taxes Total in the cart.

See the Behat scenario below:

```

Given the store has excluded from price "VAT" tax rate of 23%

And the store has a product "T-Shirt" priced at "$10.00"

When I add product "T-Shirt" to my cart

Then my cart total should be "$12.30"

And my cart taxes should be "$2.30"

```

######## How to create a TaxRate programmatically?[¶](#how-to-create-a-taxrate-programmatically "Permalink to this headline")

Note

Before creating a tax rate you need to know that you can have different tax zones, in order to apply correct taxes for customers coming from any country in the world.

To understand how zones work, please refer to the [Zones](https://docs.sylius.com/en/latest/book/customers/addresses/zones.html) chapter of this book.

Use a factory to create a new, empty TaxRate. Provide a `code`, a `name`. Set the amount of charge in float.

Then choose a calculator and zone (retrieved from the repository beforehand).

Finally you can set the TaxCategory of your new TaxRate.

```

/\*\* @var TaxRateInterface $taxRate \*/

$taxRate = $this->container->get('sylius.factory.tax\_rate')->createNew();

$taxRate->setCode('7%');

$taxRate->setName('7%');

$taxRate->setAmount(0.07);

$taxRate->setCalculator('default');

// Get a Zone from the repository, for example the 'US' zone

/\*\* @var ZoneInterface $zone \*/

$zone = $this->container->get('sylius.repository.zone')->findOneBy(['code' => 'US']);

$taxRate->setZone($zone);

// Get a TaxCategory from the repository, for example the 'alcohol' category

/\*\* @var TaxCategoryInterface $taxCategory \*/

$taxCategory = $this->container->get('sylius.repository.tax\_category')->findOneBy(['code' => 'alcohol']);

$taxRate->setCategory($taxCategory);

$this->container->get('sylius.repository.tax\_rate')->add($taxRate);

```

####### Default Tax Zone[¶](#default-tax-zone "Permalink to this headline")

The **default tax zone** concept is used for situations when we want to show taxes included in price even when we do not know the

address of the Customer, therefore we cannot choose a proper Zone, which will have proper TaxRates.

Since we are using the concept of [Channels](index.html#document-book/configuration/channels), we will use **the Zone assigned to the Channel as default Zone for Taxation**.

Note

To understand how zones work, please refer to the [Zones](https://docs.sylius.com/en/latest/book/customers/addresses/zones.html) chapter of this book.

####### Applying Taxes[¶](#applying-taxes "Permalink to this headline")

For applying Taxes **Sylius** is using the [OrderTaxesProcessor](https://github.com/Sylius/Sylius/blob/master/src/Sylius/Component/Core/OrderProcessing/OrderTaxesProcessor.php),

which has the services that implement the [OrderTaxesApplicatorInterface](https://github.com/Sylius/Sylius/blob/master/src/Sylius/Component/Core/Taxation/Applicator/OrderTaxesApplicatorInterface.php) inside.

######## Calculators[¶](#calculators "Permalink to this headline")

For calculating Taxes **Sylius** is using tax calculators.

To select a proper service we have a one that decides for us

- the [DelegatingCalculator](https://github.com/Sylius/Sylius/blob/1.12/src/Sylius/Component/Taxation/Calculator/DelegatingCalculator.php).

Basing on the **TaxRate** assigned on the Product it will get its calculator type calculate the amount properly.

You can create your custom calculator for taxes by creating a class that implements

the [CalculatorInterface](https://github.com/Sylius/Sylius/blob/1.12/src/Sylius/Component/Taxation/Calculator/CalculatorInterface.php)

and registering it as a `sylius.tax\_calculator.your\_calculator\_name` service.

######## Built-in Calculators[¶](#built-in-calculators "Permalink to this headline")

The already defined calculators in Sylius:

* **DefaultCalculator** - calculates the `amount` with rounding.

* **DecimalCalculator** - calculates the `amount` without rounding, which results in a distribution of decimal values among the items.

####### Learn more[¶](#learn-more "Permalink to this headline")

* [Taxation - Bundle Documentation](index.html#document-components_and_bundles/bundles/SyliusTaxationBundle/index)

* [taxation - Component Documentation](index.html#document-components_and_bundles/components/Taxation/index)

###### Adjustments[¶](#adjustments "Permalink to this headline")

**Adjustment** is a resource closely connected to the [Orders’ concept](index.html#document-book/orders/orders). It **influences the order’s total**.

Adjustments may appear on the Order, the OrderItems and the OrderItemUnits.

There are a few types of adjustments in Sylius:

* Order Promotion Adjustments,

* OrderItem Promotion Adjustments,

* OrderItemUnit Promotion Adjustments,

* Shipping Adjustments,

* Shipping Promotion Adjustments,

* Tax Adjustments

And they can be generally divided into three *groups*: **promotion adjustments**, **shipping adjustments** and **taxes adjustments**.

Also note that adjustments can be either **positive**: charges (with a `+`) or **negative**: discounts (with a `-`).

####### How to create an Adjustment programmatically?[¶](#how-to-create-an-adjustment-programmatically "Permalink to this headline")

The Adjustments alone are a bit useless. They should be created alongside Orders.

As usual, get a factory and create an adjustment.

Then give it a `type` - you can find all the available types on the [AdjustmentInterface](https://github.com/Sylius/Sylius/blob/master/src/Sylius/Component/Core/Model/AdjustmentInterface.php).

The adjustment needs also the `amount` - which is the amount of money that will be **added to the orders total**.

Note

The `amount` is always saved in the **base currency**.

Additionally you can set the `label` that will be displayed on the order view and whether your adjustment is `neutral` -

**neutral adjustments** do not affect the order’s total (like for example taxes included in price).

```

/\*\* @var AdjustmentInterface $adjustment \*/

$adjustment = $this->container->get('sylius.factory.adjustment')->createNew();

$adjustment->setType(AdjustmentInterface::ORDER\_PROMOTION\_ADJUSTMENT);

$adjustment->setAmount(200);

$adjustment->setNeutral(false);

$adjustment->setLabel('Test Promotion Adjustment');

$order->addAdjustment($adjustment);

```

Note

Remember that if you are creating OrderItem adjustments you have to add them on the OrderItem level.

The same happens with the OrderItemUnit adjustments, which have to be added on the OrderItemUnit level.

To see changes on the order you need to update it in the database.

```

$this->container->get('sylius.manager.order')->flush();

```

Tip

An adjustment can be locked with `$adjustment->lock()`.

It can be useful when the total order price is recalculated and a promotion isn’t applicable anymore

but you still want it to be applied to the order.

In case of an expired coupon that still should be included in the order for example.

####### Learn more[¶](#learn-more "Permalink to this headline")

* [Cart Promotions - Concept Documentation](index.html#document-book/orders/cart-promotions)

* [Taxation - Concept Documentation](index.html#document-book/orders/taxation)

* [Shipments - Concept Documentation](index.html#document-book/orders/shipments)

###### Cart Promotions[¶](#cart-promotions "Permalink to this headline")

The system of **Cart Promotions** in **Sylius** is really flexible. It is a combination of promotion rules and actions.

Cart Promotions have a few parameters - a unique `code`, `name`, `usageLimit`,

the period of time when it works.

There is a possibility to define **exclusive cart promotions** (no other can be applied if an exclusive promotion was applied)

and **priority** that is useful for them, because the exclusive promotion should get the top priority.

Tip

The `usageLimit` of a promotion is the **total number of times this promotion can be used**.

Tip

**Promotion priorities** are numbers that you assign to the promotion. The larger the number, the higher the priority.

So a promotion with priority 3 would be applied before a promotion with priority set to 1.

What can you use the priority for? Well, imagine that you have two different cart promotions, one’s action is to give 10% discount

on whole order and the other one gives 5$ discount from the order total. Business (and money) wise, which one should we apply first? ;)

####### How to create a Promotion programmatically?[¶](#how-to-create-a-promotion-programmatically "Permalink to this headline")

Just as usual, use a factory. The promotion needs a `code` and a `name`.

```

/\*\* @var PromotionInterface $promotion \*/

$promotion = $this->container->get('sylius.factory.promotion')->createNew();

$promotion->setCode('simple\_promotion\_1');

$promotion->setName('Simple Promotion');

```

**Of course an empty promotion would be useless** - it is just a base for adding **Rules** and **Actions**.

Let’s see how to make it functional.

####### Promotion Rules[¶](#promotion-rules "Permalink to this headline")

The promotion **Rules** restrict in what circumstances a promotion will be applied.

An appropriate **RuleChecker** (each Rule type has its own RuleChecker) may check if the Order:

* Contains a number of items from a specified taxon (for example: *contains 4 products that are categorized as t-shirts*)

* Has a specified total price of items from a given taxon (for example: *all mugs in the order cost 20$ in total*)

* Has total price of at least a defined value (for example: *the orders’ items total price is equal at least 50$*)

And many more similar, suitable to your needs.

######## Rule Types[¶](#rule-types "Permalink to this headline")

The types of rules that are configured in **Sylius** by default are:

* **Cart Quantity** - checks if there is a given amount of items in the cart,

* **Item Total** - checks if items in the cart cost a given amount of money,

* **Has at least one from taxons** - checks if there is at least one item from given taxons in the cart,

* **Total price of items from taxon** - checks in the cart if items from a given taxon cost a given amount of money,

* **Nth Order** - checks if this is for example the second order made by the customer,

* **Shipping Country** - checks if the order’s shipping address is in a given country.

* **Customer Group** - checks if the current customer is in a given customer group.

* **Contains Product** - checks if the order contains a certain product.

######## How to create a new PromotionRule programmatically?[¶](#how-to-create-a-new-promotionrule-programmatically "Permalink to this headline")

Creating a **PromotionRule** is really simple since we have the [PromotionRuleFactory](https://github.com/Sylius/Sylius/blob/master/src/Sylius/Component/Core/Factory/PromotionRuleFactory.php).

It has dedicated methods for creating all types of rules available by default.

In the example you can see how to create a simple Cart Quantity rule. It will check if there are at least 5 items in the cart.

```

/\*\* @var PromotionRuleFactoryInterface $ruleFactory \*/

$ruleFactory = $this->container->get('sylius.factory.promotion\_rule');

$quantityRule = $ruleFactory->createCartQuantity('5');

// add your rule to the previously created Promotion

$promotion->addRule($quantityRule);

```

Note

**Rules** are just constraints that have to be fulfilled by an order to make the promotion **eligible**.

To make something happen to the order you will need **Actions**.

######## PromotionRules configuration reference[¶](#promotionrules-configuration-reference "Permalink to this headline")

Each PromotionRule type has a very specific structure of its configuration array:

| PromotionRule type | Rule Configuration Array |

| --- | --- |

| `cart\_quantity` | `['count' => $count]` |

| `item\_total` | `[$channelCode => ['amount' => $amount]]` |

| `has\_taxon` | `['taxons' => $taxons]` |

| `total\_of\_items\_from\_taxon` | `[$channelCode => ['taxon' => $taxonCode, 'amount' => $amount]]` |

| `nth\_order` | `['nth' => $nth]` |

| `contains\_product` | `['product\_code' => $productCode]` |

####### Promotion Actions[¶](#promotion-actions "Permalink to this headline")

Promotion Action is basically what happens when the rules of a Promotion are fulfilled, what discount is applied on the whole Order (or its Shipping cost).

There are a few kinds of actions in **Sylius**:

* fixed discount on the order (for example: -5$ off the order total)

* percentage discount on the order (for example: -10% on the whole order)

* fixed unit discount (for example: -1$ off the order total but *distributed and applied on each order item unit*)

* percentage unit discount (for example: -10% off the order total but *distributed and applied on each order item unit*)

* shipping precentage discount (for example: -10% off the costs of shipping)

Tip

Actions are applied on all items in the Order. If you are willing to apply discounts on specific items

in the order check Filters at the bottom of this article.

######## How to create an PromotionAction programmatically?[¶](#how-to-create-an-promotionaction-programmatically "Permalink to this headline")

In order to create a new PromotionAction we can use the dedicated [PromotionActionFactory](https://github.com/Sylius/Sylius/blob/master/src/Sylius/Component/Core/Factory/PromotionActionFactory.php).

It has special methods for creating all types of actions available by default.

In the example below you can see how to create a simple Fixed Discount action, that reduces the total of an order by 10$.

```

/\*\* @var PromotionActionFactoryInterface $actionFactory \*/

$actionFactory = $this->container->get('sylius.factory.promotion\_action');

$action = $actionFactory->createFixedDiscount(10);

// add your action to the previously created Promotion

$promotion->addAction($action);

```

Note

All **Actions** are assigned to a Promotion and are executed while the Promotion is applied.

This happens via the [CompositeOrderProcessor](https://github.com/Sylius/Sylius/blob/master/src/Sylius/Component/Order/Processor/CompositeOrderProcessor.php) service.

See details of **applying Cart Promotions** below.

And finally after you have an **PromotionAction** and a **PromotionRule** assigned to the **Promotion** add it to the repository.

```

$this->container->get('sylius.repository.promotion')->add($promotion);

```

######## PromotionActions configuration reference[¶](#promotionactions-configuration-reference "Permalink to this headline")

Each PromotionAction type has a very specific structure of its configuration array:

| PromotionAction type | Action Configuration Array |

| --- | --- |

| `order\_fixed\_discount` | `[$channelCode => ['amount' => $amount]]` |

| `unit\_fixed\_discount` | `[$channelCode => ['amount' => $amount]]` |

| `order\_percentage\_discount` | `['percentage' => $percentage]` |

| `unit\_percentage\_discount` | `[$channelCode => ['percentage' => $percentage]]` |

| `shipping\_percentage\_discount` | `['percentage' => $percentage]` |

####### Applying Cart Promotions[¶](#applying-cart-promotions "Permalink to this headline")

Cart Promotions in Sylius are handled by the [PromotionProcessor](https://github.com/Sylius/Sylius/blob/master/src/Sylius/Component/Promotion/Processor/PromotionProcessor.php)

which inside uses the [PromotionApplicator](https://github.com/Sylius/Sylius/blob/master/src/Sylius/Component/Promotion/Action/PromotionApplicator.php).

The **PromotionProcessor**’s method `process()` is executed on the subject of cart promotions - an Order:

* firstly it iterates over the cart promotions of a given Order and first **reverts** them all,

* then it checks the eligibility of all cart promotions available in the system on the given Order

* and finally it applies all the eligible cart promotions to that order.

######## How to apply a Cart Promotion manually?[¶](#how-to-apply-a-cart-promotion-manually "Permalink to this headline")

Let’s assume that you would like to **apply a 10% discount on everything** somewhere in your code.

To achieve that, create a Cart Promotion with an PromotionAction that gives 10% discount. You don’t need rules.

```

/\*\* @var PromotionInterface $promotion \*/

$promotion = $this->container->get('sylius.factory.promotion')->createNew();

$promotion->setCode('discount\_10%');

$promotion->setName('10% discount');

/\*\* @var PromotionActionFactoryInterface $actionFactory \*/

$actionFactory = $this->container->get('sylius.factory.promotion\_action');

$action = $actionFactory->createPercentageDiscount(10);

$promotion->addAction($action);

$this->container->get('sylius.repository.promotion')->add($promotion);

// and now get the PromotionApplicator and use it on an Order (assuming that you have one)

$this->container->get('sylius.promotion\_applicator')->apply($order, $promotion);

```

####### Promotion Filters[¶](#promotion-filters "Permalink to this headline")

Filters are really handy when you want to apply promotion’s actions to groups of products in an Order.

For example if you would like to apply actions only on products from a desired taxon - use the available by default

[TaxonFilter](https://github.com/Sylius/Sylius/blob/master/src/Sylius/Component/Core/Promotion/Filter/TaxonFilter.php).

Read [these scenarios regarding promotion filters](https://github.com/Sylius/Sylius/blob/master/features/promotion/receiving_discount/receiving_fixed_discount_on_products_from_specific_taxon.feature)

to have a better understanding of them.

####### Learn more[¶](#learn-more "Permalink to this headline")

* [Promotion - Component Documentation](index.html#document-components_and_bundles/components/Promotion/index)

* [Promotion - Bundle Documentation](index.html#document-components_and_bundles/bundles/SyliusPromotionBundle/index)

* [How to create a custom promotion rule?](index.html#document-cookbook/promotions/custom-cart-promotion-rule)

* [How to create a custom promotion action?](index.html#document-cookbook/promotions/custom-cart-promotion-action)

###### Coupons[¶](#coupons "Permalink to this headline")

The concept of coupons is closely connected to the [Cart Promotions Concept](index.html#document-book/orders/cart-promotions).

####### Coupon Parameters[¶](#coupon-parameters "Permalink to this headline")

A **Coupon** besides a `code` has a date when it expires, the `usageLimit` and it counts how many times it was already used.

####### How to create a coupon with a promotion programmatically?[¶](#how-to-create-a-coupon-with-a-promotion-programmatically "Permalink to this headline")

Warning

The promotion has to be `couponBased = true` in order to be able to hold a collection of Coupons that belong to it.

Let’s create a promotion that will have a single coupon that activates the free shipping promotion.

```

/\*\* @var PromotionInterface $promotion \*/

$promotion = $this->container->get('sylius.factory.promotion')->createNew();

$promotion->setCode('free\_shipping');

$promotion->setName('Free Shipping');

```

Remember to set a **channel** for your promotion and to make it **couponBased**!

```

$promotion->addChannel($this->container->get('sylius.repository.channel')->findOneBy(['code' => 'US\_Web\_Store']));

$promotion->setCouponBased(true);

```

Then create a coupon and add it to the promotion:

```

/\*\* @var CouponInterface $coupon \*/

$coupon = $this->container->get('sylius.factory.promotion\_coupon')->createNew();

$coupon->setCode('FREESHIPPING');

$promotion->addCoupon($coupon);

```

Now create an PromotionAction that will take place after applying this promotion - 100% discount on shipping

```

/\*\* @var PromotionActionFactoryInterface $actionFactory \*/

$actionFactory = $this->container->get('sylius.factory.promotion\_action');

// Provide the amount in float ( 1 = 100%, 0.1 = 10% )

$action = $actionFactory->createShippingPercentageDiscount(1);

$promotion->addAction($action);

$this->container->get('sylius.repository.promotion')->add($promotion);

```

Finally to see the effects of your promotion with coupon you need to **apply a coupon on the Order**.

####### How to apply a coupon to an Order?[¶](#how-to-apply-a-coupon-to-an-order "Permalink to this headline")

To apply your promotion with coupon that gives 100% discount on the shipping costs

you need an order that has shipments. Set your promotion coupon on that order -

this is what happens when a customer provides a coupon code during checkout.

And after that call the OrderProcessor on the order to have the promotion applied.

```

$order->setPromotionCoupon($coupon);

$this->container->get('sylius.order\_processing.order\_processor')->process($order);

```

####### Promotion Coupon Generator[¶](#promotion-coupon-generator "Permalink to this headline")

Making up new codes might become difficult if you would like to prepare a lot of coupons at once. That is why Sylius

provides a service that generates random codes for you - [CouponGenerator](https://github.com/Sylius/Sylius/blob/master/src/Sylius/Component/Promotion/Generator/PromotionCouponGenerator.php).

In its **PromotionCouponGeneratorInstruction** you can define the amount of coupons that will be generated, the length of their codes, expiration date and usage limit.

```

// Find a promotion you desire in the repository

$promotion = $this->container->get('sylius.repository.promotion')->findOneBy(['code' => 'simple\_promotion']);

// Get the CouponGenerator service

/\*\* @var CouponGeneratorInterface $generator \*/

$generator = $this->container->get('sylius.promotion\_coupon\_generator');

// Then create a new empty PromotionCouponGeneratorInstruction

/\*\* @var PromotionCouponGeneratorInstructionInterface $instruction \*/

$instruction = new PromotionCouponGeneratorInstruction();

// By default the instruction will generate 5 coupons with codes of length equal to 6

// You can easily change it with the ``setAmount()`` and ``setLength()`` methods

$instruction->setAmount(10);

// Coupon prefix and suffix are not mandatory but can be used to make coupon code more human-friendly

$instruction->setPrefix('NEW\_YEAR\_');

$instruction->setSuffix('\_SALE');

// Now use the ``generate()`` method with your instruction on the promotion where you want to have Coupons

$generator->generate($promotion, $instruction);

```

The above piece of code will result in a set of 10 coupons that will work with the promotion identified by the `simple\_promotion` code.

####### Learn more[¶](#learn-more "Permalink to this headline")

* [Cart Promotions Concept Documentation](index.html#document-book/orders/cart-promotions)

* [promotion - Component Documentation](index.html#document-components_and_bundles/components/Promotion/index)

* [promotion - Bundle Documentation](index.html#document-components_and_bundles/bundles/SyliusPromotionBundle/index)

###### Shipments[¶](#shipments "Permalink to this headline")

A **Shipment** is a representation of a shipping request for an Order. Sylius can attach multiple shipments to each single Order.

Shipment consists of **ShipmentUnits**, which are a representation of OrderItemUnits from its Order.

####### How is a Shipment created for an Order?[¶](#how-is-a-shipment-created-for-an-order "Permalink to this headline")

Warning

Read more about creating [Orders](index.html#document-book/orders/orders) where the process of assigning Shipments is clarified.

######## Splitting shipments[¶](#splitting-shipments "Permalink to this headline")

As mentioned in the beginning Sylius Order holds a collection of Shipments. In Sylius Plus edition Orders can be

fulfilled partially, therefore it is possible to split the default Order’s shipment.

To do it Sylius Plus provides a UI, where you can choose which items from the initial shipments you’d like to extract to

a new split shipment and send it (providing a tracking code or not). Shipments of an Order can be split as long as

there remains one shipment in state `ready`.

[![_images/banner6.png](_images/banner6.png)](https://sylius.com/plus/?utm_source=docs)

####### The Shipment State Machine[¶](#the-shipment-state-machine "Permalink to this headline")

A Shipment that is attached to an Order will have its own state machine with the following states available:

`cart`, `ready`, `cancelled`, `shipped`.

The allowed transitions between these states are:

```

transitions:

 create:

 from: [cart]

 to: ready

 ship:

 from: [ready]

 to: shipped

 cancel:

 from: [ready]

 to: cancelled

```

[![_images/sylius_shipment.png](_images/sylius_shipment.png)](_images/sylius_shipment.png)

####### Shipping Methods[¶](#shipping-methods "Permalink to this headline")

**ShippingMethod** in Sylius is an entity that represents the way an order can be shipped to a customer.

######## How to create a ShippingMethod programmatically?[¶](#how-to-create-a-shippingmethod-programmatically "Permalink to this headline")

As usual use a factory to create a new ShippingMethod. Give it a `code`, set a desired shipping calculator and set a `zone`.

It also need a configuration, for instance of the amount (cost).

At the end add it to the system using a repository.

```

$shippingMethod = $this->container->get('sylius.factory.shipping\_method')->createNew();

$shippingMethod->setCode('DHL');

$shippingMethod->setCalculator(DefaultCalculators::FLAT\_RATE);

$shippingMethod->setConfiguration(['channel\_code' => ['amount' => 50]]);

$zone = $this->container->get('sylius.repository.zone')->findOneByCode('US');

$shippingMethod->setZone($zone);

$this->container->get('sylius.repository.shipping\_method')->add($shippingMethod);

```

In order to have your shipping method available in checkout add it to a desired channel.

```

$channel = $this->container->get('sylius.repository.channel')->findOneByCode('channel\_code');

$channel->addShippingMethod($shippingMethod);

```

######### Shipping method rules[¶](#shipping-method-rules "Permalink to this headline")

The shipping method **Rules** restrict in what circumstances a shipping method is available.

An appropriate **RuleChecker** (each Rule type has its own RuleChecker) may check if:

* All products belong to a certain taxon

* The order total is greater than a given amount

* The total weight is below a given number

* The total volume is below a given value

And many more similar, suitable to your needs.

######### Rule Types[¶](#rule-types "Permalink to this headline")

The types of rules that are configured in **Sylius** by default are:

* **Items total greater than or equal** - checks if the items total is greater than or equal to a given amount

* **Items total less than or equal** - checks if the items total is less than or equal to a given amount

* **Total weight greater than or equal** - checks if the total weight of the order is greater than or equal to a given number

* **Total weight less than or equal** - checks if the total weight of the order is less than or equal to a given number

####### Shipping Zones[¶](#shipping-zones "Permalink to this headline")

Sylius has an approach of **Zones** used also for shipping. As in each e-commerce you may be willing to ship only to certain countries for example.

Therefore while configuring your **ShippingMethods** pay special attention to the zones you are assigning to them.

You have to prepare methods for each zone, because the available methods are retrieved for the zone the customer has basing on his address.

####### Shipping Cost Calculators[¶](#shipping-cost-calculators "Permalink to this headline")

The shipping cost calculators are services that are used to calculate the cost for a given shipment.

The [CalculatorInterface](https://github.com/Sylius/Sylius/blob/master/src/Sylius/Component/Shipping/Calculator/CalculatorInterface.php)

has a method `calculate()` that takes object with a configuration and returns *integer* that is the cost of shipping for that subject.

It also has a `getType()` method that works just like in the forms.

To select a proper service we have a one that decides for us

- the [DelegatingCalculator](https://github.com/Sylius/Sylius/blob/master/src/Sylius/Component/Shipping/Calculator/DelegatingCalculator.php).

Basing on the **ShippingMethod** assigned on the Shipment it will get its calculator type and configuration and calculate the cost properly.

```

$shippingCalculator = $this->container->get('sylius.shipping\_calculator');

$cost = $shippingCalculator->calculate($shipment);

```

####### Built-in Calculators[¶](#built-in-calculators "Permalink to this headline")

The already defined calculators in Sylius are described as constants in the

[SyliusComponentShippingCalculatorDefaultCalculators](https://github.com/Sylius/Sylius/blob/master/src/Sylius/Component/Shipping/Calculator/DefaultCalculators.php)

* **FlatRateCalculator** - just returns the `amount` from the ShippingMethod’s configuration.

* **PerUnitRateCalculator** - returns the `amount` from the ShippingMethod’s configuration multiplied by the `units` count.

####### Shipment complete events[¶](#shipment-complete-events "Permalink to this headline")

There are two events that are triggered on the shipment `ship` action:

| Event id |

| --- |

| `sylius.shipment.pre\_ship` |

| `sylius.shipment.post\_ship` |

####### Learn more[¶](#learn-more "Permalink to this headline")

* [Shipping - Component Documentation](index.html#document-components_and_bundles/components/Shipping/index)

* [How to create a custom shipping method rule?](index.html#document-cookbook/shipping-methods/custom-shipping-method-rule)

###### Payments[¶](#payments "Permalink to this headline")

Sylius contains a very flexible payments management system with support for many gateways (payment providers).

We are using a payment abstraction library - [Payum](https://github.com/Payum/Payum), which handles all sorts of capturing, refunding and recurring payments logic.

On Sylius side, we integrate it into our checkout and manage all the payment data.

####### Payment[¶](#payment "Permalink to this headline")

Every payment in Sylius, successful or failed, is represented by the **Payment** model, which contains basic information and a reference to appropriate order.

######## Invoices[¶](#invoices "Permalink to this headline")

By installing the [Sylius/InvoicingPlugin](https://github.com/Sylius/InvoicingPlugin) you can enhance your Sylius

application with the automatic generation of invoicing documents for each Order’s payment.

Sylius invoices have their own numbers, issuing dates, consist of shop and customer’s data, the order items with prices and quantity

and separated taxes section. Their templates can be changed, and of course the moment of invoice generation can be customized.

####### Payment State Machine[¶](#payment-state-machine "Permalink to this headline")

A Payment that is assigned to an order will have it’s own state machine with a few available states:

`cart`, `new`, `processing`, `completed`, `failed`, `cancelled`, `refunded`.

The available transitions between these states are:

```

transitions:

 create:

 from: [cart]

 to: new

 process:

 from: [new]

 to: processing

 authorize:

 from: [new, processing]

 to: authorized

 complete:

 from: [new, processing, authorized]

 to: completed

 fail:

 from: [new, processing]

 to: failed

 cancel:

 from: [new, processing, authorized]

 to: cancelled

 refund:

 from: [completed]

 to: refunded

```

[![_images/sylius_payment.png](_images/sylius_payment.png)](_images/sylius_payment.png)

Of course, you can define your own states and transitions to create a workflow, that perfectly matches your needs.

Full configuration can be seen in the [PaymentBundle/Resources/config/app/state\_machine.yml](https://github.com/Sylius/Sylius/blob/master/src/Sylius/Bundle/PaymentBundle/Resources/config/app/state_machine.yml).

Changes to payment happen through applying appropriate transitions.

######## How to create a Payment programmatically?[¶](#how-to-create-a-payment-programmatically "Permalink to this headline")

We cannot create a Payment without an Order, therefore let’s assume that you have an Order to which you will assign a new payment.

```

$payment = $this->container->get('sylius.factory.payment')->createNew();

$payment->setOrder($order);

$payment->setCurrencyCode('USD');

$this->container->get('sylius.repository.payment')->add($payment);

```

Tip

Not familiar with the Order concept? Check [here](index.html#document-book/orders/orders).

####### Payment Methods[¶](#payment-methods "Permalink to this headline")

A **PaymentMethod** represents a way that your customer pays during the checkout process.

It holds a reference to a specific `gateway` with custom configuration.

Gateway is configured for each payment method separately using the payment method form.

######## How to create a PaymentMethod programmatically?[¶](#how-to-create-a-paymentmethod-programmatically "Permalink to this headline")

As usual, use a factory to create a new PaymentMethod and give it a unique code.

```

$paymentMethod = $this->container->get('sylius.factory.payment\_method')->createWithGateway('offline');

$paymentMethod->setCode('ALFA1');

$this->container->get('sylius.repository.payment\_method')->add($paymentMethod);

```

In order to have your new payment method available in the checkout remember to **add your desired channel to the payment method**:

```

$paymentMethod->addChannel($channel);

```

####### Payment Gateway configuration[¶](#payment-gateway-configuration "Permalink to this headline")

######## Payment Gateways that already have a Sylius bridge[¶](#payment-gateways-that-already-have-a-sylius-bridge "Permalink to this headline")

First you need to create the configuration form type for your gateway. Have a look at the configuration form types of

* [Paypal Commerce Platform](https://github.com/Sylius/PayPalPlugin/blob/master/src/Form/Type/PayPalConfigurationType.php)

* [Paypal Express Checkout](https://github.com/Sylius/Sylius/blob/master/src/Sylius/Bundle/PayumBundle/Form/Type/PaypalGatewayConfigurationType.php)

* [Stripe](https://github.com/Sylius/Sylius/blob/master/src/Sylius/Bundle/PayumBundle/Form/Type/StripeGatewayConfigurationType.php)

Then you should register its configuration form type with `sylius.gateway\_configuration\_type` tag.

After that it will be available in the Admin panel in the gateway choice dropdown.

Tip

If you are not sure how your configuration form type should look like, head to [Payum](https://github.com/Payum/Payum) documentation.

######## Other Payment Gateways[¶](#other-payment-gateways "Permalink to this headline")

Note

Learn more about integrating payment gateways in the [dedicated guide](index.html#document-cookbook/payments/custom-payment-gateway) and in [the Payum docs](https://github.com/Payum/Payum/blob/master/docs/index.md).

When the Payment Gateway you are trying to use does have a bridge available and you integrate them on your own,

use our guide on [extension development](index.html#document-book/plugins/guide/index).

####### Troubleshooting[¶](#troubleshooting "Permalink to this headline")

Sylius stores the payment output inside the **details** column of the **sylius\_payment** table.

It can provide valuable information when debugging the payment process.

######## PayPal Error Code 10409[¶](#paypal-error-code-10409 "Permalink to this headline")

The 10409 code, also known as the *“Checkout token was issued for a merchant account other than yours”* error.

You have most likely changed the PayPal credentials during the checkout process. Clear the cache and try again:

```

bin/console cache:clear

```

####### Payment complete events[¶](#payment-complete-events "Permalink to this headline")

Warning

The following events are not a universal way to listen for a completed payment, as they are only dispatched by ResourceController at specific places, like completing the payment for a given order in the Admin Panel.

It means that pre\_complete and post\_complete events won’t be triggered when a customer completes the payment by using, e.g., a payment gateway.

Tip

If you are looking for a way to listen for completing the payment, consider [adding a new callback](index.html#document-customization/state_machine) for a sylius\_order\_payment state machine on the pay transition.

There are two events that are triggered on the payment complete action:

| Event id |

| --- |

| `sylius.payment.pre\_complete` |

| `sylius.payment.post\_complete` |

####### Learn more[¶](#learn-more "Permalink to this headline")

* [Payment - Component Documentation](index.html#document-components_and_bundles/components/Payment/index)

* [Payum - Project Documentation](https://github.com/Payum/Payum/blob/master/docs/index.md)

* [Refunds](index.html#document-book/orders/refunds)

###### Invoices[¶](#invoices "Permalink to this headline")

An Invoice is a commercial document issued by the shop, that is a sort of confirmation of the sale transaction. It indicates the

products, quantities, agreed prices for these products. An invoice contains usually also payment terms (like due date,

or if it was already paid). From the shop’s point of view, an invoice is a sales invoice. From the customer’s point of view,

an invoice is a purchase invoice.

####### Invoicing in Sylius[¶](#invoicing-in-sylius "Permalink to this headline")

Sylius is providing invoicing engine via a [free open-source plugin.](https://github.com/Sylius/InvoicingPlugin)

The plugin installation guide you will find in the plugin’s README.

Having the plugin installed, you will notice a new main menu item among “Sales” section - “Invoices”. It allows you to access the index

of all invoices issued at your shop (sortable and with filters as most of the grids).

Moreover a section on admin Order show page is added: Invoices. This same section will appear also

on the Order show page for customers in the shop.

[![_images/order_invoices.png](_images/order_invoices.png)](_images/order_invoices.png)

  

######## When is the Invoice issued?[¶](#when-is-the-invoice-issued "Permalink to this headline")

The invoices are generated by default **when the Order is placed** (so after the customer clicks the Confirm button at the end of checkout).

After that the Invoice is already downloadable for both the Admin and Customer.

Tip

In order to customize the moment when the Invoice is generated, you will need to override the logic around

a specific event listener and the OrderPlacedProducer. <https://github.com/Sylius/InvoicingPlugin/blob/master/src/Resources/config/services/events.xml#L13:L23>

######## Sending and downloading Invoices[¶](#sending-and-downloading-invoices "Permalink to this headline")

Sending invoice is an action separate from its generation.

By default the Invoice is sent to customer **when the Order’s payment is paid**.

Of course you can customize it by overriding a part of configuration placed in config.yml file.

You can customize this file by adding new state machine event listeners or editing existing ones.

######## Shop Billing Data[¶](#shop-billing-data "Permalink to this headline")

The Invoicing plugin for the invoicing sake is using a feature of Sylius Channels.

Each channel has a separate section for providing billing data of the shop, that will be placed on the invoice.

[![_images/shop_billing_data1.png](_images/shop_billing_data1.png)](_images/shop_billing_data1.png)

  

[![_images/invoice.png](_images/invoice.png)](_images/invoice.png)

####### Learn more[¶](#learn-more "Permalink to this headline")

* [Sylius/InvoicingPlugin](https://github.com/Sylius/InvoicingPlugin)

* [Other Sylius plugins](index.html#document-book/plugins/index)

###### Checkout[¶](#checkout "Permalink to this headline")

**Checkout** is a process that begins when the Customer decides to finish their shopping and pay for their order.

The process of specifying address, payment and a way of shipping transforms the **Cart** into an **Order**.

####### Checkout State Machine[¶](#checkout-state-machine "Permalink to this headline")

The Order Checkout state machine has 7 states available: `cart`, `addressed`, `shipping\_selected`, `shipping\_skipped`, `payment\_selected`, `payment\_skipped`, `completed` and a set of defined transitions between them.

These states are saved as the **checkoutState** of the **Order**.

Besides the steps of checkout, each of them can be done more than once. For instance if the Customer changes their mind

and after selecting payment they want to change the shipping address they have already specified, they can of course go back and readdress it.

The transitions on the order checkout state machine are:

```

transitions:

 address:

 from: [cart, addressed, shipping\_selected, shipping\_skipped, payment\_selected, payment\_skipped]

 to: addressed

 skip\_shipping:

 from: [addressed]

 to: shipping\_skipped

 select\_shipping:

 from: [addressed, shipping\_selected, payment\_selected, payment\_skipped]

 to: shipping\_selected

 skip\_payment:

 from: [shipping\_selected, shipping\_skipped]

 to: payment\_skipped

 select\_payment:

 from: [payment\_selected, shipping\_skipped, shipping\_selected]

 to: payment\_selected

 complete:

 from: [payment\_selected, payment\_skipped]

 to: completed

```

[![_images/sylius_order_checkout.png](_images/sylius_order_checkout.png)](_images/sylius_order_checkout.png)

####### Steps of Checkout[¶](#steps-of-checkout "Permalink to this headline")

Checkout in Sylius is divided into 4 steps. Each of these steps occurs when the Order goes into a certain state.

See the Checkout state machine in the [state\_machine/sylius\_order\_checkout.yml](https://github.com/Sylius/Sylius/blob/master/src/Sylius/Bundle/CoreBundle/Resources/config/app/state_machine/sylius_order_checkout.yml)

together with the routing file for checkout: [checkout.yml](https://github.com/Sylius/Sylius/blob/master/src/Sylius/Bundle/ShopBundle/Resources/config/routing/checkout.yml).

Note

Before performing Checkout [you need to have an Order created](index.html#document-book/orders/orders).

######## Addressing[¶](#addressing "Permalink to this headline")

This is a step where the customer provides both **shipping and billing addresses**.

| Transition after step | Template |

| `cart`-> `addressed` | `SyliusShopBundle:Checkout:addressing.html.twig` |

######### How to perform the Addressing Step programmatically?[¶](#how-to-perform-the-addressing-step-programmatically "Permalink to this headline")

Firstly if the **Customer** is not yet set on the Order it will be assigned depending on the case:

* An already logged in **User** - the Customer is set for the Order using the [ShopCartBlamerListener](https://github.com/Sylius/Sylius/blob/1.12/src/Sylius/Bundle/ShopBundle/EventListener/ShopCartBlamerListener.php), that determines the user basing on the event.

* A **Customer** or **User** that was present in the system before (we’ve got their e-mail) - the Customer instance is updated via cascade, the order is assigned to it.

* A new **Customer** with unknown e-mail - a new Customer instance is created and assigned to the order.

Note

Before Sylius `v1.7` a **User** (i.e. we have their e-mail and they are registered) had to login before they could complete the checkout process. If you want this constraint on your checkout, you can add this to your application:

```

 <?xml version="1.0" encoding="UTF-8"?>

<constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping https://symfony.com/schema/dic/services/constraint-mapping-1.0.xsd">

 <class name="Sylius\Component\Core\Model\Customer">

 <constraint name="Sylius\Bundle\CoreBundle\Validator\Constraints\RegisteredUser">

 <option name="message">sylius.customer.email.registered</option>

 <option name="groups">sylius_customer_checkout_guest</option>

 </constraint>

 </class>

</constraint-mapping>

```

If you would like to achieve the same behaviour in API, read [the dedicated cookbook](index.html#document-cookbook/api/how_force_login_already_registered_user_during_checkout).

Hint

If you do not understand the Users and Customers concept in Sylius go to the [Users Concept documentation](index.html#document-book/customers/customer_and_shopuser).

The typical **Address** consists of: country, city, street and postcode - to assign it to an Order either create it manually or retrieve from the repository.

```

/\*\* @var AddressInterface $address \*/

$address = $this->container->get('sylius.factory.address')->createNew();

$address->setFirstName('Anne');

$address->setLastName('Shirley');

$address->setStreet('Avonlea');

$address->setCountryCode('CA');

$address->setCity('Canada');

$address->setPostcode('C0A 1N0');

$order->setShippingAddress($address);

$order->setBillingAddress($address);

```

Having the **Customer** and the **Address** set you can apply a state transition to your order.

Get the StateMachine for the Order via the StateMachineFactory with a proper schema, and apply a transition

and of course flush your order after that via the manager.

```

$stateMachineFactory = $this->container->get('sm.factory');

$stateMachine = $stateMachineFactory->get($order, OrderCheckoutTransitions::GRAPH);

$stateMachine->apply(OrderCheckoutTransitions::TRANSITION\_ADDRESS);

$this->container->get('sylius.manager.order')->flush();

```

**What happens during the transition?**

The method `process($order)` of the [CompositeOrderProcessor](https://github.com/Sylius/Sylius/blob/master/src/Sylius/Component/Order/Processor/CompositeOrderProcessor.php) is run.

######## Selecting shipping[¶](#selecting-shipping "Permalink to this headline")

It is a step where the customer selects the way their order will be shipped to them.

Basing on the ShippingMethods configured in the system the options for the Customer are provided together with their prices.

| Transition after step | Template |

| `addressed`-> `shipping\_selected` | `SyliusShopBundle:Checkout:shipping.html.twig` |

######### How to perform the Selecting shipping Step programmatically?[¶](#how-to-perform-the-selecting-shipping-step-programmatically "Permalink to this headline")

Before approaching this step be sure that your Order is in the `addressed` state. In this state your order

will already have a default ShippingMethod assigned, but in this step you can change it and have everything recalculated automatically.

Firstly either create new (see how in the [Shipments concept](/book/orders/shipments)) or retrieve a **ShippingMethod**

from the repository to assign it to your order’s shipment created defaultly in the addressing step.

```

// Let's assume you have a method with code 'DHL' that has everything set properly

$shippingMethod = $this->container->get('sylius.repository.shipping\_method')->findOneByCode('DHL');

// Shipments are a Collection, so even though you have one Shipment by default you have to iterate over them

foreach ($order->getShipments() as $shipment) {

    $shipment->setMethod($shippingMethod);

}

```

After that get the StateMachine for the Order via the StateMachineFactory with a proper schema,

and apply a proper transition and flush the order via the manager.

```

$stateMachineFactory = $this->container->get('sm.factory');

$stateMachine = $stateMachineFactory->get($order, OrderCheckoutTransitions::GRAPH);

$stateMachine->apply(OrderCheckoutTransitions::TRANSITION\_SELECT\_SHIPPING);

$this->container->get('sylius.manager.order')->flush();

```

**What happens during the transition?**

The method `process($order)` of the [CompositeOrderProcessor](https://github.com/Sylius/Sylius/blob/master/src/Sylius/Component/Order/Processor/CompositeOrderProcessor.php) is run.

Here this method is responsible for: controlling the **shipping charges** which depend on the chosen ShippingMethod,

controlling the **promotions** that depend on the shipping method.

######### Skipping shipping step[¶](#skipping-shipping-step "Permalink to this headline")

What if in the order you have only products that do not require shipping (they are downloadable for example)?

Note

When all of the [ProductVariants](index.html#document-book/products/products) of the order have the `shippingRequired`

property set to `false`, then Sylius assumes that the whole order **does not require shipping**,

and **the shipping step of checkout will be skipped**.

######## Selecting payment[¶](#selecting-payment "Permalink to this headline")

This is a step where the customer chooses how are they willing to pay for their order.

Basing on the PaymentMethods configured in the system the possibilities for the Customer are provided.

| Transition after step | Template |

| `shipping\_selected`-> `payment\_selected` | `SyliusShopBundle:Checkout:payment.html.twig` |

######### How to perform the Selecting payment step programmatically?[¶](#how-to-perform-the-selecting-payment-step-programmatically "Permalink to this headline")

Before this step your Order should be in the `shipping\_selected` state. It will have a default Payment selected after the addressing step,

but in this step you can change it.

Firstly either create new (see how in the [Payments concept](/book/orders/payments)) or retrieve a **PaymentMethod**

from the repository to assign it to your order’s payment created defaultly in the addressing step.

```

// Let's assume that you have a method with code 'paypal' configured

$paymentMethod = $this->container->get('sylius.repository.payment\_method')->findOneByCode('paypal');

// Payments are a Collection, so even though you have one Payment by default you have to iterate over them

foreach ($order->getPayments() as $payment) {

    $payment->setMethod($paymentMethod);

}

```

After that get the StateMachine for the Order via the StateMachineFactory with a proper schema,

and apply a proper transition and flush the order via the manager.

```

$stateMachineFactory = $this->container->get('sm.factory');

$stateMachine = $stateMachineFactory->get($order, OrderCheckoutTransitions::GRAPH);

$stateMachine->apply(OrderCheckoutTransitions::TRANSITION\_SELECT\_PAYMENT);

$this->container->get('sylius.manager.order')->flush();

```

**What happens during the transition?**

The method `process($order)` of the

[CompositeOrderProcessor](https://github.com/Sylius/Sylius/blob/master/src/Sylius/Component/Order/Processor/CompositeOrderProcessor.php)

is run and checks all the adjustments on the order.

######## Finalizing[¶](#finalizing "Permalink to this headline")

In this step the customer gets an order summary and is redirected to complete the payment they have selected.

| Transition after step | Template |

| `payment\_selected`-> `completed` | `SyliusShopBundle:Checkout:summary.html.twig` |

Note

The order will be processed through `OrderIntegrityChecker` in case to validate promotions applied to the order. If any of the promotions will expire during the finalizing checkout processor will remove this promotion and recalculate the order and update it.

######### How to complete Checkout programmatically?[¶](#how-to-complete-checkout-programmatically "Permalink to this headline")

Before executing the completing transition you can set some notes to your order.

```

$order->setNotes('Thank you dear shop owners! I am allergic to tape so please use something else for packaging.');

```

After that get the StateMachine for the Order via the StateMachineFactory with a proper schema,

and apply a proper transition and flush the order via the manager.

```

$stateMachineFactory = $this->container->get('sm.factory');

$stateMachine = $stateMachineFactory->get($order, OrderCheckoutTransitions::GRAPH);

$stateMachine->apply(OrderCheckoutTransitions::TRANSITION\_COMPLETE);

$this->container->get('sylius.manager.order')->flush();

```

**What happens during the transition?**

* The Order will have the **checkoutState** - `completed`,

* The Order will have the general **state** - `new` instead of `cart` it has had before the transition,

* When the Order is transitioned from `cart` to `new` the **paymentState** is set to `awaiting\_payment` and the **shippingState** to `ready`

The Checkout is finished after that.

####### Checkout related events[¶](#checkout-related-events "Permalink to this headline")

On each step of checkout a dedicated event is triggered.

| Event id |

| --- |

| `sylius.order.pre\_address` |

| `sylius.order.post\_address` |

| `sylius.order.pre\_select\_shipping` |

| `sylius.order.post\_select\_shipping` |

| `sylius.order.pre\_payment` |

| `sylius.order.post\_payment` |

| `sylius.order.pre\_complete` |

| `sylius.order.post\_complete` |

####### Learn more[¶](#learn-more "Permalink to this headline")

* [State Machine - Documentation](index.html#document-book/architecture/state_machine)

* [Orders - Concept Documentation](index.html#document-book/orders/orders)

###### Returns[¶](#returns "Permalink to this headline")

Return is the process of requesting and then bringing back merchandise, from a customer to the merchant, for a refund or exchange.

In distance sales like e-commerce returns (especially in the cooling-off period) are required by legislation.

####### Return Requests (Return Merchandise Agreements)[¶](#return-requests-return-merchandise-agreements "Permalink to this headline")

After a registered Customer makes an Order in a Sylius Plus shop, they are able to access it in their `My Account` section in shop.

Each fulfilled (paid and sent) Order has a Returns section, where the Customer can request a Return.

![_images/order_return_request.png](_images/order_return_request.png)

![_images/return_request_form.png](_images/return_request_form.png)

As you can see above, the Customer can choose which items they want to return, what resolution do they expect and provide

a written reason, including images as proofs of item damage for example.

There are three resolutions provided out-of-the-box:

* refund (give money back to the Customer)

* replacement (wrong items ordered, or sent should be replaced by the merchant to other specified by the customer)

* repair (the item(s) are damaged and should be repaired by the merchant according to the guarantee)

######## Customizing the available resolutions[¶](#customizing-the-available-resolutions "Permalink to this headline")

In order to **remove one of the predefined resolutions**:

Removing one of possible resolutions

Overwrite the `ReturnRequestResolutionsProvider` service:

```

# services.yaml

Sylius\Plus\Returns\Domain\Provider\ReturnRequestResolutionsProvider:

 class: Sylius\Plus\Returns\Application\Provider\StringReturnRequestResolutionsProvider

 arguments:

 - ['refund', 'repair'] # the "replacement" resolution has been removed

```

In order to **add a custom Resolution**:

Overwrite the `ReturnRequestResolutionsProvider` service and add a translation of the new resolution:

```

# services.yaml

Sylius\Plus\Returns\Domain\Provider\ReturnRequestResolutionsProvider:

 class: Sylius\Plus\Returns\Application\Provider\StringReturnRequestResolutionsProvider

 arguments:

 - ['refund', 'repair', 'size\_change']

```

```

```

# messages.en.yaml

> 

> 

> sylius\_plus:

> 

> ui:

> 

> returns:

> size\_change: “Size change”

> 

> 

> 

> 

> 

> 

After the Customer places a Return Request for chosen items they will not be able to place another Return Requests for these

items, they although can cancel the Return Request and then place it again.

Tip

Each placed Return Request has a pdf confirmation document on it, that can be downloaded from the return request show page

by both administrator and customer.

####### New Return Requests management[¶](#new-return-requests-management "Permalink to this headline")

Once a Return Request is placed it will become visible to the Administrators of the shop. They will get an email and be

able to see it in the Admin panel.

![_images/admin_return_requests.png](_images/admin_return_requests.png)

The administrators can manage the return request in state `new` (newly placed) here:

![_images/admin_return_request_show.png](_images/admin_return_request_show.png)

As you can see above the Admin can:

* change the resolution of the Return Request - if the chosen one cannot be fulfilled, the Customer will get a notifying email

* accept/reject the Return Request providing a reasoning to the Customer, the Customer will get a notifying email

A Return Request that becomes rejected can no longer get processed. The Customer needs to open a new one if they still feel

the need to return the items.

####### Accepted Return Requests management[¶](#accepted-return-requests-management "Permalink to this headline")

After a Return Request gets accepted it can be processed according to the resolution that was chosen.

######## Refund[¶](#refund "Permalink to this headline")

Return requests that have the `refund` resolution chosen, have an option to at once accept the return request and proceed

to the refunding process with just one button.

If you do not do the refund then, after accepting, the return request management section will look like that:

![_images/admin_return_request_refund.png](_images/admin_return_request_refund.png)

Besides making a refund, you can mark here that a package from customer has been received; or resolve the return request,

after which you will no longer be able to process it.

Tip

To learn about the refunding process check the [Refunds](index.html#document-book/orders/refunds) documentation section.

######## Replacement[¶](#replacement "Permalink to this headline")

Same as for other resolutions you are able to mark returned items as received, make an additional refund, resolving the request.

![_images/admin_return_request_replacement.png](_images/admin_return_request_replacement.png)

But also, as you can see in the image above, you can make a replacement Order.

This will help you create a new Order with the items requested by the customer. While preparing a replacement Order you can modify the items

being replaced, so that you can for example change the size, color, quantity or even send a completely different item.

The Order will be free and marked as replacement, so you won’t loose it. This feature let’s you properly track the inventory and new shipment.

######### Repair[¶](#repair "Permalink to this headline")

Same as for other resolutions you are able to mark returned items as received, make an additional refund, resolving the request,

What is specific for return requests of repair type, you are able to the mark repaired items as sent, after you have received them.

![_images/admin_return_request_repair.png](_images/admin_return_request_repair.png)

####### API coverage[¶](#api-coverage "Permalink to this headline")

Sylius Plus provides a route that allows accepting or rejecting return request with an API call:

```

POST /api/v1/return-requests/{id}/accept

```

The `id` is an id of return request that we want to accept. Content of the request may contain response of return request:

```

{

 "response": "Return request confirmed and accepted."

}

```

```

POST /api/v1/return-requests/{id}/reject

```

The `id` is an id of return request that we want to accept. Content of the request may contain response of return request:

```

{

 "response": "We are not able to replace this item."

}

```

The response can also be empty:

```

{}

```

####### Learn more[¶](#learn-more "Permalink to this headline")

* [Sylius/RefundPlugin](https://github.com/Sylius/RefundPlugin)

* [Refunds](index.html#document-book/orders/refunds)

* [Emails related to Return Requests](index.html#document-book/architecture/emails)

[![_images/banner6.png](_images/banner6.png)](https://sylius.com/plus/?utm_source=docs)

###### Refunds[¶](#refunds "Permalink to this headline")

Return is a two-step (Returns & Refunds) process of a customer giving previously purchased item(s) back to the shop,

and in turn receiving a financial refund in the original payment method, exchange for another item etc..

Sylius is providing both steps of this process: one via a [free open-source plugin](https://github.com/Sylius/RefundPlugin)

(Refunds) and the other via the [Sylius Plus](https://sylius.com/plus/) version (Returns).

####### How to refund money to a Customer in Sylius?[¶](#how-to-refund-money-to-a-customer-in-sylius "Permalink to this headline")

Having the plugin installed you will notice a new button on admin Order show page, the “Refunds” button in the top-right corner.

On the refunds paged for this Order you are able to refund order items all at once or one by one fully or partially,

you can also return the shipping cost. The original payment method of the order is chosen, but it can be modified.

######## Credit Memos[¶](#credit-memos "Permalink to this headline")

After creating a refund it is documented in the system with a Credit Memo document, which is an opposite to an invoice.

Credit Memos look like invoices, although not for incomes but for store’s account charges. Credit Memos appear

as a separate section under Sales in the left admin menu.

######## Refund Payments[¶](#refund-payments "Permalink to this headline")

Alongside the Credit Memo document, a new refund Payment is created in response to a Refund. This is a convenient object,

with which you can automate the process of paying the refunds to the customer;s account.

####### Learn more[¶](#learn-more "Permalink to this headline")

* [Sylius/RefundPlugin](https://github.com/Sylius/RefundPlugin)

* [Returns](index.html#document-book/orders/returns)

* [Other Sylius plugins](index.html#document-book/plugins/index)

* [Orders](index.html#document-book/orders/orders)

* [Cart flow](index.html#document-book/orders/cart-flow)

* [Taxation](index.html#document-book/orders/taxation)

* [Adjustments](index.html#document-book/orders/adjustments)

* [Cart Promotions](index.html#document-book/orders/cart-promotions)

* [Coupons](index.html#document-book/orders/coupons)

* [Payments](index.html#document-book/orders/payments)

* [Invoices](index.html#document-book/orders/invoices)

* [Shipments](index.html#document-book/orders/shipments)

* [Checkout](index.html#document-book/orders/checkout)

* [Returns](index.html#document-book/orders/returns)

* [Refunds](index.html#document-book/orders/refunds)

* [Orders](index.html#document-book/orders/orders)

* [Cart flow](index.html#document-book/orders/cart-flow)

* [Taxation](index.html#document-book/orders/taxation)

* [Adjustments](index.html#document-book/orders/adjustments)

* [Cart Promotions](index.html#document-book/orders/cart-promotions)

* [Coupons](index.html#document-book/orders/coupons)

* [Payments](index.html#document-book/orders/payments)

* [Invoices](index.html#document-book/orders/invoices)

* [Shipments](index.html#document-book/orders/shipments)

* [Checkout](index.html#document-book/orders/checkout)

* [Returns](index.html#document-book/orders/returns)

* [Refunds](index.html#document-book/orders/refunds)

#### API[¶](#api "Permalink to this headline")

Warning

The new, unified Sylius API is still under development, that’s why the whole `ApiBundle` is tagged with `@experimental`.

This means that all code from `ApiBundle` is excluded from [Backward Compatibility Promise](index.html#document-book/organization/backward-compatibility-promise).

This chapter will explain to you how to start with our new API, show concepts used in it, and you will inform you why we have decided to rebuild entire api from scratch.

To use this API remember to generate JWT token. For more information, please visit [jwt package documentation](https://github.com/lexik/LexikJWTAuthenticationBundle/blob/master/Resources/doc/index.md#generate-the-ssh-keys/)

This part of the documentation is about the currently developed unified API for the Sylius platform.

##### API[¶](#api "Permalink to this headline")

Warning

The new, unified Sylius API is still under development, that’s why the whole `ApiBundle` is tagged with `@experimental`.

This means that all code from `ApiBundle` is excluded from [Backward Compatibility Promise](index.html#document-book/organization/backward-compatibility-promise).

To use this API remember to generate JWT token. For more information, please visit [jwt package documentation](https://github.com/lexik/LexikJWTAuthenticationBundle/blob/v2.11.0/Resources/doc/index.md#generate-the-ssl-keys).

This part of the documentation is about the currently developed unified API for the Sylius platform.

###### Introduction[¶](#introduction "Permalink to this headline")

Warning

The new, unified Sylius API is still under development, that’s why the whole `ApiBundle` is tagged with `@experimental`.

This means that all code from `ApiBundle` is excluded from [Backward Compatibility Promise](index.html#document-book/organization/backward-compatibility-promise).

You can enable entire API by changing the flag `sylius\_api.enabled` to `true` in `config/packages/\_sylius.yaml`.

We have decided that we should rebuild our API and use API Platform to build a truly mature, multi-purpose API

which can define a new standard for headless e-commerce backends.

We will be supporting API Platform out-of-the-box. Secondly, it means that both APIs (Admin API and Shop API) will

be deprecated. We are not dropping them right now, but they will not receive further development. In the later phase,

we should provide an upgrade path for currently working apps. Last, but not least, you can already track our progress.

All the PR’s will be aggregated [in this issue](https://github.com/Sylius/Sylius/issues/11250) and the documentation

can be already found [here](http://master.demo.sylius.com/api/v2/docs).

###### Authorization[¶](#authorization "Permalink to this headline")

In the new API all admin routes are protected by JWT authentication. If you would like to test these endpoints

[in our Swagger UI docs](http://master.demo.sylius.com/api/v2/docs), you need to retrieve a JWT token first.

You could do that by using an endpoint with default credentials for API administrators:

[![_images/api_platform_authentication_endpoint.png](_images/api_platform_authentication_endpoint.png)](_images/api_platform_authentication_endpoint.png)

  

In the response, you will get a token that has to be passed in each request header. In the Swagger UI, you can set

the authentication token for each request.

[![_images/api_platform_authentication_response.png](_images/api_platform_authentication_response.png)](_images/api_platform_authentication_response.png)

  

Notice the **Authorize** button and unlocked padlock near the available URLs:

[![_images/api_platform_not_authorized.png](_images/api_platform_not_authorized.png)](_images/api_platform_not_authorized.png)

  

Click the **Authorize** button and put the authentication token (remember about the `Bearer` prefix):

[![_images/api_platform_authorization.png](_images/api_platform_authorization.png)](_images/api_platform_authorization.png)

  

After clicking **Authorize**, you should see locked padlock near URLs and the proper header should be added to each API call:

[![_images/api_platform_authorized.png](_images/api_platform_authorized.png)](_images/api_platform_authorized.png)

###### Sylius API paths[¶](#sylius-api-paths "Permalink to this headline")

All paths in new API have the same prefix structure: `/api/v2/admin/` or `/api/v2/shop/`

The `/api/v2` prefix part indicates the API version and the `/admin/` or `/shop/` prefixes are necessary for authorization purposes.

When you are adding a new path to API resource configuration, you should remember to add also proper prefix.

You can declare the entire path for each operation (without `/api/v2/` as this part is configured globally):

```

<collectionOperation name="admin\_get">

 <attribute name="method">GET</attribute>

 <attribute name="path">admin/orders</attribute>

</collectionOperation>

```

or you can add a proper prefix for all paths in the chosen resource:

```

<attribute name="route\_prefix">shop</attribute>

```

Note

In some situations, you may need to add a path with a custom structure, in this case, you will probably need to

configure also the appropriate access in the security file (`config/security.yaml`)

###### Translations[¶](#translations "Permalink to this headline")

In the shop part of our API translatable entities are translated at the server-side and users get an already translated field in the response.

By default it will be provided in default locale configured on the channel, different locale must be configured in the header (for example accept-language: de\_DE)

Warning

Remember that the chosen locale has to be enabled in the channel!

###### Legacy API[¶](#legacy-api "Permalink to this headline")

We have decided to rebuild our APIs and unify them with API Platform.

Previously we had 2 separate APIs for shop ([SyliusShopAPi Plugin)](https://github.com/Sylius/ShopApiPlugin), and for admin ([SyliusAdminApiBundle](https://github.com/Sylius/SyliusAdminApiBundle)).

Both of them are using the [FOSRestBundle](https://github.com/FriendsOfSymfony/FOSRestBundle), and make operation using commands and events.

This approach is easy to understand and implement, but when we need to customize something we need to overwrite many files (command, event, command handler, event listener etc).

The second reason to create a new Sylius API from scratch is that the API Platform is a modern framework for API and it replaces FOSRestBundle.

We will fix security issues in our legacy APIs but all new features will be developed only in the new API.

* [Introduction](index.html#document-book/api/introduction)

* [Authorization](index.html#document-book/api/authorization)

* [Sylius API paths](index.html#document-book/api/sylius_api_paths)

* [Translations](index.html#document-book/api/translations)

* [Legacy API](index.html#document-book/api/legacy_api)

* [Introduction](index.html#document-book/api/introduction)

* [Authorization](index.html#document-book/api/authorization)

* [Sylius API paths](index.html#document-book/api/sylius_api_paths)

* [Translations](index.html#document-book/api/translations)

* [Legacy API](index.html#document-book/api/legacy_api)

#### Frontend[¶](#frontend "Permalink to this headline")

This chapter will introduce you to the world of Sylius’ frontend.

##### Frontend[¶](#frontend "Permalink to this headline")

This chapter will introduce you to the world of Sylius’ frontend.

###### Overview[¶](#overview "Permalink to this headline")

Note

If you’re still using Gulp consider switching to Webpack (as it became our default build tool) using [our guide](index.html#document-cookbook/frontend/migrating-to-webpack-1-12-or-later).

####### Requirements[¶](#requirements "Permalink to this headline")

We recommend using Node.js `16.x` as it is the current LTS version. However, Sylius frontend is also compatible with Node.js `14.x` and `18.x`.

In Sylius, we use `yarn` as a package manager, but there are no obstacles to using `npm`.

####### Stack overview[¶](#stack-overview "Permalink to this headline")

Sylius frontend is based on the following technologies:

* Semantic UI

* jQuery

* Webpack

Of course, it is not a complete list of packages we use, but these are the most important. To see all packages used in Sylius check the [package.json](https://github.com/Sylius/Sylius/blob/1.12/package.json) file.

####### Webpack vs Gulp[¶](#webpack-vs-gulp "Permalink to this headline")

For a long time, the Gulp was the default build tool for Sylius. Since version 1.12, Gulp has been replaced by Webpack. Gulp’s configs are still present due to compatibility with previous versions, but we do not recommend using them anymore.

####### Webpack Encore[¶](#webpack-encore "Permalink to this headline")

To improve the experience of using Webpack with Sylius, we use the Webpack Encore package made by the Symfony team. [You can read more about Encore in the official Symfony documentation](https://symfony.com/doc/current/frontend.html#webpack-encore).

####### Assets structure[¶](#assets-structure "Permalink to this headline")

We provide the following assets directory structure:

```

<project_root>

├── assets

│ ├── admin <- all admin-related assets should be placed here, they are only included when you are in the admin panel

│ │ ├── entry.js <- entry point for admin assets, do not remove nor rename it unless you know what you do

│ ├── shop <- all shop-related assets should be placed here, they are only included when you are in the shop

│ │ ├── entry.js <- entry point for shop assets, do not remove nor rename it unless you know what you do

```

When you want to add e.g. SCSS files or images your structure might look like this:

```

<project_root>

├── assets

│ ├── admin

│ │ ├── entry.js

│ ├── shop

│ │ ├── styles

│ │ │ ├── app.scss

│ │ ├── images

│ │ │ ├── logo.png

│ │ ├── entry.js

```

If you want to know how to import and manage those assets take a look at our [Managing assets](index.html#document-book/frontend/managing-assets) guide.

###### Managing assets[¶](#managing-assets "Permalink to this headline")

This topic covers the most common cases of managing assets using Webpack.

####### Importing CSS/SCSS files[¶](#importing-css-scss-files "Permalink to this headline")

Let’s say you have the following assets directory structure:

```

assets/

├── shop

│   ├── styles

│   │   ├── app.scss

│   └── entry.js

```

If you want to add the `app.scss` to your build process, you can do it by importing your `app.scss` in your `entry.js` file:

```

// assets/shop/entry.js

import './styles/app.scss';

// ...

```

After building, your `css` result file (on a default settings) should be available under `<project\_root>/public/build/app/shop/app-shop-entry.css` path.

####### Importing images[¶](#importing-images "Permalink to this headline")

Let’s say you have the following assets directory structure:

```

assets/

├── shop

│   ├── images

│   │   ├── logo.png

│   └── entry.js

```

If you want to add the `logo.png` to your build process, you can do it by importing your `logo.png` in your `entry.js` file:

```

// assets/shop/entry.js

import logo from './images/logo.png';

// ...

```

After building, your image (with default settings) should be available under `<project\_root>/public/build/app/shop/images/logo.<hash>.png` path.

Note

You do not need to worry about `<hash>` part of the file name. It will be automatically generated by Webpack and handled in Twig by the `asset()` function.

####### Importing fonts[¶](#importing-fonts "Permalink to this headline")

Let’s say you have the following assets directory structure:

```

assets/

├── shop

│   ├── fonts

│   │   ├── roboto.woff

│   └── entry.js

```

If you want to add the `roboto.woff` to your build process, you can do it by importing your `roboto.woff` in your `entry.js` file:

```

// assets/shop/entry.js

import roboto from './fonts/roboto.woff';

// ...

```

After building, your font (on a default settings) should be available under `<project\_root>/public/build/app/shop/fonts/roboto.<hash>.woff` path.

####### Using assets in Twig templates[¶](#using-assets-in-twig-templates "Permalink to this headline")

So far, we have seen how to import assets in JavaScript files. But what if you want to use them in Twig templates? The answer is simple: use the `asset()` function.

```

<img src="{{ asset('build/app/shop/images/logo.png', 'app.shop') }}" />

```

So far, when using `asset()` function you have been passing only one argument. Using Webpack we need to pass a second argument pointing to our package name from `framework.assets.package` configuration.

By default, for `Sylius Standard`, we configured `shop` and `admin` packages for Sylius’ assets. We also defined `app.shop` and `app.admin` packages to avoid conflicts between assets’ names.

* [Overview](index.html#document-book/frontend/overview)

* [Managing assets](index.html#document-book/frontend/managing-assets)

* [Overview](index.html#document-book/frontend/overview)

* [Managing assets](index.html#document-book/frontend/managing-assets)

#### Themes[¶](#themes "Permalink to this headline")

Here you will learn basics about the theming concept of sylius. How to change the theme of your shop? keep reading!

##### Themes[¶](#themes "Permalink to this headline")

###### Themes[¶](#themes "Permalink to this headline")

Theming is a method of customizing how your channels look like in Sylius. Each channel can have a different theme.

####### What is the purpose of using themes?[¶](#what-is-the-purpose-of-using-themes "Permalink to this headline")

There are some criteria that you have to analyze before choosing either [standard Symfony template overriding](index.html#document-customization/template) or themes.

**When you should choose standard template overriding:**

* you have only one channel

* **or** you do not need different looks/themes on each of you channels

* you need only basic changes in the views (changing colors, some blocks rearranging)

**When you should use Sylius themes:**

* you have more than one channel for a single Sylius instance

* **and** you want each channel to have their own look and behaviour

* you change a lot of things in the views

####### How to enable themes in a project?[¶](#how-to-enable-themes-in-a-project "Permalink to this headline")

To use themes inside of your project you need to add these few lines to your `config/packages/sylius\_theme.yaml`.

```

sylius\_theme:

 sources:

 filesystem:

 scan\_depth: 1

 directories:

 - "%kernel.project\_dir%/themes"

```

####### How to create themes?[¶](#how-to-create-themes "Permalink to this headline")

Let’s see how to customize the login view inside of your custom theme.

1. Inside of the `themes/` directory create a new directory for your theme:

Let it be `CrimsonTheme/` for instance.

2. Create `composer.json` for your theme:

```

{

 "name": "acme/crimson-theme",

 "authors": [

 {

 "name": "James Potter",

 "email": "prongs@example.com"

 }

 ],

 "extra": {

 "sylius-theme": {

 "title": "Crimson Theme"

 }

 }

}

```

3. Install theme assets

Theme assets are installed by running the `sylius:theme:assets:install` command, which is supplementary for and should be used after `assets:install`.

```

bin/console sylius:theme:assets:install

```

The command run with `--symlink` or `--relative` parameters creates symlinks for every installed asset file,

not for entire asset directory (eg. if `AcmeBundle/Resources/public/asset.js` exists, it creates symlink `public/bundles/acme/asset.js`

leading to `AcmeBundle/Resources/public/asset.js` instead of symlink `public/bundles/acme/` leading to `AcmeBundle/Resources/public/`).

When you create a new asset or delete an existing one, it is required to rerun this command to apply changes (just as the hard copy option works).

Note

Whenever you install a new bundle with assets you will need to run `sylius:theme:assets:install` again to make sure they are accessible in your theme.

4. Customize a template:

In order to customize the login view you should take the content of `@SyliusShopBundle/views/login.html.twig` file and …

* Before theme-bundle v2,

paste it to your theme directory: `themes/CrimsonTheme/SyliusShopBundle/views/login.html.twig`

(There is more information in the official documentation about [theme structure v1.5.1](https://github.com/Sylius/SyliusThemeBundle/blob/v1.5.1/docs/your_first_theme.md#theme-structure))

* From [theme-bundle v2](https://github.com/Sylius/SyliusThemeBundle/releases/tag/v2.0.0),

paste it to your theme directory: `themes/CrimsonTheme/templates/bundles/SyliusShopBundle/login.html.twig`

(There is more information in the official documentation about [theme structure v2.0.0](https://github.com/Sylius/SyliusThemeBundle/blob/v2.0.0/docs/your_first_theme.md#theme-structure))

Let’s remove the registration column in this example:

```

{% extends '@SyliusShop/layout.html.twig' %}

{% form\_theme form '@SyliusUi/Form/theme.html.twig' %}

{% import '@SyliusUi/Macro/messages.html.twig' as messages %}

{% block content %}

 {% include '@SyliusShop/Login/\_header.html.twig' %}

 <div class="ui padded segment">

 <div class="ui one column very relaxed stackable grid">

 <div class="column">

 <h4 class="ui dividing header">{{ 'sylius.ui.registered\_customers'|trans }}</h4>

 <p>{{ 'sylius.ui.if\_you\_have\_an\_account\_sign\_in\_with\_your\_email\_address'|trans }}.</p>

 {{ form\_start(form, {'action': path('sylius\_shop\_login\_check'), 'attr': {'class': 'ui loadable form', 'novalidate': 'novalidate'}}) }}

 {% include '@SyliusShop/Login/\_form.html.twig' %}

 <button type="submit" class="ui blue submit button">{{ 'sylius.ui.login'|trans }}</button>

 <a href="{{ path('sylius\_shop\_request\_password\_reset\_token') }}" class="ui right floated button">{{ 'sylius.ui.forgot\_password'|trans }}</a>

 {{ form\_end(form, {'render\_rest': false}) }}

 </div>

 </div>

 </div>

{% endblock %}

```

Tip

Learn more about customizing templates [here](index.html#document-customization/template).

You can check major modifications in theme-bundle structure and configuration [here](https://github.com/Sylius/SyliusThemeBundle/blob/master/docs/important_changes.md)

5. Choose your new theme on the channel:

In the administration panel go to channels and change the theme of your desired channel to `Crimson Theme`.

![_images/channel_theme.png](_images/channel_theme.png)

6. If changes are not yet visible, clear the cache:

```

php bin/console cache:clear

```

####### Learn more[¶](#learn-more "Permalink to this headline")

* [Theme - Bundle Documentation](https://github.com/Sylius/SyliusThemeBundle/blob/master/docs/index.md).

###### Theming with BootstrapTheme[¶](#theming-with-bootstraptheme "Permalink to this headline")

This tutorial will guide you on how to create your own theme based on [BootstrapTheme](https://github.com/Sylius/BootstrapTheme) using Webpack.

Tutorial is divided into 3 parts:

1. [Creating a new theme based on BootstrapTheme](#creating-a-new-theme-based-on-bootstraptheme)

2. [Webpack Encore configuration](#webpack-encore-configuration)

3. [Customization](#customization)

####### 1. Creating a new theme based on BootstrapTheme[¶](#creating-a-new-theme-based-on-bootstraptheme "Permalink to this headline")

Install BootstrapTheme

```

composer require sylius/bootstrap-theme

```

In the `config/packages/\_sylius.yaml` file, add the path to the installed package

```

sylius\_theme:

 sources:

 filesystem:

 directories:

 - "%kernel.project\_dir%/vendor/sylius/bootstrap-theme"

 - "%kernel.project\_dir%/themes"

```

Create your custom theme based on BootstrapTheme. In the `themes` directory, create a new folder

- name it as you like, e.g. `BootstrapChildTheme` and create `composer.json` with basic information

```

{

 "name": "acme/bootstrap-child-theme",

 "description": "Bootstrap child theme",

 "license": "MIT",

 "authors": [

 {

 "name": "James Potter",

 "email": "prongs@example.com"

 }

 ],

 "extra": {

 "sylius-theme": {

 "title": "Bootstrap child theme",

 "parents": [ "sylius/bootstrap-theme" ]

 }

 }

}

```

Now you can go to the channel settings in the admin panel and select the created theme as default.

####### 2. Webpack Encore configuration[¶](#webpack-encore-configuration "Permalink to this headline")

You need to prepare a new theme for working with webpack and include it in the build process.

Install missing BootstrapTheme dependencies

```

yarn add sass-loader@^7.0.0 node-sass lodash.throttle -D

yarn add bootstrap bootstrap.native glightbox axios form-serialize @fortawesome/fontawesome-svg-core @fortawesome/free-brands-svg-icons @fortawesome/free-regular-svg-icons @fortawesome/free-solid-svg-icons

```

in `theme/BootstrapChildTheme/assets` create 2 files: `entry.js` and `scss/index.scss`

`entry.js` is the main file for your theme. All files used in the theme will be imported here.

First, add the files used in the BootstrapTheme and your newly created scss file

```

import '../../../vendor/sylius/bootstrap-theme/assets/js/index';

import './scss/index.scss';

import '../../../vendor/sylius/bootstrap-theme/assets/media/sylius-logo.png';

import '../../../vendor/sylius/bootstrap-theme/assets/js/fontawesome';

```

`index.scss` is the main file for styles, import styles used in the BootstrapTheme

```

@import '../../../../vendor/sylius/bootstrap-theme/assets/scss/index';

```

In the `webpack.config.js` file, add configurations for the new theme

```

Encore.reset();

Encore

 .setOutputPath('public/bootstrap-theme')

 .setPublicPath('/bootstrap-theme')

 .addEntry('app', './themes/BootstrapChildTheme/assets/entry.js')

 .disableSingleRuntimeChunk()

 .cleanupOutputBeforeBuild()

 .enableSassLoader()

 .enableSourceMaps(!Encore.isProduction())

 .enableVersioning(Encore.isProduction());

const bootstrapThemeConfig = Encore.getWebpackConfig();

bootstrapThemeConfig.name = 'bootstrapTheme';

```

Also add `bootstrapThemeConfig` to export at the end of the file.

In the app config, add paths where the compiled files will be located:

In the `config/packages/assets.yaml` add:

```

framework:

 assets:

 packages:

 bootstrapTheme:

 json\_manifest\_path: '%kernel.project\_dir%/public/bootstrap-theme/manifest.json'

```

and in the `config/packages/webpack\_encore.yaml` add:

```

webpack\_encore:

 output\_path: '%kernel.project\_dir%/public/build/default'

 builds:

 bootstrapTheme: '%kernel.project\_dir%/public/bootstrap-theme'

```

Now you can use one of the commands `yarn encore dev`, `yarn encore production` or `yarn encore dev-server`

to compile all assets. Open the page - everything should work.

####### 3. Customization[¶](#customization "Permalink to this headline")

######## Changing styles[¶](#changing-styles "Permalink to this headline")

To add new styles, create a new scss file in your theme’s `assets` folder, and then import it into the

`index.scss`. After compilation, new styles should appear on the page.

You can also override the default styles used in BootstrapTheme by changing some variables. To do that,

create a file `\_variables.scss` in the `assets` folder, change e.g. primary color by typing

`$primary: blue;`, and then import this file into `index.scss`.

Tip

Variables should be overwritten before importing styles from BootstrapTheme, so the `\_variables.scss`

file should be imported at the beginning of the `index.scss` file.

######## Adding new assets[¶](#adding-new-assets "Permalink to this headline")

To add new assets to the theme, such as scripts or images, simply place them in your theme’s directory

and then import them into the file `entry.js`

######## Overwriting templates[¶](#overwriting-templates "Permalink to this headline")

To overwrite the template, copy the selected twig file from BootstrapTheme and paste it into the same place

in your theme. For example, if you want to change something in the `layout.html.twig` file,

copy it to `themes/BootstrapChildTheme/templates/bundles/SyliusShopBundle`

* [Themes](index.html#document-book/themes/themes)

* [Theming with BootstrapTheme](index.html#document-book/themes/bootstrap-theme)

* [Themes](index.html#document-book/themes/themes)

* [Theming with BootstrapTheme](index.html#document-book/themes/bootstrap-theme)

#### Sylius Plus[¶](#sylius-plus "Permalink to this headline")

[Sylius Plus](https://sylius.com/plus/), which is a licensed edition of Sylius, gives you all the power of Open Source and much more.

It comes with a set of enterprise-grade features and technical support from its creators.

As the state-of-the-art eCommerce platform, it reduces risks and increases your ROI.

Documentation sections of The Book referring to Sylius Plus features are:

##### Loyalty Rule[¶](#loyalty-rule "Permalink to this headline")

###### Adding new Loyalty Rule Configuration to API[¶](#adding-new-loyalty-rule-configuration-to-api "Permalink to this headline")

After creating a new loyalty rule configuration you need to add a new `LoyaltyRuleActionDataTransformer` that implements `Sylius\Plus\Loyalty\Infrastructure\DataTransformer\LoyaltyRuleActionDataTransformerInterface`

To be more flexible, new `LoyaltyRuleConfiguration` is created by new DTO object: `Sylius\Plus\Loyalty\Application\DTO\LoyaltyRuleAction` that has `configuration` field as an `array`

That field allows you to add any configuration and in the aftermath, you have to transform this object to `Sylius\Plus\Loyalty\Domain\Model\LoyaltyRuleConfiguration\LoyaltyRuleConfigurationInterface` instance by your custom `LoyaltyRuleActionDataTransformer`

Exemplary `LoyaltyRuleActionYourCustomConfigurationDataTransformer` for new your\_custom\_configuration configuration type:

```

use Sylius\Plus\Loyalty\Application\DTO\LoyaltyRuleActionInterface as LoyaltyRuleActionInterfaceDTO;

use Sylius\Plus\Loyalty\Domain\Model\LoyaltyRuleActionInterface as LoyaltyRuleActionInterfaceModel;

final class LoyaltyRuleActionYourCustomConfigurationDataTransformer implements LoyaltyRuleActionDataTransformerInterface

{

    /\*\* @var LoyaltyRuleActionFactoryInterface \*/

    private $loyaltyRuleActionFactory;

    public function \_\_construct(LoyaltyRuleActionFactoryInterface $loyaltyRuleActionFactory)

    {

        $this->loyaltyRuleActionFactory = $loyaltyRuleActionFactory;

    }

    public function transform(LoyaltyRuleActionInterfaceDTO $object, string $to, array $context = []): LoyaltyRuleActionInterfaceModel

    {

        //$object is an input LoyaltyRuleActionInterfaceDTO instance that allow you get new changes and create/update a LoyaltyRuleActionInterfaceModel object

        if (isset($context['object\_to\_populate'])) {

            /\*\* @var LoyaltyRuleActionInterfaceModel $loyaltyRuleAction \*/

            $loyaltyRuleAction = $context['object\_to\_populate'];

            //update object while using PUT or PATCH method

            return $loyaltyRuleAction;

        }

        //create new LoyaltyRuleActionInterfaceModel object while using POST method

        return $this

            ->loyaltyRuleActionFactory

            ->createNewWithDataAndConfiguration('your\_custom\_configuration', $configuration)

        ;

    }

    public function supportsTransformation(string $actionType): bool

    {

        return $actionType === 'your\_custom\_configuration';

    }

}

```

Next you must register `LoyaltyRuleActionYourCustomConfigurationDataTransformer` as a service with tag `sylius\_plus.api.loyalty\_rule\_action\_data\_transformer` and key that is same as given configuration type:

```

<service id="...\LoyaltyRuleActionYourCustomConfigurationDataTransformer" public="true">

 <argument type="service" id="Sylius\Plus\Loyalty\Application\Factory\LoyaltyRuleActionFactory" />

 <tag name="sylius\_plus.api.loyalty\_rule\_action\_data\_transformer" key="your\_custom\_configuration" />

</service>

```

[![_images/banner5.png](_images/banner5.png)](https://sylius.com/plus/?utm_source=docs)

[![_images/banner3.png](_images/banner3.png)](https://sylius.com/plus/?utm_source=docs)

#### Sylius Plugins[¶](#sylius-plugins "Permalink to this headline")

The collection of Sylius Plugins and basic introduction to the concept of plugins.

##### Sylius Plugins[¶](#sylius-plugins "Permalink to this headline")

Sylius as a platform has a lot of space for various customizations and extensions. It aims to provide a simple schema for

developing plugins. Anything you can imagine can be implemented and added to the Sylius framework as a plugin.

###### What are the plugins for?[¶](#what-are-the-plugins-for "Permalink to this headline")

The plugins either modify or extend Sylius default behaviour, providing useful features that

are built on top of the Sylius Core.

Exemplary features may be: Social media buttons, newsletter, wishlists, payment gateways integrations etc.

Tip

**The list of all Sylius Plugins (official and approved) is available on the Sylius website** [here](https://plugins.sylius.com/plugins/).

####### How to create a plugin for Sylius?[¶](#how-to-create-a-plugin-for-sylius "Permalink to this headline")

Sylius plugin is nothing more but a regular Symfony bundle adding custom behaviour to the default Sylius application.

The best way to create your own plugin is to use [Sylius plugin skeleton](https://github.com/Sylius/PluginSkeleton),

which has built-in infrastructure for designing and testing using [Behat](https://behat.org/en/latest/).

Note

This doc is a very short introduction to plugin development. For a more detailed guide please head to [Sylius Plugins](index.html#document-book/plugins/index).

Quickstart to plugins development:

######## 1. Create project using Composer.[¶](#create-project-using-composer "Permalink to this headline")

```

composer create-project sylius/plugin-skeleton SyliusMyFirstPlugin

```

Note

The plugin can be created anywhere, not only inside a Sylius application, because it already has the test environment inside.

######## 2. Get familiar with basic plugin design.[¶](#get-familiar-with-basic-plugin-design "Permalink to this headline")

The skeleton comes with simple application that greets a customer. There are feature scenarios in `features` directory;

exemplary bundle with a controller, a template and a routing configuration in `src`;

and the testing infrastructure in `tests`.

Note

The `tests/Application` directory contains a sample Symfony application used to test your plugin.

######## 3. Remove boilerplate files and rename your bundle.[¶](#remove-boilerplate-files-and-rename-your-bundle "Permalink to this headline")

In most cases you don’t want your Sylius plugin to greet the customer like it is now, so feel free to remove unnecessary

controllers, assets and features. You will also want to change the plugin’s namespace from `Acme\SyliusExamplePlugin` to a

more meaningful one. Keep in mind that these changes also need to be done in `tests/Application` and `composer.json`.

Tip

Refer to chapter 5 for the naming conventions to be used.

######## 4. Implement your awesome features.[¶](#implement-your-awesome-features "Permalink to this headline")

Looking at existing Sylius plugins like

* [Sylius/ShopAPIPlugin](https://github.com/Sylius/SyliusShopApiPlugin)

* [bitbag-commerce/PayUPlugin](https://github.com/bitbag-commerce/PayUPlugin)

* [stefandoorn/sitemap-plugin](https://github.com/stefandoorn/sitemap-plugin)

* [bitbag-commerce/CmsPlugin](https://github.com/bitbag-commerce/CmsPlugin)

is a great way to start developing your own plugins.

You are strongly encouraged to use [BDD](https://www.agilealliance.org/glossary/bdd/) with [Behat](https://behat.org/en/latest/), [phpspec](https://www.phpspec.net/en/stable/) and [PhpUnit](https://phpunit.de/)

to ensure your plugin’s extraordinary quality.

Tip

For the plugins, the suggested way of modifying Sylius is using [the Customization Guide](index.html#document-customization/index).

There you will find a lot of help while trying to modify templates, state machines, controllers and many, many more.

######## 5. Naming conventions[¶](#naming-conventions "Permalink to this headline")

Besides the way you are creating plugins (based on our skeleton or on your own), there are a few naming conventions that should be followed:

> 

> * Repository name should use PascalCase, must have a `Sylius\*` prefix and a `Plugin` suffix

> * Project composer name should use dashes as a separator, must have a `sylius` prefix and a `plugin` suffix, e.g.: `sylius-invoice-plugin`.

> * Bundle class name should start with vendor name, followed by `Sylius` and suffixed by `Plugin` (instead of `Bundle`), e.g.: `VendorNameSyliusInvoicePlugin`.

> * Bundle extension should be named similar, but suffixed by the Symfony standard `Extension`, e.g.: `VendorNameSyliusInvoiceExtension`.

> * Bundle class must use the `Sylius\Bundle\CoreBundle\Application\SyliusPluginTrait` trait.

> * Namespace should follow [PSR-4](https://www.php-fig.org/psr/psr-4/). The top-level namespace should be the vendor name. The second-level should be prefixed by `Sylius` and suffixed by `Plugin` (e.g. `VendorName\SyliusInvoicePlugin`)

> 

> 

> 

Note

Following the naming strategy for the bundle class & extension class prevents configuration key collision. Following the convention mentioned

above generates the default configuration key as e.g. `vendor\_name\_sylius\_invoice\_plugin`.

The rules are to be applied to all bundles which will provide an integration with the whole Sylius platform

(`sylius/sylius` or `sylius/core-bundle` as dependency).

Reusable components for the whole Symfony community, which will be based just on some Sylius bundles should follow

the regular Symfony conventions.

######### Example[¶](#example "Permalink to this headline")

Assuming you are creating the invoicing plugin as used above, this will result in the following set-up.

**1.** Name your repository: `vendor-name/sylius-invoice-plugin`.

**2.** Create bundle class in `src/VendorNameSyliusInvoicePlugin.php`:

```

<?php

declare(strict\_types=1);

namespace VendorName\SyliusInvoicePlugin;

use Sylius\Bundle\CoreBundle\Application\SyliusPluginTrait;

use Symfony\Component\HttpKernel\Bundle\Bundle;

final class VendorNameSyliusInvoicePlugin extends Bundle

{

    use SyliusPluginTrait;

}

```

**3.** Create extension class in `src/DependencyInjection/VendorNameSyliusInvoiceExtension.php`:

```

<?php

declare(strict\_types=1);

namespace VendorName\SyliusInvoicePlugin\DependencyInjection;

use Symfony\Component\Config\FileLocator;

use Symfony\Component\DependencyInjection\ContainerBuilder;

use Symfony\Component\DependencyInjection\Extension\Extension;

use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;

final class VendorNameSyliusInvoiceExtension extends Extension

{

    public function load(array $configs, ContainerBuilder $container): void

    {

        $config = $this->processConfiguration($this->getConfiguration([], $container), $configs);

        $loader = new XmlFileLoader($container, new FileLocator(\_\_DIR\_\_ . '/../Resources/config'));

    }

}

```

**4.** In `composer.json`, define the correct namespacing for the PSR-4 autoloader:

```

{

 "autoload": {

 "psr-4": {

 "VendorName\\SyliusInvoicePlugin\\": "src/"

 }

 },

 "autoload-dev": {

 "psr-4": {

 "Tests\\VendorName\\SyliusInvoicePlugin\\": "tests/"

 }

 }

}

```

####### 🏗️ Plugin Development Guide[¶](#plugin-development-guide "Permalink to this headline")

Sylius plugins are one of the most powerful ways to extend Sylius functionalities. They’re not bounded by Sylius release cycle and can be

developed quicker and more effectively. They also allow sharing our (developers) work in an open-source community, which is not possible with

regular application customizations.

BDD methodology says the most accurate way to explain some process is using an example.

With respect to that rule, let’s create some simple first plugin together!

######## Idea[¶](#idea "Permalink to this headline")

The most important thing is a concept. You should be aware, that not every customization should be made as a plugin for Sylius.

If you:

* share the common logic between multiple projects

* think provided feature could be useful for the whole Sylius community and want to share it for free or sell it

then you should definitely consider the creation of a plugin. On the other hand, if:

* your feature is specific for your project

* you don’t want to share your work in the community (maybe **yet**)

then don’t be afraid to make a regular Sylius customization.

Tip

For needs of this tutorial, we will implement a simple plugin, making it possible to mark a product variant **available on demand**.

######## How to start?[¶](#how-to-start "Permalink to this headline")

The first step is to create a new plugin using our `PluginSkeleton`.

```

composer create-project sylius/plugin-skeleton IronManSyliusProductOnDemandPlugin

```

Note

Remember about naming convention! Sylius plugin should start with your vendor name, followed by `Sylius` prefix and with `Plugin` suffix at the end.

Let’s say your vendor name is **IronMan**. Come on **IronMan**, let’s create your plugin!

######## Naming changes[¶](#naming-changes "Permalink to this headline")

`PluginSkeleton` provides some default classes and configurations. However, they must have some default values and names that should be changed

to reflect your plugin functionality. Basing on the vendor and plugin names established above, these are the changes that should be made:

* In `composer.json`:

> 

> 

> 	+ `sylius/plugin-skeleton` -> `iron-man/sylius-product-on-demand-plugin`

> 	+ `Acme example plugin for Sylius.` -> `Plugin allowing to mark product variants as available on demand in Sylius.` (or sth similar)

> 	+ `Acme\\SyliusExamplePlugin\\` -> `IronMan\\SyliusProductOnDemandPlugin\\` (the same changes should be done in namespaces in `src/` directory

> 	+ `Tests\\Acme\\SyliusExamplePlugin\\` -> `Tests\\IronMan\\SyliusProductOnDemandPlugin\\` (the same changes should be done in namespaces in `tests/` directory

>

* `AcmeSyliusExamplePlugin` should be renamed to `IronManSyliusProductOnDemandPlugin`

* `AcmeSyliusExampleExtension` should be renamed to `IronManSyliusProductOnDemandExtension`

* In `src/DependencyInjection/Configuration.php`:

> 

> 

> 	+ `acme\_sylius\_example\_plugin` -> `iron\_man\_sylius\_product\_on\_demand\_plugin`

>

* In `tests/Application/config/bundles.php`:

> 

> 

> 	+ `Acme\SyliusExamplePlugin\AcmeSyliusExamplePlugin::class` -> `IronMan\SyliusProductOnDemandPlugin\IronManSyliusProductOnDemandPlugin::class`

>

* In `phpspec.yml.dist` (if you want to use PHPSpec in your plugin):

> 

> 

> 	+ `Acme\SyliusExamplePlugin` -> `IronMan\SyliusProductOnDemandPlugin`

>

* Don’t forget to re-build up the list of files to autoload with `composer dump-autoload`

That’s it! All other files are just a boilerplate to show you what can be done in the Sylius plugin. They can be deleted with no harm:

* All files from `features/` directory

* `src/Controller/GreetingController.php`

* `config/admin\_routing.yml`

* `config/shop\_routing.yml`

* `public/greeting.js`

* `templates/dynamic\_greeting.html.twig`

* `templates/static\_greeting.html.twig`

* All files from `tests/Behat/Page/Shop/` (with corresponding services)

* `tests/Behat/Context/Ui/Shop/WelcomeContext.php` (with corresponding service)

You should also delete Behat suite named `greeting\_customer` from `tests/Behat/Resources/suites.yml`.

Important

You **don’t have to** remove all these files mentioned above. They can be adapted to suit your plugin functionality. However, as

they provide default, dummy features only for the presentation reasons, it’s just easier to delete them and implement new ones on

your own.

Important

After you have change name of plugin, please run in your main directory of plugin (cd MyPlugin/ && composer install).

If you don’t rerun this command you may have this error :

``bash

$ (cd tests/Application && bin/console assets:install public -e test)

PHP Fatal error: Uncaught Symfony\Component\Debug\Exception\ClassNotFoundException: Attempted to load class "Kernel" from namespace "Tests\FMDD\SyliusEmailOrderAdminPlugin\Application".

Did you forget a "use" statement for e.g. "Symfony\Component\HttpKernel\Kernel" or "Sylius\Bundle\CoreBundle\Application\Kernel"? in C:\Users\FMDD\Plugins\SyliusEmailOrderAdminPlugin\tests\Application\bin\console:36

Stack trace:

#0 {main}

thrown in C:\Users\FMDD\Plugins\SyliusEmailOrderAdminPlugin\tests\Application\bin\console on line 36

``

######## Specification[¶](#specification "Permalink to this headline")

We strongly encourage you to follow our BDD path in implementing Sylius plugins. In fact, proper tests are one of the requirements to

[have your plugin officially accepted](index.html#document-book/plugins/index).

Attention

Even though we’re big fans of our Behat and PHPSpec-based workflow, we do not enforce you to use the same libraries.

We strongly believe that properly tested code is the biggest value, but everyone should feel well with their own tests.

If you’re not familiar with PHPSpec, but know PHPUnit (or anything else) by heart - keep rocking with your favorite tool!

######### Scenario[¶](#scenario "Permalink to this headline")

Let’s start with describing how **marking a product variant available on demand** should work

```

@managing\_product\_variants

Feature: Marking a variant as available on demand

 In order to inform customer about possibility to order a product variant on demand

 As an Administrator

 I want to be able to mark product variant as available on demand

 Background:

 Given the store operates on a single channel in "United States"

 And the store has a "Iron Man Suite" configurable product

 And the product "Iron Man Suite" has a "Mark XLVI" variant priced at "$400000"

 And I am logged in as an administrator

 @ui

 Scenario: Marking product variant as available on demand

 When I want to modify the "Mark XLVI" product variant

 And I mark it as available on demand

 And I save my changes

 Then I should be notified that it has been successfully edited

 And this variant should be available on demand

```

What is really important, usually you don’t need to implement the whole Behat scenario on your own! In the example above only 2 steps

would need a custom implementation. Rest of them can be easily reused from **Sylius** Behat suite.

Important

If you’re not familiar with our BDD workflow with Behat, take a look at

[our BDD guide](index.html#document-bdd/index). All Behat configurations (contexts, pages, services, suites etc.) are explained

there in details.

######### Behavior implementation[¶](#behavior-implementation "Permalink to this headline")

```

<?php

declare(strict\_types=1);

namespace Tests\IronMan\SyliusProductOnDemandPlugin\Behat\Context\Ui\Admin;

use Behat\Behat\Context\Context;

use IronMan\SyliusProductOnDemandPlugin\Entity\ProductVariantInterface;

use Tests\IronMan\SyliusProductOnDemandPlugin\Behat\Page\Ui\Admin\ProductVariantUpdatePageInterface;

use Webmozart\Assert\Assert;

final class ManagingProductVariantsContext implements Context

{

    /\*\* @var ProductVariantUpdatePageInterface \*/

    private $productVariantUpdatePage;

    public function \_\_construct(ProductVariantUpdatePageInterface $productVariantUpdatePage)

    {

        $this->productVariantUpdatePage = $productVariantUpdatePage;

    }

    /\*\*

 \* @When I mark it as available on demand

 \*/

    public function markVariantAsAvailableOnDemand(): void

    {

        $this->productVariantUpdatePage->markAsAvailableOnDemand();

    }

    /\*\*

 \* @Then /^(this variant) should be available on demand$/

 \*/

    public function thisVariantShouldBeAvailableOnDemand(ProductVariantInterface $productVariant): void

    {

        $this->productVariantUpdatePage->open([

            'id' => $productVariant->getId(),

            'productId' => $productVariant->getProduct()->getId(),

        ]);

        Assert::true($this->productVariantUpdatePage->isAvailableOnDemand());

    }

}

```

First step is done - we have a failing test, that that is going to go green when we implement a desired functionality.

######## Implementation[¶](#implementation "Permalink to this headline")

The goal of our plugin is simple - we need to extend the `ProductVariant` entity and provide a new flag, that could be set

on the product variant form. Following customizations are done just like in the **Sylius Customization Guide**,

take a look at [customizing models](index.html#document-customization/model), [form](index.html#document-customization/form) and [template](index.html#document-customization/template).

Attention

`PluginSkeleton` is focused on delivering the most friendly and testable environment. That’s why in `tests/Application` directory,

there is a **tiny Sylius application** placed, with your plugin already used. Thanks to that, you can test your plugin with Behat scenarios

**within** Sylius application without installing it to any test app manually! There is, however, one important consequence of such an architecture.

**Everything** that should be done by a plugin user (configuration import, templates copying etc.) should also be done in `tests/Application`

to simulate the real developer behavior - and therefore make your new features testable.

######### Model[¶](#model "Permalink to this headline")

The only field we need to add is an additional `$availableOnDemand` boolean. To allow plugin’s user to use multiple plugins extending

the same entity, it’s recommended to provide a trait with new properties and methods, together with ORM mapping written in annotations

(if necessary). Providing an interface containing new methods is advisable.

```

<?php

// src/Model/ProductVariantTrait.php

declare(strict\_types=1);

namespace IronMan\SyliusProductOnDemandPlugin\Model;

trait ProductVariantTrait

{

    /\*\*

 \* @var bool

 \*

 \* @ORM\Column(type="boolean", name="available\_on\_demand")

 \*/

    private $availableOnDemand = false;

    public function setAvailableOnDemand(bool $availableOnDemand): void

    {

        $this->availableOnDemand = $availableOnDemand;

    }

    public function isAvailableOnDemand(): bool

    {

        return $this->availableOnDemand;

    }

}

```

```

<?php

// src/Model/ProductVariantInterface.php

declare(strict\_types=1);

namespace IronMan\SyliusProductOnDemandPlugin\Entity;

interface ProductVariantInterface

{

    public function setAvailableOnDemand(bool $availableOnDemand): void;

    public function isAvailableOnDemand(): bool;

}

```

Warning

Remember that if you modify or add some mapping, you should either provide a migration for the plugin user (that could be

copied to their migration folder) or mention the requirement of migration generation in the installation instructions.

######### Form[¶](#form "Permalink to this headline")

To make our new field available in Admin panel, a form extension is required:

```

<?php

// src/Form/Extension/ProductVariantTypeExtension.php

declare(strict\_types=1);

namespace IronMan\SyliusProductOnDemandPlugin\Form\Extension;

use Symfony\Component\Form\AbstractTypeExtension;

use Symfony\Component\Form\Extension\Core\Type\CheckboxType;

use Sylius\Bundle\ProductBundle\Form\Type\ProductVariantType;

use Symfony\Component\Form\FormBuilderInterface;

final class ProductVariantTypeExtension extends AbstractTypeExtension

{

    public function buildForm(FormBuilderInterface $builder, array $options): void

    {

        $builder->add('availableOnDemand', CheckboxType::class, [

            'label' => 'iron\_man\_sylius\_product\_on\_demand\_plugin.ui.available\_on\_demand',

        ]);

    }

    public function getExtendedType(): string

    {

        return ProductVariantType::class;

    }

}

```

Translation keys placed in `src/Resources/translations/messages.{locale}.yml` will be resolved automatically.

```

# src/Resources/translations/messages.en.yml

iron\_man\_sylius\_product\_on\_demand\_plugin:

 ui:

 available\_on\_demand: Available on demand

```

And in your `services.yml` file:

```

# src/Resources/config/services.yml

services:

 iron\_man\_sylius\_product\_on\_demand\_plugin.form.extension.type.product\_variant:

 class: IronMan\SyliusProductOnDemandPlugin\Form\Extension\ProductVariantTypeExtension

 tags:

 - { name: form.type\_extension, extended\_type: Sylius\Bundle\ProductBundle\Form\Type\ProductVariantType }

```

Again, you must remember about importing `src/Resources/config/services.yml` in `tests/Application/config/services.yaml`.

######### Template[¶](#template "Permalink to this headline")

The last step is extending the template of a product variant form. It can be done in three ways:

* by overwriting template

* by using sonata block events

* by writing a theme

For the needs of this tutorial, we will go the first way. What’s crucial, we need to determine which template should be overwritten.

Naming for twig files in Sylius, both in **ShopBundle** and **AdminBundle** are pretty clear and straightforward. In this specific case,

the template to override is `src/Sylius/Bundle/AdminBundle/Resources/views/ProductVariant/Tab/\_details.html.twig`. It should be copied

to `src/Resources/views/SyliusAdminBundle/ProductVariant/Tab/` directory, and additional field should be placed somewhere in the template.

```

{# src/Resources/views/SyliusAdminBundle/ProductVariant/Tab/\_details.html.twig #}

{#...#}

<div class="ui segment">

 <h4 class="ui dividing header">{{ 'sylius.ui.inventory'|trans }}</h4>

 {{ form\_row(form.onHand) }}

 {{ form\_row(form.tracked) }}

 {{ form\_row(form.version) }}

 {{ form\_row(form.availableOnDemand) }}

</div>

{#...#}

```

Warning

Beware! Implementing a new template on the plugin level is **not** everything! You must remember that this template should be

copied to `templates/bundles/SyliusAdminBundle/` directory (with whole catalogs structure, means `/ProductVariant/Tab`

in the application that uses your plugin - and therefore it should be mentioned in installation instruction.

The same thing should be done for your test application (you should have `tests/Application/templates/bundles/SyliusAdminBundle/` catalog

with this template copied).

Take a look at [customizing the templates](index.html#document-customization/template) section in the documentation,

for a better understanding of this topic.

######## Summary[¶](#summary "Permalink to this headline")

Congratulations! You’ve created your first, fully tested and documented, customization to Sylius inside a Sylius plugin!

As a result, you should see a new field in product variant form:

![_images/product_variant_available_on_demand.png](_images/product_variant_available_on_demand.png)

As you can see, there are some things to do at the beginning of development, but now, when you are already familiar with

the whole structure, each next feature can be provided faster than the previous ones.

######### What’s next?[¶](#what-s-next "Permalink to this headline")

Of course, it’s only the beginning. You could think about plenty of new features associated with this new product variant

field. What could be the next step?

* [customizing a product variant grid](index.html#document-customization/grid), to see new field on the index page

* customizing template of product details page, to show information to customer if product is not available, but can be ordered on demand

* allowing to order **not available yet, but available on demand** variants and therefore

customizing the whole [order processing](index.html#document-components_and_bundles/bundles/SyliusOrderBundle/processors) and [inventory operations](index.html#document-components_and_bundles/bundles/SyliusInventoryBundle/services)

and even more. The limit is only your imagination (and business value, of course!). For more inspiration, we strongly recommend

our [customizing guide](index.html#document-customization/index).

At the end, do not hesitate to contact us at [contact@sylius.com](mailto:contact%40sylius.com) when you manage to implement a new plugin.

We would be happy to check it out and add it to our [approved plugins list](https://sylius.com/plugins/)!

Note

Beware, that to have your plugin **officially** accepted, it needs to be created with respect to clean-code principles

and properly tested!

######### Future[¶](#future "Permalink to this headline")

We are working hard to make creating Sylius plugins even more developer- and user-friendly. Be in touch with the

[PluginSkeleton](https://github.com/Sylius/PluginSkeleton) notifications and other announcements from Sylius community.

Our plugins base is growing fast - why not be a part of it?

* [Idea](index.html#document-book/plugins/guide/idea)

* [How to start?](index.html#document-book/plugins/guide/installation)

* [Naming changes](index.html#document-book/plugins/guide/naming)

* [Specification](index.html#document-book/plugins/guide/specification)

* [Implementation](index.html#document-book/plugins/guide/implementation)

* [Summary](index.html#document-book/plugins/guide/summary)

See also

[Sylius Plugins](index.html#document-book/plugins/index)

The collection of Sylius Plugins and basic introduction to the concept of plugins.

[The Customization Guide](index.html#document-customization/index)

Sylius customization techniques available to use also in plugins.

####### Official Sylius Plugins[¶](#official-sylius-plugins "Permalink to this headline")

Sylius as an organization is providing some of its own plugins in the open source model. All the official plugins

developed by Sylius can be found in the [github organization](https://github.com/Sylius) (just like the main repository).

You can recognize a Sylius official plugin by this badge in its readme:

[![_images/official_plugin.png](_images/official_plugin.png)](_images/official_plugin.png)

  

The current list is as follows:

* [Shop API Plugin](https://github.com/Sylius/ShopApiPlugin)

* [Invoicing Plugin](https://github.com/Sylius/InvoicingPlugin)

* [Refund Plugin](https://github.com/Sylius/RefundPlugin)

* [Admin Order Creation Plugin](https://github.com/Sylius/AdminOrderCreationPlugin)

* [Customer Reorder Plugin](https://github.com/Sylius/CustomerReorderPlugin)

* [Customer Order Cancellation Plugin](https://github.com/Sylius/CustomerOrderCancellationPlugin)

####### Sylius Store[¶](#sylius-store "Permalink to this headline")

As the Sylius is an open-source project, it has an awesome community of users and developers.

Therefore our ecosystem flourishes with plugins created outside of our organization. These plugins can be listed in our

[Sylius Store](https://sylius.com/plugins/) or even become officially approved by us when they meet specific requirements.

######## How to have a Plugin listed on Sylius Store?[¶](#how-to-have-a-plugin-listed-on-sylius-store "Permalink to this headline")

Since Sylius is an open-source platform, there is a precise flow for the plugin to become officially adopted by the community.

**1.** Develop the plugin using [the official Plugin Development guide](index.html#document-book/plugins/creating-plugin).

**2.** Remember about the tests and code quality! Check out [Technical requirements](#book-plugins-technical-requirements) for more details.

Warning

Beware! Your plugin needs to have at least one tag (even if it’s 0.1). We’re not putting plugins in dev-master version

into the Sylius Store.

**3.** Send it to the project maintainers. The preferred way is to use [plugin request submit form](https://store.sylius.com/submit).

**4.** One of our Plugin Curators will contact you with the feedback regarding your plugin’s code quality, test suite,

and general feeling. They will also ask you to provide some changes in the code (if needed) to make this plugin visible in the Sylius Store.

**5.** Wait for your Plugin to be featured in [the list of plugins](https://sylius.com/plugins/) on the Sylius website.

######## Technical requirements[¶](#technical-requirements "Permalink to this headline")

Below you can find a list of requirements that your plugin needs to fulfill to pass the Sylius Core Team review. Try to follow

them and your plugin’s approval process will be faster and more efficient!

######### Must have[¶](#must-have "Permalink to this headline")

Every plugin must fulfill these requirements to be listed on the Sylius Store. We’re happy to accept new extensions to our platform,

but it’s also crucial for us to keep their high standards.

**Name of the plugin:**

* Does the name clearly say what kind of feature the plugin provides?

* Does the plugin name contain the vendor’s name?

* Are all configuration roots and all classes appropriately named?

* Generally, does the plugin fulfills [naming conventions](index.html#book-plugins-creating-plugin-naming-conventions)?

**Documentation:**

* Does the plugin contain a description of its features?

* Is there a description of the plugin installation?

* Does the documentation consist of any screenshots (if the plugin provides any visual content)?

**Installation:**

* Is it possible to install the plugin on a fresh Sylius-Standard application with no problems?

* Is every step needed for installation and configuration explained in the documentation? Are there any assumptions that could be confusing for less experienced developers?

**Coding standards:**

* Does the code apply at least [PSR-1](https://www.php-fig.org/psr/psr-1/)?

######### Should have[¶](#should-have "Permalink to this headline")

If you want your plugin to be officially approved by Sylius Core Team, there are more things to do. Only plugins with the

highest standards of code and properly developed test suite could get the “Approved by Sylius” mark, which makes them more

visible in the community and ensures users about their quality.

When Sylius approve a plugin, you can recognize it also by this badge below in its readme file:

[![_images/approved_plugin.png](_images/approved_plugin.png)](_images/approved_plugin.png)

  

**Coding standards:**

* Does the code follow SOLID principles?

* Are conventions in the code consistent with each other (are the same conventions used for the same concepts)?

* Does the code apply some other [PSR’s](https://www.php-fig.org/psr/)?

**Tests:**

* Are there any unit tests for the plugin’s classes? They can be written in PHPSpec, PHPUnit or any other working and reliable unit testing library

* Does the unit tests cover at least the most crucial classes in the plugin (those which contain important business logic)?

* Does the plugin include some functional/acceptance tests (written in Behat/PHPUnit or similar tool)?

* Are the core features of the plugin described and tests by them?

* Do the functional/acceptance tests describe most of the application business-related features?

**Continuous integration:**

* Is there any CI tool used in the plugin’s repository (CircleCI, Travis CI, Jenkins)?

* [How to create a plugin for Sylius?](index.html#document-book/plugins/creating-plugin)

* [🏗️ Plugin Development Guide](index.html#document-book/plugins/guide/index)

* [Official Sylius Plugins](index.html#document-book/plugins/official-plugins)

* [Sylius Store](index.html#document-book/plugins/sylius-store)

* [How to create a plugin for Sylius?](index.html#document-book/plugins/creating-plugin)

* [🏗️ Plugin Development Guide](index.html#document-book/plugins/guide/index)

* [Official Sylius Plugins](index.html#document-book/plugins/official-plugins)

* [Sylius Store](index.html#document-book/plugins/sylius-store)

#### Organization[¶](#organization "Permalink to this headline")

This chapter describes the rules and processes we use to organize our work.

##### Organization[¶](#organization "Permalink to this headline")

###### The Release Cycle[¶](#the-release-cycle "Permalink to this headline")

This document explains the **release cycle** of the Sylius project (i.e. the

code & documentation hosted on the main `Sylius/Sylius` [repository](https://github.com/Sylius/Sylius)).

Sylius follows the [Semantic Versioning](https://semver.org/) strategy:

* A new Sylius patch version (e.g. 1.0.1, 1.0.2, etc.) comes out usually *once a month*, depending on the number of bug fixes developed

* A new Sylius minor version (e.g. 1.1, 1.2, etc.) is released depending on various factors (see below), usually *once a few months*

New Sylius minor releases will drop unsupported PHP versions.

####### Scope-based vs time-based[¶](#scope-based-vs-time-based "Permalink to this headline")

Sylius **release cycle** is **not** strictly time-based (contrary to the [Symfony release cycle](https://symfony.com/releases)). Based on the experience

from over 10 minor versions, we decided that time is not the only reason on which we should rely when planning the new Sylius’

version. Therefore, each new minor release of Sylius takes in consideration:

* what we would like to include in it (features, improvements, fixes)

* when we would like to release it (based on the Team capacity, estimated amount of work and experience from previous minor releases development)

Note

The natural consequence of such a decision is uncertainty regarding the exact time of the next minor version release.

We try to estimate it as precisely as possible, but sometimes delays cannot be avoided. We believe that

releasing a **good** product is more important than releasing it **fast** 🤖

####### Development[¶](#development "Permalink to this headline")

The full development period for any minor version is divided into two phases:

* **Development**: *First 5/6 of the time intended for the release* to add new features and to enhance existing ones.

* **Stabilization**: *Last 1/6 of the time intended for the release* to fix bugs, prepare the release, and wait

for the whole Sylius ecosystem (third-party libraries, plugins, and projects using Sylius) to catch up.

During both periods, any new feature can be reverted if it won’t be

finished in time or won’t be stable enough to be included in the coming release.

####### Maintenance[¶](#maintenance "Permalink to this headline")

Each minor Sylius version is maintained for a fixed period of time after its release.

This maintenance is divided into:

* *Bug fixes and security fixes*: During this period, being *eight months* long,

all issues can be fixed. The end of this period is referenced as being the

*end of maintenance* of a release.

* *Security fixes only*: During this period, being *sixteen months* long,

only security related issues can be fixed. The end of this period is referenced

as being the *end of life* of a release.

####### Future release[¶](#future-release "Permalink to this headline")

| Version | Development starts | Stabilization starts | Release date |

| --- | --- | --- | --- |

| 1.13 | Oct 31, 2022 | ??? | ??? |

####### Supported versions[¶](#supported-versions "Permalink to this headline")

| Version | Release date | End of maintenance | End of life | Status |

| --- | --- | --- | --- | --- |

| 1.12 | Oct 31, 2022 | Sep 1, 2023 | Jun 1, 2024 | Fully supported |

| 1.11 | Feb 14, 2022 | Jan 31, 2023 | Oct 31, 2023 | Security support only |

####### Unsupported versions[¶](#unsupported-versions "Permalink to this headline")

| Version | Release date | End of maintenance | End of life |

| --- | --- | --- | --- |

| 1.10 | Jun 29, 2021 | May 14, 2022 | Jan 14, 2023 |

| 1.9 | Mar 1, 2021 | Nov 1, 2021 | Jul 1, 2022 |

| 1.8 | Sep 14, 2020 | May 14, 2021 | Jan 14, 2022 |

| 1.7 | Mar 2, 2020 | Nov 16, 2020 | Jul 16, 2021 |

| 1.6 | Aug 29, 2019 | Apr 29, 2020 | Dec 29, 2020 |

| 1.5 | May 10, 2019 | Jan 10, 2020 | Sep 10, 2020 |

| 1.4 | Feb 4, 2019 | Oct 4, 2019 | Jun 4, 2020 |

| 1.3 | Oct 1, 2018 | Jun 1, 2019 | Feb 1, 2020 |

| 1.2 | Jun 13, 2018 | Feb 13, 2019 | Oct 13, 2019 |

| 1.1 | Feb 12, 2018 | Oct 12, 2018 | Jun 12, 2019 |

| 1.0 | Sep 13, 2017 | May 13, 2018 | Jan 13, 2019 |

####### Backward Compatibility[¶](#backward-compatibility "Permalink to this headline")

All Sylius releases have to comply with our [Backward Compatibility Promise](index.html#document-book/organization/backward-compatibility-promise).

Whenever keeping backward compatibility is not possible, the feature, the

enhancement or the bug fix will be scheduled for the next major version.

###### Backward Compatibility Promise[¶](#backward-compatibility-promise "Permalink to this headline")

Sylius follows a versioning strategy called [Semantic Versioning](https://semver.org/). It means that

only major releases include BC breaks, whereas minor releases include new features

without breaking backwards compatibility.

Since Sylius is based on Symfony, our BC promise extends [Symfony’s Backward Compatibility Promise](https://symfony.com/doc/current/contributing/code/bc.html)

with a few new rules and exceptions stated in this document. We also follow [Symfony’s Experimental Features](https://symfony.com/doc/current/contributing/code/experimental.html)

process to be able to innovate safely.

####### Minor and patch releases[¶](#minor-and-patch-releases "Permalink to this headline")

Patch releases (such as 1.0.x, 1.1.x, etc.) do not require any additional work

apart from cleaning the Symfony cache.

Minor releases (such as 1.1.0, 1.2.0, etc.) require to run database migrations.

####### Code covered[¶](#code-covered "Permalink to this headline")

This BC promise applies to all of Sylius’ PHP code except for:

> 

> * code tagged with `@internal` or `@experimental` tags

> * event listeners

> * model and repository interfaces

> * PHPUnit tests (located at `tests/`, `src/\*\*/Tests/`)

> * PHPSpec tests (located at `src/\*\*/spec/`)

> * Behat tests (located at `src/Sylius/Behat/`)

> * final controllers (their service name is still covered with BC promise)

> 

> 

> 

####### Additional rules[¶](#additional-rules "Permalink to this headline")

######## Models & model interfaces[¶](#models-model-interfaces "Permalink to this headline")

In order to fulfill the constant Sylius’ need to evolve, model interfaces are excluded from this BC promise.

Methods may be added to the interface, but backwards compatibility is promised as long as your custom model

extends the one from Sylius, which is true for most cases.

######## Repositories & repository interfaces[¶](#repositories-repository-interfaces "Permalink to this headline")

Following the reasoning same as above and due to technological constraints, repository interfaces are also

excluded from this BC promise.

######## Event listeners[¶](#event-listeners "Permalink to this headline")

They are excluded from this BC promise, but they should be as simple as possible and always call another service.

Behaviour they’re providing (the end result) is still included in BC promise.

######## Final controllers[¶](#final-controllers "Permalink to this headline")

It is allowed to change their dependencies, but the behaviour they’re providing is still included in BC promise.

The service name and class name will not change.

######## Routing[¶](#routing "Permalink to this headline")

The currently present routes cannot have their name changed, but optional parameters might be added to them.

All the new routes will start with `sylius\_` prefix in order to avoid conflicts.

######## Services[¶](#services "Permalink to this headline")

Services names cannot change, but new services might be added with `sylius.` or `Sylius\\` prefix.

######## Templates[¶](#templates "Permalink to this headline")

Neither template events, block or templates themselves cannot be deleted or renamed.

####### Deprecations[¶](#deprecations "Permalink to this headline")

Before we remove or replace code covered by this backwards compatibility promise, it is

first deprecated in the next minor release before being removed in the next major release.

A code is marked as deprecated by adding a `@deprecated` PHPDoc to

relevant classes, methods, properties:

```

/\*\*

 \* @deprecated Deprecated since version 1.X. Use XXX instead.

 \*/

```

The deprecation message should indicate the version in which the class/method was

deprecated and how the feature was replaced (whenever possible).

A PHP deprecation must also be triggered to help people with

the migration, for instance:

```

trigger\_deprecation(

    'sylius/some-package', // package name

    '1.x', // package version

    'A is deprecated and will be removed in Sylius 2.0. Use B instead.', // message

);

```

You should not use the @trigger\_error() function.

###### Sylius Team[¶](#sylius-team "Permalink to this headline")

The following people are creating Sylius Core Team:

* **Łukasz Chruściel** ([lchrusciel](https://github.com/lchrusciel/))

* **Grzegorz Sadowski** ([GSadee](https://github.com/GSadee/))

* **Mateusz Zalewski** ([Zales0123](https://github.com/Zales0123/))

* **Loïc Frémont** ([loic425](https://github.com/loic425/))

* **Victor Vasiloi** ([vvasiloi](https://github.com/vvasiloi/))

* **Stephane Decock** ([Roshyo](https://github.com/Roshyo/))

Theirs accountabilities are:

* Reviews and merges pull requests

* Triages and reacts to issues

In addition, they are supported by:

####### Product Owner[¶](#product-owner "Permalink to this headline")

* Builds and refines the Product Backlog and Roadmap

* Gathers feedback from all stakeholders

This role is currently held by:

* **Magdalena Sadowska** ([CoderMaggie](https://github.com/CoderMaggie/))

####### Vision[¶](#vision "Permalink to this headline")

* Creates and iterates over the long term Sylius Vision

This role is currently held by:

* **Nicolas Kroll** ([bitbager](https://github.com/bitbager/))

####### Core Release Manager[¶](#core-release-manager "Permalink to this headline")

* Defines and publishes [the Release Cycle](index.html#document-book/organization/release-cycle) and [Backward Compatibility Promise](index.html#document-book/organization/backward-compatibility-promise)

* Coaches contributors and Core Team on the backwards-compatibility impact of code changes

This role is currently held by:

* **Mateusz Zalewski** ([Zales0123](https://github.com/Zales0123/))

####### Plugins Manager[¶](#plugins-manager "Permalink to this headline")

* Triages and reviews incoming issues and pull requests on supported plugins

* Schedules and releases new versions of the supported plugins

This role is currently held by:

* **Grzegorz Sadowski** ([GSadee](https://github.com/GSadee/))

####### Architect[¶](#architect "Permalink to this headline")

* Makes key architectural decisions and documenting them in form of ADRs

* Research and prototyping of possible architectures and technical solutions upon request, laying out foundations if necessary, proactively seeking input and feedback from our Open Source community and customers

* Defines target versions of key dependencies and drives their upgrade initiatives

This role is currently held by:

* **Łukasz Chruściel** ([lchrusciel](https://github.com/lchrusciel/))

* **Mateusz Zalewski** ([Zales0123](https://github.com/Zales0123/))

####### API Designer[¶](#api-designer "Permalink to this headline")

* Reviews the PRs related to API

* Minimizes the feature parity gap between regular and headless Sylius implementations but also old APIs and the new Unified API

* Defines API contracts (request & response schema and conventions)

* Maintains the ShopAPIPlugin and AdminApiBundle

* Provides actionable instructions for migration to the new API

This role is currently held by:

* **Łukasz Chruściel** ([lchrusciel](https://github.com/lchrusciel/))

####### Documentation Editor-in-chief[¶](#documentation-editor-in-chief "Permalink to this headline")

* Evolves the high-level structure of our documentation

* Reviews and merges PR’s to the documentation

* Maintains the documentation issues

* Takes care of the READMEs in all Sylius repositories

This role is currently held by:

* **Magdalena Sadowska** ([CoderMaggie](https://github.com/CoderMaggie/))

####### Sylius Technical Libraries[¶](#sylius-technical-libraries "Permalink to this headline")

* Developing and maintaining Sylius Technical Libraries

* Developing and maintaining [SyliusLabs](https://github.com/SyliusLabs/) organization repositories

This role is currently held by:

* **Mateusz Zalewski** ([Zales0123](https://github.com/Zales0123/))

* [The Release Cycle](index.html#document-book/organization/release-cycle)

* [Backward Compatibility Promise](index.html#document-book/organization/backward-compatibility-promise)

* [Sylius Team](index.html#document-book/organization/team)

* [The Release Cycle](index.html#document-book/organization/release-cycle)

* [Backward Compatibility Promise](index.html#document-book/organization/backward-compatibility-promise)

* [Sylius Team](index.html#document-book/organization/team)

#### Support[¶](#support "Permalink to this headline")

How to get support for Sylius?

##### Support[¶](#support "Permalink to this headline")

Besides documentation we have a very friendly community which provides support for all Sylius users seeking help!

At Sylius we have 3 main channels for communication and support.

###### Slack[¶](#slack "Permalink to this headline")

Most of us use Slack for their online communication with co-workers. We know that it is not convenient to have another chat window open,

therefore we’ve chosen Slack for the live communication with the community. Slack is supposed to handle the most urgent questions

in the fastest way possible.

The most important rooms for newcomers are:

#general - for discussions about Sylius development itself,

#docs - for discussions related to the documentation,

#support - for asking questions and helping others,

#random - for the non-work banter and water cooler conversation.

But there are many more specific channels also.

If you have any suggestions regarding its organization, please let us know on Slack!

Slack requires inviting new members, but this can be done automatically, just go to [sylius.com/slack](https://sylius.com/slack),

enter your email and you will get an invitation.

If possible, please use your GitHub username - it will help us to recognize each other easily!

###### Forum[¶](#forum "Permalink to this headline")

In response to the rapid growth of our Ecosystem, we decided it is necessary to launch a brand new platform for support

and exchanging experience. On the contrary to Slack, our Forum is much easier to follow, on Slack unless you are a part

of the discussion when it is happening, it might be difficult to catch up when you have been offline.

Forum has a search engine so it is convenient to browse for what is interesting for you.

The Sylius Community Forum is meant for everyone who is interested in eCommerce technology and Sylius. We invite both existing

and new community members to join the discussion and help shape the ecosystem, products & services we are building.

Get to know other community members, ask for support, suggest an improvement or discuss your challenge.

You can register via email, Twitter & GitHub by going to <https://forum.sylius.com/> and hitting the “Sign-Up” button.

###### StackOverflow[¶](#stackoverflow "Permalink to this headline")

We also encourage asking Sylius related questions on the [stackoverflow.com](http://stackoverflow.com) platform.

* Search for the question before asking, maybe someone has already solved your problem,

* Be specific about your question, this is what SO is about, concrete questions and solutions,

* Be sure to tag them with **sylius** tag - it will make it easier to find for people who can answer it.

* Try also tagging your questions with the **symfony** tag - the Symfony community is very big and might be really helpful with

Sylius related questions, as we are basing on Symfony.

To view all Sylius related questions - visit [this link](http://stackoverflow.com/questions/tagged/sylius).

You can also [search for phrase](http://stackoverflow.com/search?tab=newest&q=sylius).

* [Support](index.html#document-book/support/index)

#### Contributing[¶](#contributing "Permalink to this headline")

Guides you how to contribute to Sylius.

##### Contributing[¶](#contributing "Permalink to this headline")

Note

This section is based on the great [Symfony documentation](https://symfony.com/doc/current).

###### Install to Contribute[¶](#install-to-contribute "Permalink to this headline")

Before you can start contributing to Sylius code or documentation, you should install Sylius locally.

To install Sylius main application from our main repository and contribute, run the following command:

```

composer create-project sylius/sylius

```

This will create a new Sylius project in `sylius` directory. When all the dependencies are installed,

you should create .env file basing on provides .env.dist files. The most important parameter that need to be set,

is DATABASE\_URL.

```

DATABASE_URL=mysql://username:password@host/database_name_%kernel.environment%

```

After everything is in place, run the following commands:

```

cd sylius # Move to the newly created directory

php bin/console sylius:install

```

The `sylius:install` command actually runs several other commands, which will ask you some questions and check if everything is setup to run Sylius properly.

This package contains our main Sylius development repository, with all the components and bundles in the `src/` folder.

In order to see a fully functional frontend you will need to install its assets.

**Sylius** already has a `gulpfile.babel.js`, therefore you just need to get [Gulp](http://gulpjs.com/) using [Node.js](https://nodejs.org/en/download/).

Having Node.js installed go to your project directory and run:

```

yarn install

```

And now you can use gulp for installing views, by just running a simple command:

```

yarn build

```

For the contributing process questions, please refer to the [Contributing Guide](https://docs.sylius.com/en/latest/contributing/index.html) that comes up in the following chapters:

####### Contributing Code[¶](#contributing-code "Permalink to this headline")

######## Submitting a Patch[¶](#submitting-a-patch "Permalink to this headline")

Patches are the best way to provide a bug fix or to propose enhancements to

Sylius.

######### Step 1: Setup your Environment[¶](#step-1-setup-your-environment "Permalink to this headline")

########## Install the Software Stack[¶](#install-the-software-stack "Permalink to this headline")

Before working on Sylius, set a Symfony friendly environment up with the following

software:

* Git

* PHP version 8.0 or above

* MySQL

########## Configure Git[¶](#configure-git "Permalink to this headline")

Set your user information up with your real name and a working email address:

```

git config --global user.name "Your Name"

git config --global user.email "you@example.com"

```

Tip

If you are new to Git, you are highly recommended to read the excellent and

free [ProGit](http://git-scm.com/book) book.

Tip

If your IDE creates configuration files inside the directory of the project,

you can use global `.gitignore` file (for all projects) or

`.git/info/exclude` file (per project) to ignore them. See

[Github’s documentation](https://help.github.com/articles/ignoring-files).

Tip

Windows users: when installing Git, the installer will ask what to do with

line endings, and will suggest replacing all LF with CRLF. This is the wrong

setting if you wish to contribute to Sylius. Selecting the as-is method is

your best choice, as Git will convert your line feeds to the ones in the

repository. If you have already installed Git, you can check the value of

this setting by typing:

```

git config core.autocrlf

```

This will return either “false”, “input” or “true”; “true” and “false” being

the wrong values. Change it to “input” by typing:

```

git config --global core.autocrlf input

```

Replace –global by –local if you want to set it only for the active

repository

########## Get the Sylius Source Code[¶](#get-the-sylius-source-code "Permalink to this headline")

Get the Sylius source code:

* Create a [GitHub](https://github.com/signup/free) account and sign in;

* Fork the [Sylius repository](https://github.com/Sylius/Sylius) (click on the “Fork” button);

* After the “forking action” has completed, clone your fork locally

(this will create a `Sylius` directory):

```

git clone git@github.com:USERNAME/Sylius.git

```

* Add the upstream repository as a remote:

```

cd sylius

git remote add upstream git://github.com/Sylius/Sylius.git

```

######### Step 2: Work on your Patch[¶](#step-2-work-on-your-patch "Permalink to this headline")

########## The License[¶](#the-license "Permalink to this headline")

Before you start, you must know that all patches you are going to submit

must be released under the *MIT license*, unless explicitly specified in your

commits.

########## Choose the right Base Branch[¶](#choose-the-right-base-branch "Permalink to this headline")

Before starting to work on a patch, you must determine on which branch you need to work. It will be:

* `1.12`, if you are fixing a bug for an existing feature or want to make a change that falls into the list of acceptable changes in patch versions

* `1.13`, if you are adding a new feature.

Note

All bug fixes merged into the `1.12` maintenance branch are also merged into `1.13` on a regular basis.

########## Create a Topic Branch[¶](#create-a-topic-branch "Permalink to this headline")

Each time you want to work on a patch for a bug or on an enhancement, create a

topic branch, starting from the previously chosen base branch:

```

git switch upstream/1.13 -c BRANCH_NAME

```

Tip

Use a descriptive name for your branch (`issue\_XXX` where `XXX` is the

GitHub issue number is a good convention for bug fixes).

The above checkout command automatically switches the code to the newly created

branch (check the branch you are working on with `git branch`).

########## Work on your Patch[¶](#work-on-your-patch "Permalink to this headline")

Work on the code as much as you want and commit as much as you want; but keep

in mind the following:

* Practice [BDD](index.html#document-bdd/index), which is the development methodology we use at Sylius;

* Follow [coding standards](index.html#document-book/contributing/code/standards) (use `git diff --check` to check for

trailing spaces – also read the tip below);

* Do atomic and logically separate commits (use the power of `git rebase` to

have a clean and logical history);

* Squash irrelevant commits that are just about fixing coding standards or

fixing typos in your own code;

* Never fix coding standards in some existing code as it makes the code review

more difficult (submit CS fixes as a separate patch);

* In addition to this “code” pull request, you must also update the documentation when appropriate.

See more in [contributing documentation](index.html#document-book/contributing/documentation/overview) section.

* Write good commit messages (see the tip below).

Tip

A good commit message is composed of a summary (the first line),

optionally followed by a blank line and a more detailed description. The

summary should start with the Component you are working on in square

brackets (`[Cart]`, `[Taxation]`, …). Use a

verb (`fixed ...`, `added ...`, …) to start the summary and **don’t

add a period at the end**.

########## Prepare your Patch for Submission[¶](#prepare-your-patch-for-submission "Permalink to this headline")

When your patch is not about a bug fix (when you add a new feature or change

an existing one for instance), it must also include the following:

* An explanation of the changes in the relevant `CHANGELOG` file(s) (the

`[BC BREAK]` or the `[DEPRECATION]` prefix must be used when relevant);

* An explanation on how to upgrade an existing application in the relevant

`UPGRADE` file(s) if the changes break backward compatibility or if you

deprecate something that will ultimately break backward compatibility.

######### Step 3: Submit your Patch[¶](#step-3-submit-your-patch "Permalink to this headline")

Whenever you feel that your patch is ready for submission, follow the

following steps.

########## Rebase your Patch[¶](#rebase-your-patch "Permalink to this headline")

Before submitting your patch, update your branch (needed if it takes you a

while to finish your changes):

If you are basing on the `1.13` branch:

```

git checkout BRANCH_NAME # to make sure you're on the right branch

git rebase upstream/1.13

```

If you are basing on the `1.12` branch:

```

git checkout BRANCH_NAME # to make sure you're on the right branch

git rebase upstream/1.12

```

When doing the `rebase` command, you might have to fix merge conflicts.

`git status` will show you the *unmerged* files. Resolve all the conflicts,

then continue the rebase:

```

git add ... # add resolved files

git rebase --continue

```

Push your branch remotely:

```

git push --force-with-lease origin BRANCH_NAME

```

########## Make a Pull Request[¶](#make-a-pull-request "Permalink to this headline")

Warning

Please remember that bug fixes must be submitted against the `1.12` branch,

but features and deprecations against the `1.13` branch. Just accordingly to which branch you chose as the base branch before.

You can now make a pull request on the `Sylius/Sylius` GitHub repository.

To ease the core team work, always include the modified components in your

pull request message, like in:

```

[Cart] Fixed something

[Taxation] [Addressing] Added something

```

The pull request description must include the following checklist at the top

to ensure that contributions may be reviewed without needless feedback

loops and that your contributions can be included into Sylius as quickly as

possible:

```

| Q               | A

| --------------- | -----

| Branch?         | 1.12 or 1.13

| Bug fix?        | no/yes

| New feature?    | no/yes

| BC breaks?      | no/yes

| Deprecations?   | no/yes

| Related tickets | fixes #X, partially #Y, mentioned in #Z

| License         | MIT

```

An example submission could now look as follows:

```

| Q               | A

| --------------- | -----

| Branch?         | 1.12

| Bug fix?        | yes

| New feature?    | no

| BC breaks?      | no

| Deprecations?   | no

| Related tickets | fixes #12

| License         | MIT

```

The whole table must be included (do **not** remove lines that you think are

not relevant).

Some answers to the questions trigger some more requirements:

> 

> * If you answer yes to “Bug fix?”, check if the bug is already listed in the

> Sylius issues and reference it/them in “Related tickets”;

> * If you answer yes to “New feature?”, you should submit a pull request to the

> documentation;

> * If you answer yes to “BC breaks?”, the patch must contain updates to the

> relevant `CHANGELOG` and `UPGRADE` files;

> * If you answer yes to “Deprecations?”, the patch must contain updates to the

> relevant `CHANGELOG` and `UPGRADE` files;

> 

> 

> 

If some of the previous requirements are not met, create a todo-list and add

relevant items:

```

- [ ] Fix the specs as they have not been updated yet

- [ ] Submit changes to the documentation

- [ ] Document the BC breaks

```

If the code is not finished yet because you don’t have time to finish it or

because you want early feedback on your work, add an item to todo-list:

```

- [ ] Finish the feature

- [ ] Gather feedback for my changes

```

As long as you have items in the todo-list, please prefix the pull request

title with “[WIP]”.

In the pull request description, give as much details as possible about your

changes (don’t hesitate to give code examples to illustrate your points). If

your pull request is about adding a new feature or modifying an existing one,

explain the rationale for the changes. The pull request description helps the

code review.

########## Rework your Patch[¶](#rework-your-patch "Permalink to this headline")

Based on the feedback on the pull request, you might need to rework your

patch. Before re-submitting the patch, rebase with your base branch (`1.13` or `1.12`), don’t merge; and force the push to the origin:

```

git rebase -f upstream/1.13

git push --force-with-lease origin BRANCH_NAME

```

or

```

git rebase -f upstream/1.12

git push --force-with-lease origin BRANCH_NAME

```

Note

When doing a `push --force-with-lease`, always specify the branch name explicitly

to avoid messing other branches in the repo (`--force-with-lease` tells Git that

you really want to mess with things so do it carefully).

Often, Sylius team members will ask you to “squash” your commits. This means you will

convert many commits to one commit. To do this, use the rebase command:

```

git rebase -i upstream/1.13

git push --force-with-lease origin BRANCH_NAME

```

or

```

git rebase -i upstream/1.12

git push --force-with-lease origin BRANCH_NAME

```

After you type this command, an editor will popup showing a list of commits:

```

pick 1a31be6 first commit

pick 7fc64b4 second commit

pick 7d33018 third commit

```

To squash all commits into the first one, remove the word `pick` before the

second and the last commits, and replace it by the word `squash` or just

`s`. When you save, Git will start rebasing, and if successful, will ask

you to edit the commit message, which by default is a listing of the commit

messages of all the commits. When you are finished, execute the push command.

######## Security Issues[¶](#security-issues "Permalink to this headline")

If you think that you have found a security issue in Sylius, don’t use the bug tracker and do not post it publicly.

Instead, all security issues must be sent to **security@sylius.com**.

Emails sent to this address are forwarded to the Sylius Core Team Members responsible for the framework’s security.

Please do not reveal the issue publicly until the security fix is released and announced by Sylius.

Note

We will not publish security releases during Saturday or Sunday,

except for the vulnerabilities made public.

######## Coding Standards[¶](#coding-standards "Permalink to this headline")

You can check your code for Sylius coding standard by running the following command:

```

vendor/bin/ecs check src tests

```

Some of the violations can be automatically fixed by running the same command with `--fix` suffix like:

```

vendor/bin/ecs check src tests --fix

```

Note

Most of Sylius coding standard checks are extracted to [SyliusLabs/CodingStandard](https://github.com/SyliusLabs/CodingStandard) package so that

reusing them in your own projects or Sylius plugins is effortless. Too learn about details, take a look

at its README.

######## Conventions[¶](#conventions "Permalink to this headline")

This document describes conventions used in the Sylius to make it more consistent and predictable. Any new code

delivered to Sylius should comply with the following conventions, we also encourage you to use them in your own projects

based on Sylius.

######### Naming[¶](#naming "Permalink to this headline")

* Use `camelCase` for:

> 

> 

> 	+ PHP variables,

> 	+ method names (except PHPSpec and PHPUnit),

> 	+ arguments,

> 	+ Twig templates;

>

* Use `snake\_case` for:

> 

> 

> 	+ configuration parameters,

> 	+ Twig template variables,

> 	+ fixture fields,

> 	+ YAML files,

> 	+ XML files,

> 	+ Behat feature files,

> 	+ PHPSpec method names,

> 	+ PHPUnit method names;

>

* Use `SCREAMING\_SNAKE\_CASE` for:

> 

> 

> 	+ constants,

> 	+ environment variables;

>

* Use `UpperCamelCase` for:

> 

> 

> 	+ classes names,

> 	+ interfaces names,

> 	+ traits names,

> 	+ other PHP files names;

>

* Prefix abstract classes with Abstract,

* Suffix interfaces with Interface,

* Suffix traits with Trait,

* Use a command name with the Handler suffix to create a name of command handler,

* Suffix exceptions with Exception,

* Suffix PHPSpec classes with Spec,

* Suffix PHPUnit tests with Test,

* Prefix Twig templates that are partial blocks with \_,

* Use fully qualified class name (FQCN) of an interface as a service name of newly created service or FQCN of class

if there are multiple implementations of a given interface unless it is inconsistent with the current scope of Sylius.

######### Template Events[¶](#template-events "Permalink to this headline")

* Priorities of blocks should be increased by a step of 10.

* A block between two existing blocks should use the priority that is in the middle point between the two blocks. For

example if you want to add a block between two blocks with priorities 10 and 20, you should use 15 as a priority.

* If you want to add a block at the beginning of a template, you should use a priority of 5.

######## Sylius License[¶](#sylius-license "Permalink to this headline")

Sylius is released under the MIT license.

According to [Wikipedia](http://en.wikipedia.org/wiki/MIT_License):

> 

> “It is a permissive license, meaning that it permits reuse within

> proprietary software on the condition that the license is distributed with

> that software. The license is also GPL-compatible, meaning that the GPL

> permits combination and redistribution with software that uses the MIT

> License.”

######### The License[¶](#the-license "Permalink to this headline")

Copyright (c) 2011-2023 Sylius Sp. z o.o.

Permission is hereby granted, free of charge, to any person obtaining a copy

of this software and associated documentation files (the “Software”), to deal

in the Software without restriction, including without limitation the rights

to use, copy, modify, merge, publish, distribute, sublicense, and/or sell

copies of the Software, and to permit persons to whom the Software is furnished

to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all

copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR

IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,

FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE

AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER

LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,

OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN

THE SOFTWARE.

* [Submitting a Patch](index.html#document-book/contributing/code/patches)

* [Security Issues](index.html#document-book/contributing/code/security)

* [Coding Standards](index.html#document-book/contributing/code/standards)

* [Conventions](index.html#document-book/contributing/code/conventions)

* [Sylius License](index.html#document-book/contributing/code/license)

####### Contributing Documentation[¶](#contributing-documentation "Permalink to this headline")

######## Contributing to the Documentation[¶](#contributing-to-the-documentation "Permalink to this headline")

The documentation is as important as the code. It follows the exact same principles:

DRY, tests, ease of maintenance, extensibility, optimization, and refactoring

just to name a few. And of course, documentation has bugs, typos, hard to read

tutorials, and many more.

######### Contributing[¶](#contributing "Permalink to this headline")

Before contributing, you need to become familiar with the [markup

language](index.html#document-book/contributing/documentation/format) used by the documentation.

The Sylius documentation is hosted on GitHub, in the main repository:

```

https://github.com/Sylius/Sylius

```

If you want to submit a patch, [fork](https://help.github.com/articles/fork-a-repo) the official repository on GitHub and

then clone your fork to you local destination:

```

git clone git@github.com:YOUR_USERNAME/Sylius.git

```

Under the name `origin` you will have from now on the access to your fork.

Add also the main repository as the `upstream` remote.

```

git remote add upstream git@github.com:Sylius/Sylius.git

```

########## Choose the right Base Branch[¶](#choose-the-right-base-branch "Permalink to this headline")

Before starting to work on a patch, you must determine on which branch you need to work. It will be:

* `1.x`, if you are fixing or adding docs for features that exist in one of those versions,

* `master`, if you are documenting a new feature, that was not in `1.x`

Note

All bug fixes merged into the `1.x` maintenance branches are also merged into `master` on a regular basis.

Create a dedicated branch for your changes (for organization):

```

git checkout -b docs/improving_foo_and_bar

```

You can now make your changes directly to this branch and commit them.

Remember to name your commits descriptively, keep them possibly small, with just unitary changes (such that change something only in one part of the docs, not everywhere).

When you’re done, push this branch to *your* GitHub fork and initiate a pull request.

Your pull request will be reviewed, you will be asked to apply fixes if necessary and then it will be merged into the main repository.

########## Testing Documentation[¶](#testing-documentation "Permalink to this headline")

To test the documentation before a commit you need to install Sphinx and needed dependencies.

Tip

Official Sphinx installation guide : [www.sphinx-doc.org](https://www.sphinx-doc.org/en/master/usage/installation.html)

Note

If you are using docker - you can try building your documentation with: [Sylius Documentation Builder](https://github.com/arti0090/SyliusDocumentationBuilder)

Our recommendation is to install `Sphinx` via [Pip](https://pip.pypa.io/en/stable/installing/).

We suggest to install & use `Sphinx v1.8.5`

```

pip3 install -Iv sphinx==1.8.5

```

```

pip3 install --no-cache-dir -r ./docs/requirements.txt

```

Then run

```

sphinx-build -b html ./docs ./docs/build

```

and view the generated HTML files in the `docs/build` directory. You can open them in your browser and check how they look!

Warning

If you have problems with using `Sphinx`, please make sure that you’re using Python 3.

Using `pip`, try to uninstall old dependencies and install latest version Python and Sphinx.

```

pip uninstall sphinx

pip3 uninstall sphinx

```

If you have installed old sphinx by your operating system tools like: brew, apt-get or yum, you have to uninstall it too.

########## Creating a Pull Request[¶](#creating-a-pull-request "Permalink to this headline")

Following the example, the pull request will be from your

`improving\_foo\_and\_bar` branch to the `Sylius` `master` branch by default.

GitHub covers the topic of [pull requests](https://help.github.com/articles/using-pull-requests) in detail.

Note

The Sylius documentation is licensed under a Creative Commons

Attribution-Share Alike 3.0 Unported [License](index.html#document-book/contributing/documentation/license).

Warning

You should always prefix the PR name with a `[Documentation]` tag!

You can prefix the title of your pull request in a few cases:

* `[WIP]` (Work in Progress) is used when you are not yet finished with your

pull request, but you would like it to be reviewed. The pull request won’t

be merged until you say it is ready.

* `[ComponentName]` if you are contributing docs that regard on of [the Sylius Components](index.html#document-components_and_bundles/components/general/index).

* `[BundleName]` when you add documentation of [the Sylius Bundles](index.html#document-components_and_bundles/bundles/index).

* `[Behat]` if you modify something in the [the BDD guide](index.html#document-bdd/index).

* `[API]` when you are contributing docs to [the API guide](index.html#document-book/api/index).

For instance if your pull request is about documentation of some feature of the Resource bundle, but it is still a work in progress

it should look like : `[WIP][Documentation][ResourceBundle] Arbitrary feature documentation`.

######### Documenting new Features or Behavior Changes[¶](#documenting-new-features-or-behavior-changes "Permalink to this headline")

If you’re documenting a brand new feature or a change that’s been made in

Sylius, you should precede your description of the change with a `.. versionadded:: 1.X`

tag and a short description:

```

.. versionadded:: 1.3

    The ``getProductDiscount`` method was introduced in Sylius 1.3.

```

######### Standards[¶](#standards "Permalink to this headline")

All documentation in the Sylius Documentation should follow

[the documentation standards](index.html#document-book/contributing/documentation/standards).

######### Reporting an Issue[¶](#reporting-an-issue "Permalink to this headline")

The easiest contributions you can make is reporting issues: a typo, a grammar

mistake, a bug in a code example, a missing explanation, and so on.

Steps:

* Submit a new issue in the [GitHub tracker](https://github.com/Sylius/Sylius/issues/new);

* *(optional)* Submit a patch.

######## Documentation Format[¶](#documentation-format "Permalink to this headline")

The Sylius documentation uses the [reStructuredText](#restructuredtext) as its markup language and

[Sphinx](#sphinx) for building the output (HTML, PDF, …).

######### reStructuredText[¶](#restructuredtext "Permalink to this headline")

reStructuredText “is an easy-to-read, what-you-see-is-what-you-get plaintext

markup syntax and parser system”.

You can learn more about its syntax by reading existing Sylius [documents](https://github.com/Sylius/Sylius/tree/master/docs)

or by reading the [reStructuredText Primer](http://www.sphinx-doc.org/en/stable/rest.html) on the Sphinx website.

If you are familiar with Markdown, be careful as things are sometimes very

similar but different:

* Lists starts at the beginning of a line (no indentation is allowed);

* Inline code blocks use double-ticks (```like this```).

######### Sphinx[¶](#sphinx "Permalink to this headline")

Sphinx is a build system that adds some nice tools to create documentation

from the reStructuredText documents. As such, it adds new directives and

interpreted text roles to standard reST [markup](http://www.sphinx-doc.org/en/stable/markup/).

########## Syntax Highlighting[¶](#syntax-highlighting "Permalink to this headline")

All code examples uses PHP as the default highlighted language. You can change

it with the `code-block` directive:

```

.. code-block:: yaml

    { foo: bar, bar: { foo: bar, bar: baz } }

```

If your PHP code begins with `<?php`, then you need to use `html+php` as

the highlighted pseudo-language:

```

.. code-block:: html+php

    <?php echo $this->foobar(); ?>

```

Note

A list of supported languages is available on the [Pygments website](http://pygments.org/languages/).

The current list of supported formats are the following:

| Markup format | Displayed |

| --- | --- |

| html | HTML |

| xml | XML |

| php | PHP |

| yaml | YAML |

| json | JSON |

| jinja | Twig |

| html+jinja | Twig |

| html+php | PHP |

| ini | INI |

| php-annotations | Annotations |

########## Adding Links[¶](#adding-links "Permalink to this headline")

To add links to other pages in the documents use the following syntax:

```

:doc:`/path/to/page`

```

Using the path and filename of the page without the extension, for example:

```

:doc:`/book/architecture`

:doc:`/components\_and\_bundles/bundles/SyliusAddressingBundle/installation`

```

The link’s text will be the main heading of the document linked to. You can

also specify an alternative text for the link:

```

:doc:`Simple CRUD </components\_and\_bundles/bundles/SyliusResourceBundle/installation>`

```

You can also link to pages outside of the documentation, for instance to the [Github](http://www.github.com).

```

`Github`\_ //it is an intext link.

```

At the bottom of the document in which you are using your link add a reference:

```

.. \_`Github`: http://www.github.com // with a url to your desired destination.

```

######## Documentation Standards[¶](#documentation-standards "Permalink to this headline")

In order to help the reader as much as possible and to create code examples that

look and feel familiar, you should follow these standards.

######### Sphinx[¶](#sphinx "Permalink to this headline")

* The following characters are chosen for different heading levels: level 1

is `=`, level 2 `-`, level 3 `~`, level 4 `.` and level 5 `"`;

* Each line should break approximately after the first word that crosses the

72nd character (so most lines end up being 72-78 characters);

* The `::` shorthand is *preferred* over `.. code-block:: php` to begin a PHP

code block (read [the Sphinx documentation](http://sphinx-doc.org/rest.html#source-code) to see when you should use the

shorthand);

* Inline hyperlinks are **not** used. Separate the link and their target

definition, which you add on the bottom of the page;

* Inline markup should be closed on the same line as the open-string;

########## Example[¶](#example "Permalink to this headline")

```

Example

=======

When you are working on the docs, you should follow the

`Sylius Documentation`_ standards.

Level 2

-------

A PHP example would be::

    echo 'Hello World';

Level 3

~~~~~~~

.. code-block:: php

    echo 'You cannot use the :: shortcut here';

.. _`Sylius Documentation`: https://docs.sylius.com/en/latest/contributing/documentation/standards.html

```

######### Code Examples[¶](#code-examples "Permalink to this headline")

* The code follows the [Sylius Coding Standards](index.html#document-book/contributing/code/standards)

as well as the [Twig Coding Standards](http://twig.sensiolabs.org/doc/coding_standards.html);

* To avoid horizontal scrolling on code blocks, we prefer to break a line

correctly if it crosses the 85th character, which in many IDEs is signalised by a vertical line;

* When you fold one or more lines of code, place `...` in a comment at the point

of the fold. These comments are:

```

// ... (php),

# ... (yaml/bash),

{# ... #} (twig)

<!-- ... --> (xml/html),

; ... (ini),

... (text)

```

* When you fold a part of a line, e.g. a variable value, put `...` (without comment)

at the place of the fold;

* Description of the folded code: (optional)

If you fold several lines: the description of the fold can be placed after the `...`

If you fold only part of a line: the description can be placed before the line;

* If useful to the reader, a PHP code example should start with the namespace

declaration;

* When referencing classes, be sure to show the `use` statements at the

top of your code block. You don’t need to show *all* `use` statements

in every example, just show what is actually being used in the code block;

* If useful, a `codeblock` should begin with a comment containing the filename

of the file in the code block. Don’t place a blank line after this comment,

unless the next line is also a comment;

* You should put a `$` in front of every bash line.

########## Formats[¶](#formats "Permalink to this headline")

Configuration examples should show recommended formats using code-block`. The recommended formats

(and their orders) are:

* **Configuration** (including services and routing): YAML

* **Validation**: XML

* **Doctrine Mapping**: XML

########## Example[¶](#id1 "Permalink to this headline")

```

// src/Foo/Bar.php

namespace Foo;

use Acme\Demo\Cat;

// ...

class Bar

{

    // ...

    public function foo($bar)

    {

        // set foo with a value of bar

        $foo = $bar;

        $cat = new Cat($foo);

        // ... check if $bar has the correct value

        return $cat->baz($bar, /\*...\*/);

    }

}

```

Caution

In YAML you should put a space after `{` and before `}` (e.g. `{ \_controller: ... }`),

but this should not be done in Twig (e.g. `{'hello' : 'value'}`).

######### Language Standards[¶](#language-standards "Permalink to this headline")

* For sections, use the following capitalization rules:

[Capitalization of the first word, and all other words, except for closed-class words](http://en.wikipedia.org/wiki/Letter_case#Headings_and_publication_titles):

> 

> The Vitamins are in my Fresh California Raisins

> 

> 

>

* Please use appropriate, informative, rather formal language;

* Do not place any kind of advertisements in the documentation;

* The documentation should be neutral, without judgements, opinions. Make sure you do not favor anyone,

our community is great as a whole, there is no need to point who is better than the rest of us;

* You should use a form of *you* instead of *we* (i.e. avoid the first person

point of view: use the second instead);

* When referencing a hypothetical person, such as “a user with a session cookie”, gender-neutral

pronouns (they/their/them) should be used. For example, instead of:

> 

> 

> 	+ he or she, use they

> 	+ him or her, use them

> 	+ his or her, use their

> 	+ his or hers, use theirs

> 	+ himself or herself, use themselves

>

######## Sylius Documentation License[¶](#sylius-documentation-license "Permalink to this headline")

The Sylius documentation is licensed under a Creative Commons

Attribution-Share Alike 3.0 Unported [License](http://creativecommons.org/licenses/by-sa/3.0/).

**You are free:**

* to *Share* — to copy, distribute and transmit the work;

* to *Remix* — to adapt the work.

**Under the following conditions:**

* *Attribution* — You must attribute the work in the manner specified by

the author or licensor (but not in any way that suggests that they

endorse you or your use of the work);

* *Share Alike* — If you alter, transform, or build upon this work, you

may distribute the resulting work only under the same or similar license

to this one.

**With the understanding that:**

* *Waiver* — Any of the above conditions can be waived if you get

permission from the copyright holder;

* *Public Domain* — Where the work or any of its elements is in the public

domain under applicable law, that status is in no way affected by the

license;

* *Other Rights* — In no way are any of the following rights affected by the

license:

> 

> 

> 	+ Your fair dealing or fair use rights, or other applicable copyright

> 	exceptions and limitations;

> 	+ The author’s moral rights;

> 	+ Rights other persons may have either in the work itself or in how

> 	the work is used, such as publicity or privacy rights.

>

* *Notice* — For any reuse or distribution, you must make clear to others

the license terms of this work. The best way to do this is with a link

to this web page.

This is a human-readable summary of the [Legal Code (the full license)](http://creativecommons.org/licenses/by-sa/3.0/legalcode).

* [Contributing to the Documentation](index.html#document-book/contributing/documentation/overview)

* [Documentation Format](index.html#document-book/contributing/documentation/format)

* [Documentation Standards](index.html#document-book/contributing/documentation/standards)

* [Sylius Documentation License](index.html#document-book/contributing/documentation/license)

####### Contributing Translations[¶](#contributing-translations "Permalink to this headline")

Sylius application is fully translatable, and what is more it is translated by the community to a variety of languages!

Sylius translations are kept on [Crowdin](http://translate.sylius.com).

######## How to contribute translations in any language?[¶](#how-to-contribute-translations-in-any-language "Permalink to this headline")

The process of submitting new translations in any existing language is really simple:

1. First of all you need an account on Crowdin. If do not have one, please [sign up](https://crowdin.com/join).

2. Find a piece of Sylius and translate it to chosen language [here](http://translate.sylius.com).

3. After approval from the Crowdin community it will be automatically synced into the main repository.

That’s all! You can start translating.

###### Contributing Code[¶](#contributing-code "Permalink to this headline")

* [Submitting a Patch](index.html#document-book/contributing/code/patches)

* [Security Issues](index.html#document-book/contributing/code/security)

* [Coding Standards](index.html#document-book/contributing/code/standards)

* [Conventions](index.html#document-book/contributing/code/conventions)

* [Sylius License](index.html#document-book/contributing/code/license)

###### Contributing Documentation[¶](#contributing-documentation "Permalink to this headline")

* [Contributing to the Documentation](index.html#document-book/contributing/documentation/overview)

* [Documentation Format](index.html#document-book/contributing/documentation/format)

* [Documentation Standards](index.html#document-book/contributing/documentation/standards)

* [Sylius Documentation License](index.html#document-book/contributing/documentation/license)

###### Contributing Translations[¶](#contributing-translations "Permalink to this headline")

* [Contributing Code](index.html#document-book/contributing/code/index)

* [Contributing Documentation](index.html#document-book/contributing/documentation/index)

* [Contributing Translations](index.html#document-book/contributing/translations/index)

* [Introduction](index.html#document-book/introduction/index)

* [Installation](index.html#document-book/installation/index)

* [Architecture](index.html#document-book/architecture/index)

* [Configuration](index.html#document-book/configuration/index)

* [Customers](index.html#document-book/customers/index)

* [Products](index.html#document-book/products/index)

* [Carts & Orders](index.html#document-book/orders/index)

* [Themes](index.html#document-book/themes/index)

* [Sylius Plugins](index.html#document-book/plugins/index)

* [Organization](index.html#document-book/organization/index)

* [Support](index.html#document-book/support/index)

* [Contributing](index.html#document-book/contributing/index)

* [API](index.html#document-book/api/index)

* [Frontend](index.html#document-book/frontend/index)

The Customization Guide[¶](#the-customization-guide "Permalink to this headline")

---------------------------------------------------------------------------------

[The Customization Guide](index.html#document-customization/index) is helpful while wanting to adapt Sylius to your personal business needs.

### The Customization Guide[¶](#the-customization-guide "Permalink to this headline")

The Customization Guide is helpful while wanting to adapt Sylius to your personal business needs.

#### Customizing Models[¶](#customizing-models "Permalink to this headline")

All models in Sylius are placed in the `Sylius\Component\\*ComponentName\*\Model` namespaces alongside with their interfaces.

Warning

Many models in Sylius are **extended in the Core component**.

If the model you are willing to override exists in the `Core` you should be extending the `Core` one, not the base model from the component.

Warning

Removing the generated and executed doctrine migration may cause warnings while a new migration is executed.

To avoid it, we suggest you do not delete the migration.

Note

Note that there are **translatable models** in Sylius also. The guide to translatable entities can be found below the regular one.

##### Why would you customize a Model?[¶](#why-would-you-customize-a-model "Permalink to this headline")

To give you an idea of some purposes of models customizing have a look at a few examples:

* Add `flag` field to the `Country`

* Add `secondNumber` to the `Customer`

* Change the `reviewSubject` of a `Review` (in Sylius we have `ProductReviews` but you can imagine for instance a `CustomerReview`)

* Add `icon` to the `PaymentMethod`

And of course many similar operations limited only by your imagination.

Let’s now see how you should perform such customizations.

##### How to customize a Model?[¶](#how-to-customize-a-model "Permalink to this headline")

Tip

You can browse the full implementation of this example on [this GitHub Pull Request](https://github.com/Sylius/Customizations/pull/2).

Let’s take the `Sylius\Component\Addressing\Country` as an example. This one is not extended in Core.

How can you check that?

For the `Country` run:

```

php bin/console debug:container --parameter=sylius.model.country.class

```

As a result you will get the `Sylius\Component\Addressing\Model\Country` - this is the class that you need to be extending.

Assuming that you would want to add another field on the model - for instance a `flag`, where the flag is a variable that stores your image URL

**1.** The first thing to do is to add your field to the `App\Entity\Addressing\Country` class, which extends the base `Sylius\Component\Addressing\Model\Country` class.

Apply the following changes to the `src/Entity/Addressing/Country.php` file that already exists in Sylius-Standard.

```

<?php

declare(strict\_types=1);

namespace App\Entity\Addressing;

use Doctrine\ORM\Mapping as ORM;

use Sylius\Component\Addressing\Model\Country as BaseCountry;

use Sylius\Component\Addressing\Model\CountryInterface;

/\*\*

 \* @ORM\Entity()

 \* @ORM\Table(name="sylius\_country")

 \*/

class Country extends BaseCountry implements CountryInterface

{

    /\*\* @ORM\Column(type="string", nullable=true) \*/

    private $flag;

    public function getFlag(): ?string

    {

        return $this->flag;

    }

    public function setFlag(string $flag): void

    {

        $this->flag = $flag;

    }

}

```

**2.** After that you’ll need to check the model’s class in the `config/packages/\_sylius.yaml`.

Under the `sylius\_\*` where `\*` is the name of the bundle of the model you are customizing, in our case it will be the `SyliusAddressingBundle` -> `sylius\_addressing`.

That in Sylius-Standard configuration is overridden already.

```

sylius\_addressing:

 resources:

 country:

 classes:

 model: App\Entity\Addressing\Country

```

You can check if the configuration in `config/\_sylius.yaml` is correct by running:

```

php bin/console debug:container --parameter=sylius.model.country.class

```

If all is well the output should look like:

```

---------------------------- -------------------------------------------

 Parameter Value

---------------------------- -------------------------------------------

 sylius.model.country.class App\Entity\Addressing\Country

---------------------------- -------------------------------------------

```

Tip

In some cases you will see an error stating that the there is something wrong with the resource configuration:

`Unrecognized option "classes" under...`

When this happens, please refer to [How to get Sylius Resource configuration from the container?](index.html#resource-configuration).

**3.** Update the database. There are two ways to do it.

* via direct database schema update:

```

php bin/console doctrine:schema:update --force

```

* via migrations:

Which we strongly recommend over updating the schema.

```

php bin/console doctrine:migrations:diff

php bin/console doctrine:migrations:migrate

```

Tip

Read more about the database modifications and migrations in the [Symfony documentation here](https://symfony.com/doc/current/book/doctrine.html#creating-the-database-tables-schema).

**4.** Additionally if you want to give the administrator an ability to add the `flag` to any of countries,

you’ll need to update its form type. Check how to do it [here](index.html#document-customization/form).

###### What happens while overriding Models?[¶](#what-happens-while-overriding-models "Permalink to this headline")

* Parameter `sylius.model.country.class` contains `App\Entity\Addressing\Country`.

* `sylius.repository.country` represents Doctrine repository for your new class.

* `sylius.manager.country` represents Doctrine object manager for your new class.

* `sylius.controller.country` represents the controller for your new class.

* All Doctrine relations to `Sylius\Component\Addressing\Model\Country` are using your new class as *target-entity*, you do not need to update any mappings.

* `CountryType` form type is using your model as `data\_class`.

* `Sylius\Component\Addressing\Model\Country` is automatically turned into Doctrine Mapped Superclass.

##### How to customize a translatable Model?[¶](#how-to-customize-a-translatable-model "Permalink to this headline")

Tip

You can browse the full implementation of this example on [this GitHub Pull Request](https://github.com/Sylius/Customizations/pull/4).

One of translatable entities in Sylius is the Shipping Method. Let’s try to extend it with a new field.

Shipping methods may have different delivery time, let’s save it on the `estimatedDeliveryTime` field.

Just like for regular models you can also check the class of translatable models like that:

```

php bin/console debug:container --parameter=sylius.model.shipping_method.class

```

**1.** The first thing to do is to add your own fields in class `App\Entity\Shipping\ShippingMethod` extending the base `Sylius\Component\Core\Model\ShippingMethod` class.

Apply the following changes to the `src/Entity/Shipping/ShippingMethod.php` file existing in Sylius-Standard.

```

<?php

declare(strict\_types=1);

namespace App\Entity\Shipping;

use Doctrine\ORM\Mapping as ORM;

use Sylius\Component\Core\Model\ShippingMethod as BaseShippingMethod;

use Sylius\Component\Core\Model\ShippingMethodInterface;

use Sylius\Component\Shipping\Model\ShippingMethodTranslationInterface;

/\*\*

 \* @ORM\Entity()

 \* @ORM\Table(name="sylius\_shipping\_method")

 \*/

class ShippingMethod extends BaseShippingMethod implements ShippingMethodInterface

{

    /\*\* @ORM\Column(type="string",nullable=true) \*/

    private $estimatedDeliveryTime;

    public function getEstimatedDeliveryTime(): ?string

    {

        return $this->estimatedDeliveryTime;

    }

    public function setEstimatedDeliveryTime(?string $estimatedDeliveryTime): void

    {

        $this->estimatedDeliveryTime = $estimatedDeliveryTime;

    }

    protected function createTranslation(): ShippingMethodTranslationInterface

    {

        return new ShippingMethodTranslation();

    }

}

```

Note

Remember to set the translation class properly, just like above in the `createTranslation()` method.

**2.** After that you’ll need to check the model’s class in the `config/packages/\_sylius.yaml`.

Under the `sylius\_\*` where `\*` is the name of the bundle of the model you are customizing,

in our case it will be the `SyliusShippingBundle` -> `sylius\_shipping`.

That in Sylius-Standard configuration is overridden already, but you may check if it is correctly overridden.

```

sylius\_shipping:

 resources:

 shipping\_method:

 classes:

 model: App\Entity\Shipping\ShippingMethod

```

Configuration `sylius\_shipping:` is provided by default in the sylius-standard

**3.** Update the database. There are two ways to do it.

* via direct database schema update:

```

php bin/console doctrine:schema:update --force

```

* via migrations:

Which we strongly recommend over updating the schema.

```

php bin/console doctrine:migrations:diff

php bin/console doctrine:migrations:migrate

```

Tip

Read more about the database modifications and migrations in the [Symfony documentation here](https://symfony.com/doc/current/book/doctrine.html#creating-the-database-tables-schema).

**4.** Additionally if you need to add the `estimatedDeliveryTime` to any of your shipping methods in the admin panel,

you’ll need to update its form type. Check how to do it [here](index.html#document-customization/form).

Warning

If you want the new field of your entity to be translatable, you need to extend the Translation class of your entity.

In case of the ShippingMethod it would be the `Sylius\Component\Shipping\Model\ShippingMethodTranslation`.

Also the form on which you will add the new field should be the TranslationType.

##### How to customize translatable fields of a translatable Model?[¶](#how-to-customize-translatable-fields-of-a-translatable-model "Permalink to this headline")

Tip

You can browse the full implementation of this example on [this GitHub Pull Request](https://github.com/Sylius/Customizations/pull/7).

Suppose you want to add a translatable property to a translatable entity, for example to the Shipping Method.

Let’s assume that you would like the Shipping method to include a message with the delivery conditions. Let’s save it on the `deliveryConditions` field.

Just like for regular models you can also check the class of translatable models like that:

```

php bin/console debug:container --parameter=sylius.model.shipping_method_translation.class

```

**1.** In order to add a translatable property to your entity, start from defining it on the class AppEntityShippingShippingMethodTranslation is already there in the right place.

Apply the following changes to the `src/Entity/Shipping/ShippingMethodTranslation.php` file existing in Sylius-Standard.

```

<?php

declare(strict\_types=1);

namespace App\Entity\Shipping;

use Doctrine\ORM\Mapping as ORM;

use Sylius\Component\Shipping\Model\ShippingMethodTranslation as BaseShippingMethodTranslation;

use Sylius\Component\Shipping\Model\ShippingMethodTranslationInterface;

/\*\*

 \* @ORM\Entity()

 \* @ORM\Table(name="sylius\_shipping\_method\_translation")

 \*/

class ShippingMethodTranslation extends BaseShippingMethodTranslation implements ShippingMethodTranslationInterface

{

    /\*\* @ORM\Column(type="string", nullable=true) \*/

    private $deliveryConditions;

    public function getDeliveryConditions(): ?string

    {

        return $this->deliveryConditions;

    }

    public function setDeliveryConditions(?string $deliveryConditions): void

    {

        $this->deliveryConditions = $deliveryConditions;

    }

}

```

**2.** Implement the getter and setter methods of the interface on the `App\Entity\Shipping\ShippingMethod` class.

```

<?php

declare(strict\_types=1);

namespace App\Entity\Shipping;

use Doctrine\ORM\Mapping as ORM;

use Sylius\Component\Core\Model\ShippingMethod as BaseShippingMethod;

use Sylius\Component\Core\Model\ShippingMethodInterface;

use Sylius\Component\Shipping\Model\ShippingMethodTranslationInterface;

/\*\*

 \* @ORM\Entity()

 \* @ORM\Table(name="sylius\_shipping\_method")

 \*/

class ShippingMethod extends BaseShippingMethod implements ShippingMethodInterface

{

    public function getDeliveryConditions(): ?string

    {

        return $this->getTranslation()->getDeliveryConditions();

    }

    public function setDeliveryConditions(?string $deliveryConditions): void

    {

        $this->getTranslation()->setDeliveryConditions($deliveryConditions);

    }

    protected function createTranslation(): ShippingMethodTranslationInterface

    {

        return new ShippingMethodTranslation();

    }

}

```

Note

Remember that if the original entity is not translatable you will need to initialize the translations collection in the constructor,

and use the TranslatableTrait. Take a careful look at the Sylius translatable entities.

**3.** After that you’ll need to override the model’s class in the `config/packages/\_sylius.yaml`.

Under the `sylius\_\*` where `\*` is the name of the bundle of the model you are customizing,

in our case it will be the `SyliusShippingBundle` -> `sylius\_shipping`.

```

sylius\_shipping:

 resources:

 shipping\_method:

 classes:

 model: App\Entity\Shipping\ShippingMethod

 translation:

 classes:

 model: App\Entity\Shipping\ShippingMethodTranslation

```

Configuration `sylius\_addressing:` is provided by default in the sylius-standard

**4.** Update the database. There are two ways to do it.

* via direct database schema update:

```

php bin/console doctrine:schema:update --force

```

* via migrations:

Which we strongly recommend over updating the schema.

```

php bin/console doctrine:migrations:diff

php bin/console doctrine:migrations:migrate

```

Tip

Read more about the database modifications and migrations in the [Symfony documentation here](https://symfony.com/doc/current/book/doctrine.html#creating-the-database-tables-schema).

**5.** If you need to add delivery conditions to your shipping methods in the admin panel,

you’ll need to update its form type. Check how to do it [here](index.html#document-customization/form).

###### Good to know[¶](#good-to-know "Permalink to this headline")

See also

All the customizations can be done either in your application directly or in [Plugins](index.html#document-book/plugins/index)!

#### Customizing Forms[¶](#customizing-forms "Permalink to this headline")

The forms in Sylius are placed in the `Sylius\Bundle\\*BundleName\*\Form\Type` namespaces and the extensions

will be placed in AppFormExtension.

##### Why would you customize a Form?[¶](#why-would-you-customize-a-form "Permalink to this headline")

There are plenty of reasons to modify forms that have already been defined in Sylius.

Your business needs may sometimes slightly differ from our internal assumptions.

You can:

* add completely **new fields**,

* **modify** existing fields, make them required, change their HTML class, change labels etc.,

* **remove** fields that are not used.

##### How to customize a Form?[¶](#how-to-customize-a-form "Permalink to this headline")

Tip

You can browse the full implementation of this example on [this GitHub Pull Request.](https://github.com/Sylius/Customizations/pull/8)

If you want to modify the form for the `Customer Profile` in your system there are a few steps that you should take.

Assuming that you would like to (for example):

* Add a `secondaryPhoneNumber` field,

* Remove the `gender` field,

* Change the label for the `lastName` from `sylius.form.customer.last\_name` to `app.form.customer.surname`

These will be the steps that you will have to take to achieve that:

**1.** If you are planning to add new fields remember that beforehand they need to be added on the model that the form type is based on.

> 

> In case of our example if you need to have the `secondaryPhoneNumber` on the model and the entity mapping for the `Customer` resource.

> To get to know how to prepare that go [there](index.html#document-customization/model).

**2.** Create a **Form Extension**.

Your form has to extend a proper base class. How can you check that?

For the `CustomerProfileType` run:

```

php bin/console debug:container sylius.form.type.customer_profile

```

As a result you will get the `Sylius\Bundle\CustomerBundle\Form\Type\CustomerProfileType` - this is the class that you need to be extending.

```

<?php

declare(strict\_types=1);

namespace App\Form\Extension;

use Sylius\Bundle\CustomerBundle\Form\Type\CustomerProfileType;

use Symfony\Component\Form\AbstractTypeExtension;

use Symfony\Component\Form\Extension\Core\Type\TextType;

use Symfony\Component\Form\FormBuilderInterface;

final class CustomerProfileTypeExtension extends AbstractTypeExtension

{

    public function buildForm(FormBuilderInterface $builder, array $options): void

    {

        $builder

            // Adding new fields works just like in the parent form type.

            ->add('secondaryPhoneNumber', TextType::class, [

                'required' => false,

                'label' => 'app.form.customer.secondary\_phone\_number',

            ])

            // To remove a field from a form simply call ->remove(`fieldName`).

            ->remove('gender')

            // You can change the label by adding again the same field with a changed `label` parameter.

            ->add('lastName', TextType::class, [

                'label' => 'app.form.customer.surname',

            ]);

    }

    public static function getExtendedTypes(): iterable

    {

        return [CustomerProfileType::class];

    }

}

```

Note

Of course remember that you need to define new labels for your fields

in the `translations\messages.en.yaml` for english contents of your messages.

**3.** After creating your class, register this extension as a service in the `config/services.yaml`:

Caution

Remember! Service registration is not needed if you have autoconfiguration enabled in your services container.

```

services:

 app.form.extension.type.customer\_profile:

 class: App\Form\Extension\CustomerProfileTypeExtension

 tags:

 - { name: form.type\_extension, extended\_type: Sylius\Bundle\CustomerBundle\Form\Type\CustomerProfileType }

```

Note

Of course remember that you need to render the new fields you have created,

and remove the rendering of the fields that you have removed **in your views**.

In our case you will need to copy the original template from `vendor/sylius/sylius/src/Sylius/Bundle/ShopBundle/Resources/views/Account/profileUpdate.html.twig`

to `templates/bundles/SyliusShopBundle/Account/` and add the fields inside the copy.

```

{{ form\_row(form.phoneNumber) }}

{{ form\_row(form.subscribedToNewsletter) }}

<!-- your fields -->

{{ form\_row(form.birthday) }}

{{ form\_row(form.secondaryPhoneNumber) }}

{{ sonata\_block\_render\_event('sylius.shop.account.profile.update.form', {'customer': customer, 'form': form}) }}

```

###### Need more information?[¶](#need-more-information "Permalink to this headline")

Warning

Some of the forms already have extensions in Sylius. Learn more about Extensions [here](https://symfony.com/doc/current/form/create_form_type_extension.html).

For instance the `ProductVariant` admin form is defined under `Sylius/Bundle/ProductBundle/Form/Type/ProductVariantType.php` and later extended in

`Sylius/Bundle/CoreBundle/Form/Extension/ProductVariantTypeExtension.php`. If you again extend the base type form like this:

```

services:

 app.form.extension.type.product\_variant:

 class: App\Form\Extension\ProductVariantTypeMyExtension

 tags:

 - { name: form.type\_extension, extended\_type: Sylius\Bundle\ProductBundle\Form\Type\ProductVariantType, priority: -5 }

```

your form extension will also be executed. Whether before or after the other extensions depends on priority tag set.

###### How to customize forms that are already extended in Core?[¶](#how-to-customize-forms-that-are-already-extended-in-core "Permalink to this headline")

Tip

You can browse the full implementation of this example on [this GitHub Pull Request.](https://github.com/Sylius/Customizations/pull/9)

Having a look at the extensions and possible additionally defined event handlers can also be useful when form elements are embedded dynamically,

as is done in the `ProductVariantTypeExtension` by the `CoreBundle`:

```

<?php

// ...

final class ProductVariantTypeExtension extends AbstractTypeExtension

{

    public function buildForm(FormBuilderInterface $builder, array $options): void

    {

        // ...

        $builder->addEventListener(FormEvents::PRE\_SET\_DATA, function (FormEvent $event) {

            $productVariant = $event->getData();

            $event->getForm()->add('channelPricings', ChannelCollectionType::class, [

                'entry\_type' => ChannelPricingType::class,

                'entry\_options' => function (ChannelInterface $channel) use ($productVariant) {

                    return [

                        'channel' => $channel,

                        'product\_variant' => $productVariant,

                        'required' => false,

                    ];

                },

                'label' => 'sylius.form.variant.price',

            ]);

        });

    }

    // ...

}

```

The `channelPricings` get added on `FormEvents::PRE\_SET\_DATA`, so when you wish to remove or alter this form definition,

you will also have to set up an event listener and then remove the field:

```

<?php

//...

final class ProductVariantTypeMyExtension extends AbstractTypeExtension

{

    // ...

    public function buildForm(FormBuilderInterface $builder, array $options): void

    {

        //...

        $builder

            ->addEventListener(FormEvents::PRE\_SET\_DATA, function (FormEvent $event) {

                $event->getForm()->remove('channelPricings');

            })

            ->addEventSubscriber(new AddCodeFormSubscriber(NULL, ['label' => 'app.form.my\_other\_code\_label']))

        ;

        // ...

    }

}

```

###### Adding constraints inside a form extension[¶](#adding-constraints-inside-a-form-extension "Permalink to this headline")

Warning

When adding your constraints dynamically from inside a form extension, be aware to add the correct validation groups.

Although it is advised to follow the [Validation Customization Guide](index.html#document-customization/validation), it might happen that you

want to define the form constraints from inside the form extension. They will not be used unless the correct validation group(s)

has been added. The example below shows how to add the default sylius group to a constraint.

Tip

You can browse the full implementation of this example on [this GitHub Pull Request.](https://github.com/Sylius/Customizations/pull/8)

```

<?php

// ...

final class CustomerProfileTypeExtension extends AbstractTypeExtension

{

    // ...

    public function buildForm(FormBuilderInterface $builder, array $options): void

    {

        //...

        $builder

        // Adding new fields works just like in the parent form type.

        ->add('secondaryPhoneNumber', TextType::class, [

            'required' => false,

            'label' => 'app.form.customer.secondary\_phone\_number',

            'constraints' => [

                new Length([

                    'min' => 6,

                    'max' => 10,

                    'groups' => ['sylius'],

                ]),

            ],

        ]);

        // ...

    }

    // ...

}

```

###### Overriding forms completely[¶](#overriding-forms-completely "Permalink to this headline")

Tip

If you need to create a new form type on top of an existing one - create this new alternative form type and define getParent()

to the old one. [See details in the Symfony docs](https://symfony.com/doc/current/form/create_custom_field_type.html).

####### Good to know[¶](#good-to-know "Permalink to this headline")

See also

All the customizations can be done either in your application directly or in [Plugins](index.html#document-book/plugins/index)!

#### Customizing Repositories[¶](#customizing-repositories "Permalink to this headline")

Warning

In **Sylius** we are using both default Doctrine repositories and the custom ones.

Often you will be needing to add your very own methods to them. You need to check before which repository is your resource using.

##### Why would you customize a Repository?[¶](#why-would-you-customize-a-repository "Permalink to this headline")

Different sets of different resources can be obtained in various scenarios in your application.

You may need for instance:

> 

> * finding Orders by a Customer and a chosen Product

> * finding Products by a Taxon

> * finding Comments by a Customer

> 

> 

> 

##### How to customize a Repository?[¶](#how-to-customize-a-repository "Permalink to this headline")

Tip

You can browse the full implementation of this example on [this GitHub Pull Request.](https://github.com/Sylius/Customizations/pull/5)

Let’s assume that you would want to find products that you are running out of in the inventory.

**1.** Create your own repository class under the `App\Repository` namespace.

Remember that it has to extend a proper base class. How can you check that?

For the `ProductRepository` run:

```

php bin/console debug:container sylius.repository.product

```

As a result you will get the `Sylius\Bundle\CoreBundle\Doctrine\ORM\ProductRepository` - this is the class that you need to be extending.

To make your class more reusable, you should create a new interface `src/Repository/ProductRepositoryInterface.php`

which will extend `Sylius\Component\Core\Repository\ProductRepositoryInterface`

```

<?php

declare(strict\_types=1);

namespace App\Repository;

use Sylius\Component\Core\Repository\ProductRepositoryInterface as BaseProductRepositoryInterface;

interface ProductRepositoryInterface extends BaseProductRepositoryInterface

{

    public function findAllByOnHand(int $limit): array;

}

```

```

<?php

namespace App\Repository;

use Sylius\Bundle\CoreBundle\Doctrine\ORM\ProductRepository as BaseProductRepository;

class ProductRepository extends BaseProductRepository

{

    public function findAllByOnHand(int $limit = 8): array

    {

        return $this->createQueryBuilder('o')

            ->addSelect('variant')

            ->addSelect('translation')

            ->leftJoin('o.variants', 'variant')

            ->leftJoin('o.translations', 'translation')

            ->addOrderBy('variant.onHand', 'ASC')

            ->setMaxResults($limit)

            ->getQuery()

            ->getResult()

        ;

    }

}

```

We are using the [Query Builder](https://www.doctrine-project.org/projects/doctrine-orm/en/latest/reference/query-builder.html) in the Repositories.

As we are selecting Products we need to have a join to translations, because they are a translatable resource. Without it in the query results we wouldn’t have a name to be displayed.

We are sorting the results by the count of how many products are still available on hand, which is saved on the `onHand` field on the specific `variant` of each product.

Then we are limiting the query to 8 by default, to get only 8 products that are low in stock.

**2.** In order to use your repository you need to configure it in the `config/packages/\_sylius.yaml`.

As you can see in the `\_sylius.yaml` you already have a basic configuration, now you just need to add your repository and override resourceRepository

```

sylius\_product:

 resources:

 product:

 classes:

 #...

 repository: App\Repository\ProductRepository

 #...

```

**3.** After configuring the `sylius.repository.product` service has your `findByOnHand()` method available.

You can now use your method in anywhere when you are operating on the Product repository.

For example you can configure new route:

```

app\_shop\_partial\_product\_index\_by\_on\_hand:

path: /partial/products/by-on-hand

methods: [GET]

defaults:

 \_controller: sylius.controller.product:indexAction

 \_sylius:

 template: '@SyliusShop/Product/\_horizontalList.html.twig'

 repository:

 method: findAllByOnHand

 arguments: [4]

 criteria: false

 paginate: false

 limit: 100

```

##### What happens while overriding Repositories?[¶](#what-happens-while-overriding-repositories "Permalink to this headline")

* The parameter `sylius.repository.product.class` contains `App\Repository\ProductRepository`.

* The repository service `sylius.repository.product` is using your new class.

* Under the `sylius.repository.product` service you have got all methods from the base repository available plus the one you have added.

###### Good to know[¶](#good-to-know "Permalink to this headline")

See also

All the customizations can be done either in your application directly or in [Plugins](index.html#document-book/plugins/index)!

#### Customizing Factories[¶](#customizing-factories "Permalink to this headline")

Warning

Some factories may already be decorated in the **Sylius** Core.

You need to check before decorating which factory (Component or Core) is your resource using.

##### Why would you customize a Factory?[¶](#why-would-you-customize-a-factory "Permalink to this headline")

Differently configured versions of resources may be needed in various scenarios in your application.

You may need for instance to:

> 

> * create a Product with a Supplier (which is your own custom entity)

> * create a disabled Product (for further modifications)

> * create a ProductReview with predefined description

> 

> 

> 

and many, many more.

##### How to customize a Factory?[¶](#how-to-customize-a-factory "Permalink to this headline")

Tip

You can browse the full implementation of this example on [this GitHub Pull Request.](https://github.com/Sylius/Customizations/pull/12)

Let’s assume that you would want to have a possibility to create disabled products.

**1.** Create your own factory class in the `App\Factory` namespace.

Remember that it has to implement a proper interface. How can you check that?

For the `ProductFactory` run:

```

php bin/console debug:container sylius.factory.product

```

As a result you will get the `Sylius\Component\Product\Factory\ProductFactory` - this is the class that you need to decorate.

Take its interface (`Sylius\Component\Product\Factory\ProductFactoryInterface`) and implement it.

```

<?php

declare(strict\_types=1);

namespace App\Factory;

use Sylius\Component\Product\Model\ProductInterface;

use Sylius\Component\Product\Factory\ProductFactoryInterface;

final class ProductFactory implements ProductFactoryInterface

{

    /\*\* @var ProductFactoryInterface \*/

    private $decoratedFactory;

    public function \_\_construct(ProductFactoryInterface $factory)

    {

        $this->decoratedFactory = $factory;

    }

    public function createNew(): ProductInterface

    {

        return $this->decoratedFactory->createNew();

    }

    public function createWithVariant(): ProductInterface

    {

        return $this->decoratedFactory->createWithVariant();

    }

    public function createDisabled(): ProductInterface

    {

        /\*\* @var ProductInterface $product \*/

        $product = $this->decoratedFactory->createWithVariant();

        $product->setEnabled(false);

        return $product;

    }

}

```

**2.** In order to decorate the base ProductFactory with your implementation you need to configure it

as a decorating service in the `config/services.yaml`.

```

services:

 app.factory.product:

 class: App\Factory\ProductFactory

 decorates: sylius.factory.product

 arguments: ['@app.factory.product.inner']

 public: false

```

**3.** You can use the new method of the factory in routing.

After the `sylius.factory.product` has been decorated it has got the new `createDisabled()` method.

To actually use it overwrite `sylius\_admin\_product\_create\_simple` route like below in `config/routes.yaml`:

```

# config/routes.yaml

sylius\_admin\_product\_create\_simple:

 path: /products/new/simple

 methods: [GET, POST]

 defaults:

 \_controller: sylius.controller.product:createAction

 \_sylius:

 section: admin

 factory:

 method: createDisabled # like here for example

 template: "@SyliusAdmin/Crud/create.html.twig"

 redirect: sylius\_admin\_product\_update

 vars:

 subheader: sylius.ui.manage\_your\_product\_catalog

 templates:

 form: "@SyliusAdmin/Product/\_form.html.twig"

 route:

 name: sylius\_admin\_product\_create\_simple

```

###### Good to know[¶](#good-to-know "Permalink to this headline")

See also

All the customizations can be done either in your application directly or in [Plugins](index.html#document-book/plugins/index)!

####### Learn more[¶](#learn-more "Permalink to this headline")

* [ResourceBundle documentation](https://github.com/Sylius/SyliusResourceBundle/blob/master/docs/index.md)

#### Customizing Controllers[¶](#customizing-controllers "Permalink to this headline")

All **Sylius** resources use the

[Sylius\Bundle\ResourceBundle\Controller\ResourceController](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Bundle/Controller/ResourceController.php)

by default, but some of them have already been extended in Bundles.

If you want to override a controller action, check which controller you should be extending.

Note

There are two types of controllers we can define in Sylius:

**Resource Controllers** - are based only on one Entity, so they return only the resources they have in their name. For instance a `ProductController` should return only products.

**Standard Controllers** - non-resource; these may use many entities at once, they are useful on more general pages.

We are defining these controllers only if the actions we want cannot be done through yaml configuration - like sending emails.

Tip

You can browse the full implementation of these examples on [this GitHub Pull Request.](https://github.com/Sylius/Customizations/pull/13)

##### Why would you customize a Controller?[¶](#why-would-you-customize-a-controller "Permalink to this headline")

To add your custom actions you need to override controllers. You may need to:

* add a generic action that will render a list of recommended products with a product on its show page.

* render a partial template that cannot be done via yaml resource action.

##### How to customize a Resource Controller?[¶](#how-to-customize-a-resource-controller "Permalink to this headline")

Imagine that you would want to render a list of best selling products in a partial template that will be reusable anywhere.

Assuming that you already have a method on the `ProductRepository` - you can see such an example [here](index.html#document-customization/repository).

Having this method you may be rendering its result in a new action of the `ProductController` using a partial template.

See example below:

**1.** Create a new Controller class under the `App\Controller` namespace.

Remember that it has to extend a proper base class. How can you check that?

For the `ProductController` run:

```

php bin/console debug:container sylius.controller.product

```

As a result you will get the `Sylius\Bundle\ResourceBundle\Controller\ResourceController` - this is the class that you need to extend.

Now you have to create the controller that will have a generic action that is basically the `showAction` from the `ResourceController` extended by

getting a list of recommended products from your external api.

```

<?php

declare(strict\_types=1);

namespace App\Controller;

use Sylius\Bundle\ResourceBundle\Controller\ResourceController;

use Sylius\Component\Resource\ResourceActions;

use Symfony\Component\HttpFoundation\Request;

use Symfony\Component\HttpFoundation\Response;

class ProductController extends ResourceController

{

    public function showAction(Request $request): Response

    {

        $configuration = $this->requestConfigurationFactory->create($this->metadata, $request);

        $this->isGrantedOr403($configuration, ResourceActions::SHOW);

        $product = $this->findOr404($configuration);

        // some custom provider service to retrieve recommended products

        $recommendationService = $this->get('app.provider.product');

        $recommendedProducts = $recommendationService->getRecommendedProducts($product);

        $this->eventDispatcher->dispatch(ResourceActions::SHOW, $configuration, $product);

        if ($configuration->isHtmlRequest()) {

            return $this->render($configuration->getTemplate(ResourceActions::SHOW . '.html'), [

                'configuration' => $configuration,

                'metadata' => $this->metadata,

                'resource' => $product,

                'recommendedProducts' => $recommendedProducts,

                $this->metadata->getName() => $product,

            ]);

        }

        return $this->createRestView($configuration, $product);

    }

}

```

**2.** In order to use your controller and its actions you need to configure it in the `config/packages/\_sylius.yaml`.

```

sylius\_product:

 resources:

 product:

 classes:

 controller: App\Controller\ProductController

```

**3.** Disable autowire for your controller in `config/services.yaml`

```

App\Controller\ProductController:

 autowire: false

```

Tip

Run `php bin/console debug:container sylius.controller.product` to make sure the class has changed to your implementation.

**4.** Finally you’ll need to add routes in the `config/routes.yaml`.

```

app\_product\_show\_index:

 path: /product/show

 methods: [GET]

 defaults:

 \_controller: sylius.controller.product::showAction

```

##### How to customize a Standard Controller?[¶](#how-to-customize-a-standard-controller "Permalink to this headline")

Let’s assume that you would like to add some logic to the Homepage.

**1.** Create a new Controller class under the `App\Controller\Shop` namespace.

If you still need the methods of the original `HomepageController`, then copy its body to the new class.

```

<?php

declare(strict\_types=1);

namespace App\Controller\Shop;

use Symfony\Component\HttpFoundation\Response;

use Twig\Environment;

final class HomepageController

{

    /\*\* @var Environment \*/

    private $twig;

    public function \_\_construct(Environment $twig)

    {

        $this->twig = $twig;

    }

    public function indexAction(): Response

    {

        return new Response($this->twig->render('@SyliusShop/Homepage/index.html.twig'));

    }

    public function customAction(): Response

    {

        return new Response($this->twig->render('custom.html.twig'));

    }

}

```

**2.** The next thing you have to do is to override the `sylius.controller.shop.homepage` service definition in the `config/services.yaml`.

```

# config/services.yaml

services:

 sylius.controller.shop.homepage:

 class: App\Controller\Shop\HomepageController

 autowire: true

 tags: ['controller.service\_arguments']

```

Tip

Run `php bin/console debug:container sylius.controller.shop.homepage` to make sure the class has changed to your implementation.

**3.** Finally you’ll need to add routes in the `config/routes.yaml`.

```

app\_shop\_custom\_action:

 path: /custom

 methods: [GET]

 defaults:

 \_controller: sylius.controller.shop.homepage::customAction

```

From now on your `customAction` of the `HomepageController` will be available alongside the `indexAction` from the base class.

###### Good to know[¶](#good-to-know "Permalink to this headline")

See also

All the customizations can be done either in your application directly or in [Plugins](index.html#document-book/plugins/index)!

#### Customizing Validation[¶](#customizing-validation "Permalink to this headline")

The default validation group for all resources is `sylius`, but you can configure your own validation.

##### How to customize validation?[¶](#how-to-customize-validation "Permalink to this headline")

Tip

You can browse the full implementation of these examples on [this GitHub Pull Request.](https://github.com/Sylius/Customizations/pull/15)

Let’s take the example of changing the length of `name` for the `Product` entity - watch out the field `name` is hold on the `ProductTranslation` model.

In the `sylius` validation group the minimum length is equal to 2.

What if you’d want to have at least 10 characters?

**1.** Create the `config/validator/validation.yaml`.

In this file you need to overwrite the whole validation of your field that you are willing to modify.

Take this configuration from the `src/Sylius/Bundle/ProductBundle/Resources/config/validation/ProductTranslation.xml` - you can choose format `xml` or `yaml`.

Give it a new, custom validation group - `[app\_product]`.

```

Sylius\Component\Product\Model\ProductTranslation:

 properties:

 name:

 - NotBlank:

 message: sylius.product.name.not\_blank

 groups: [app\_product]

 - Length:

 min: 10

 minMessage: sylius.product.name.min\_length

 max: 255

 maxMessage: sylius.product.name.max\_length

 groups: [app\_product]

```

Tip

When using custom validation messages see [here how to add them](https://symfony.com/doc/current/validation/translations.html).

**2.** Configure the new validation group in the `config/services.yaml`.

```

# config/services.yaml

parameters:

 sylius.form.type.product\_translation.validation\_groups: [app\_product]

 sylius.form.type.product.validation\_groups: [app\_product] # the product class also needs to be aware of the translation's validation

```

Done. Now in all forms where the Product `name` is being used, your new validation group will be applied,

not letting users add products with name shorter than 10 characters.

Tip

When you would like to use group sequence validation, [like so](https://symfony.com/doc/current/validation/sequence_provider.html).

Be sure to use `[Default]` as validation group. Otherwise your `getGroupSequence()` method will not be called.

###### Good to know[¶](#good-to-know "Permalink to this headline")

See also

All the customizations can be done either in your application directly or in [Plugins](index.html#document-book/plugins/index)!

#### Customizing Menus[¶](#customizing-menus "Permalink to this headline")

Adding new positions in your menu is done **via events**.

You have got the `Sylius\Bundle\UiBundle\Menu\Event\MenuBuilderEvent` with `FactoryInterface` and `ItemInterface` of [KnpMenu](https://github.com/KnpLabs/KnpMenu), this lets you manipulate the whole menu.

You’ve got eight events that you should be subscribing to:

```

sylius.menu.shop.account # For the menu of the MyAccount section in shop

sylius.menu.admin.main # For the Admin Panel menu

sylius.menu.admin.customer.show # For the buttons menu on top of the show page of the Customer (/admin/customers/{id})

sylius.menu.admin.order.show # For the buttons menu on top of the show page of the Order (/admin/orders/{id})

sylius.menu.admin.product.form # For the tabular menu on the left hand side of the new/edit pages of the Product (/admin/products/new & /admin/products/{id}/edit)

sylius.menu.admin.product.update # For the buttons menu on top of the update page of the Product (/admin/products/{id}/edit)

sylius.menu.admin.product\_variant.form # For the tabular menu on the left hand side of the new/edit pages of the ProductVariant (/admin/products/{productId}/variants/new & /admin/products/{productId}/variants/{id}/edit)

sylius.menu.admin.promotion.update # For the buttons menu on top of the update page of the Promotion (/admin/promotions/{id}/edit)

```

##### How to customize Admin Menu?[¶](#how-to-customize-admin-menu "Permalink to this headline")

Tip

You can browse the full implementation of these examples on [this GitHub Pull Request.](https://github.com/Sylius/Customizations/pull/14)

Tip

Admin Panel menu is the one in the left expandable sidebar on the `/admin/` url.

**1.** In order to add items to the Admin menu in **Sylius** you have to create a `App\Menu\AdminMenuListener` class.

In the example below we are adding a one new item and sub-item to the Admin panel menu.

```

<?php

namespace App\Menu;

use Sylius\Bundle\UiBundle\Menu\Event\MenuBuilderEvent;

final class AdminMenuListener

{

    public function addAdminMenuItems(MenuBuilderEvent $event): void

    {

        $menu = $event->getMenu();

        $newSubmenu = $menu

            ->addChild('new')

            ->setLabel('Custom Admin Submenu')

        ;

        $newSubmenu

            ->addChild('new-subitem')

            ->setLabel('Custom Admin Menu Item')

        ;

    }

}

```

**2.** After creating your class with a proper method for the menu customizations you need, subscribe your

listener to the `sylius.menu.admin.main` event in the `config/services.yaml`.

```

# config/services.yaml

services:

 app.listener.admin.menu\_builder:

 class: App\Menu\AdminMenuListener

 tags:

 - { name: kernel.event\_listener, event: sylius.menu.admin.main, method: addAdminMenuItems }

```

**3.** Result:

After these two steps your admin panel menu should look like that, the new items appear at the bottom:

![_images/admin_menu.png](_images/admin_menu.png)

##### How to customize Account Menu?[¶](#how-to-customize-account-menu "Permalink to this headline")

Tip

My Account panel menu is the one in the left sidebar on the `/account/dashboard/` url.

**1.** In order to add items to the Account menu in **Sylius** you have to create a `App\Menu\AccountMenuListener` class.

In the example below we are adding a one new item to **the menu in the My Account section of shop**.

```

<?php

namespace App\Menu;

use Sylius\Bundle\UiBundle\Menu\Event\MenuBuilderEvent;

final class AccountMenuListener

{

    public function addAccountMenuItems(MenuBuilderEvent $event): void

    {

        $menu = $event->getMenu();

        $menu

            ->addChild('new', ['route' => 'sylius\_shop\_account\_dashboard'])

            ->setLabel('Custom Account Menu Item')

            ->setLabelAttribute('icon', 'star')

        ;

    }

}

```

As you can see above the new item can be given a route, a label and an icon.

**2.** After creating your class with a proper method for the menu customizations you need, subscribe your

listener to the `sylius.menu.shop.account` event in the `config/services.yaml`.

```

# config/services.yaml

services:

 app.listener.shop.menu\_builder:

 class: App\Menu\AccountMenuListener

 tags:

 - { name: kernel.event\_listener, event: sylius.menu.shop.account, method: addAccountMenuItems }

```

**3.** Result:

After these two steps your user account menu should look like that, the new item appears at the bottom:

![_images/account_menu.png](_images/account_menu.png)

##### How to customize Admin Customer Show Menu?[¶](#how-to-customize-admin-customer-show-menu "Permalink to this headline")

Tip

Admin customer menu is the set of buttons in the right top corner on the `/admin/customers/{id}` url.

**1.** In order to add buttons to the Admin Customer Show menu in **Sylius** you have to create a `App\Menu\AdminCustomerShowMenuListener` class.

Note

**This menu is build from buttons.** There are a few button types available:

`edit`, `show`, `delete`, `link` (default), and `transition` (for state machines).

Buttons (except for the `link` and `transition` types) already have a defined color, icon and label.

The `link` and `transition` types buttons can be customized with the `setLabel('label')`, `setLabelAttribute('color', 'color')`

and `setLabelAttribute('icon', 'icon')` methods.

The `delete` button must have also the `resource\_id` attribute set (for csrf token purposes).

In the example below, we are adding one new button to the Admin Customer Show Menu. It has the type set, even though the `link`

type is default to make the example easily customizable.

```

<?php

namespace App\Menu;

use Sylius\Bundle\AdminBundle\Event\CustomerShowMenuBuilderEvent;

final class AdminCustomerShowMenuListener

{

    public function addAdminCustomerShowMenuItems(CustomerShowMenuBuilderEvent $event): void

    {

        $menu = $event->getMenu();

        $customer = $event->getCustomer();

        if (null !== $customer->getUser()) {

            $menu

                ->addChild('impersonate', [

                    'route' => 'sylius\_admin\_impersonate\_user',

                    'routeParameters' => ['username' => $customer->getUser()->getEmailCanonical()]

                ])

                ->setAttribute('type', 'link')

                ->setLabel('Impersonate')

                ->setLabelAttribute('icon', 'unhide')

                ->setLabelAttribute('color', 'blue')

            ;

        }

    }

}

```

**2.** After creating your class with a proper method for the menu customizations you need, subscribe your

listener to the `sylius.menu.admin.customer.show` event in the `config/services.yaml`.

```

# config/services.yaml

services:

 app.listener.admin.customer.show.menu\_builder:

 class: App\Menu\AdminCustomerShowMenuListener

 tags:

 - { name: kernel.event\_listener, event: sylius.menu.admin.customer.show, method: addAdminCustomerShowMenuItems }

```

After these two steps your admin panel customer menu should look like that, the new item appears at right corner:

![_images/admin_panel_customer_menu.png](_images/admin_panel_customer_menu.png)

##### How to customize Admin Order Show Menu?[¶](#how-to-customize-admin-order-show-menu "Permalink to this headline")

Tip

Admin order show menu is the set of buttons in the right top corner on the `/admin/orders/{id}` url.

**1.** In order to add buttons to the Admin Order Show menu in **Sylius** you have to create a `App\Menu\AdminOrderShowMenuListener` class.

Note

**This menu is build from buttons.** There are a few button types available:

`edit`, `show`, `delete`, `link` (default), and `transition` (for state machines).

Buttons (except for the `link` and `transition` types) already have a defined color, icon and label.

The `link` and `transition` types buttons can be customized with the `setLabel('label')`, `setLabelAttribute('color', 'color')`

and `setLabelAttribute('icon', 'icon')` methods.

The `delete` button must have also the `resource\_id` attribute set (for csrf token purposes).

In the example below, we are adding one new button to the Admin Order Show Menu. It is a `link` type button,

that will let the admin ship the order.

```

<?php

namespace App\Menu;

use Sylius\Bundle\AdminBundle\Event\OrderShowMenuBuilderEvent;

use Sylius\Component\Order\OrderTransitions;

final class AdminOrderShowMenuListener

{

    public function addAdminOrderShowMenuItems(OrderShowMenuBuilderEvent $event): void

    {

        $menu = $event->getMenu();

        $order = $event->getOrder();

        if (null !== $order->getId()) {

            $menu

                ->addChild('ship', [

                    'route' => 'sylius\_admin\_order\_shipment\_ship',

                    'routeParameters' => ['id' => $order->getId()]

                ])

                ->setAttribute('type', 'transition')

                ->setLabel('Ship')

                ->setLabelAttribute('icon', 'checkmark')

                ->setLabelAttribute('color', 'green')

            ;

        }

    }

}

```

**2.** After creating your class with a proper method for the menu customizations you need, subscribe your

listener to the `sylius.menu.admin.order.show` event in the `config/services.yaml`.

```

# config/services.yaml

services:

 app.listener.admin.order.show.menu\_builder:

 class: App\Menu\AdminOrderShowMenuListener

 tags:

 - { name: kernel.event\_listener, event: sylius.menu.admin.order.show, method: addAdminOrderShowMenuItems }

```

After these two steps your admin panel order menu should look like that (the new item appears at right corner):

![_images/admin_panel_order_menu.png](_images/admin_panel_order_menu.png)

##### How to customize Admin Product Form Menu?[¶](#how-to-customize-admin-product-form-menu "Permalink to this headline")

Tip

Admin product form menu is the set of tabs on your left hand side on the `/admin/products/new` and `/admin/products/{id}/edit` urls.

Warning

This part of the guide assumes you already know how to customize [models](index.html#document-customization/model) and [forms](index.html#document-customization/form).

**1.** In order to add a new tab to the Admin Product Form menu in **Sylius** you have to create a `App\Menu\AdminProductFormMenuListener` class.

Note

**This menu is build from tabs, each coupled with their own template containing the necessary part of the form.**

So lets say you want to add the product’s manufacturer details to the tabs.

Provided you have created a new template with all the required form fields and saved it etc.

as `templates\Admin\Product\Tab\\_manufacturer.html.twig`, we will use it in the example below.

```

<?php

namespace App\Menu;

use Sylius\Bundle\AdminBundle\Event\ProductMenuBuilderEvent;

final class AdminProductFormMenuListener

{

    public function addItems(ProductMenuBuilderEvent $event): void

    {

        $menu = $event->getMenu();

        $menu

            ->addChild('manufacturer')

            ->setAttribute('template', 'Admin/Product/Tab/\_manufacturer.html.twig')

            ->setLabel('Manufacturer')

        ;

    }

}

```

**2.** After creating your class with a proper method for the menu customizations you need, subscribe your

listener to the `sylius.menu.admin.product.form` event in the `config/services.yaml`.

```

# config/services.yaml

services:

 app.listener.admin.product.form.menu\_builder:

 class: App\Menu\AdminProductFormMenuListener

 tags:

 - { name: kernel.event\_listener, event: sylius.menu.admin.product.form, method: addItems }

```

After these two steps your admin panel product form menu should look like that (the new item appears at the bottom):

![_images/admin_panel_product_menu.png](_images/admin_panel_product_menu.png)

##### How to customize Admin Product Variant Form Menu?[¶](#how-to-customize-admin-product-variant-form-menu "Permalink to this headline")

Tip

Admin product variant form menu is the set of tabs on your left hand side on the `/admin/product/{productId}/variants/new` and `/admin/product/{productId}/variants/{id}/edit` urls.

Warning

This part of the guide assumes you already know how to customize [models](index.html#document-customization/model) and [forms](index.html#document-customization/form).

**1.** In order to add a new tab to the Admin Product Variant Form menu in **Sylius** you have to create a `App\Menu\AdminProductVariantFormMenuListener` class.

Note

**This menu is build from tabs, each coupled with their own template containing the necessary part of the form.**

So lets say you want to add the product variant’s media to the tabs.

Provided you have created a new template with the required form fields and saved it etc. as `templates\Admin\ProductVariant\Tab\\_media.html.twig`, we will use it in the example below.

```

<?php

namespace App\Menu;

use Sylius\Bundle\AdminBundle\Event\ProductVariantMenuBuilderEvent;

final class AdminProductVariantFormMenuListener

{

    public function addItems(ProductVariantMenuBuilderEvent $event): void

    {

        $menu = $event->getMenu();

        $menu

            ->addChild('media')

            ->setAttribute('template', 'Admin/ProductVariant/Tab/\_media.html.twig')

            ->setLabel('Media')

        ;

    }

}

```

**2.** After creating your class with a proper method for the menu customizations you need, subscribe your

listener to the `sylius.menu.admin.product\_variant.form` event in the `config/services.yaml`.

```

# config/services.yaml

services:

 app.listener.admin.product\_variant.form.menu\_builder:

 class: App\Menu\AdminProductVariantFormMenuListener

 tags:

 - { name: kernel.event\_listener, event: sylius.menu.admin.product\_variant.form, method: addItems }

```

After these two steps your admin panel variant menu should look like that (the new item appears at the bottom):

![_images/admin_panel_variant_menu.png](_images/admin_panel_variant_menu.png)

###### Good to know[¶](#good-to-know "Permalink to this headline")

See also

All the customizations can be done either in your application directly or in [Plugins](index.html#document-book/plugins/index)!

#### Customizing Templates[¶](#customizing-templates "Permalink to this headline")

Note

There are two kinds of templates in Sylius. **Shop** and **Admin** ones, plus you can create your own to satisfy your needs.

##### Why would you customize a template?[¶](#why-would-you-customize-a-template "Permalink to this headline")

The most important case for modifying the existing templates is of course **integrating your own layout of the system**.

Sometimes even if you have decided to stay with the default layout provided by Sylius, you need to **slightly modify it to meet your

business requirements**.

You may just need to **add your logo anywhere**.

##### Methods of templates customizing[¶](#methods-of-templates-customizing "Permalink to this headline")

Warning

There are three ways of customizing templates of Sylius:

The first one is simple **templates overriding** inside of the `templates/bundles` directory of your project. Using

this method you can completely change the content of templates.

The second method is **templates customization via events**. You are able to listen on these template events,

and by that add your own blocks without copying and pasting the whole templates. This feature is really useful

when [creating Sylius Plugins](index.html#document-book/plugins/creating-plugin).

The third method is **using Sylius themes**. Creating a Sylius theme requires a few more steps than basic template overriding,

but allows you to have a different design on multiple channels of the same Sylius instance. [Learn more about themes here](index.html#document-book/themes/themes).

Tip

You can browse the full implementation of these examples on [this GitHub Pull Request.](https://github.com/Sylius/Customizations/pull/16)

##### How to customize templates by overriding?[¶](#how-to-customize-templates-by-overriding "Permalink to this headline")

Note

How do you know which template you should be overriding?

Go to the page that you are going to modify, at the bottom in the Symfony toolbar click on the route,

which will redirect you to the profiler. In the Request Attributes section

under `\_sylius [ template => ...]` you can check the path to the current template.

* **Shop** templates: customizing Login Page template:

The default login template is: `@SyliusShopBundle/login.html.twig`.

In order to override it you need to create your own: `templates/bundles/SyliusShopBundle/login.html.twig`.

Copy the contents of the original template to make your work easier. And then modify it to your needs.

```

{% extends '@SyliusShop/layout.html.twig' %}

{% import '@SyliusUi/Macro/messages.html.twig' as messages %}

{% block content %}

<div class="ui column stackable center page grid">

 {% if last\_error %}

 {{ messages.error(last\_error.messageKey|trans(last\_error.messageData, 'security')) }}

 {% endif %}

 {# You can add a headline for instance to see if you are changing things in the correct place. #}

 <h1>

 This Is My Headline

 </h1>

 <div class="five wide column"></div>

 <form class="ui six wide column form segment" action="{{ path('sylius\_shop\_login\_check') }}" method="post" novalidate>

 <div class="one field">

 {{ form\_row(form.\_username, {'value': last\_username|default('')}) }}

 </div>

 <div class="one field">

 {{ form\_row(form.\_password) }}

 </div>

 <div class="one field">

 <button type="submit" class="ui fluid large primary submit button">{{ 'sylius.ui.login\_button'|trans }}</button>

 </div>

 </form>

</div>

{% endblock %}

```

Done! If you do not see any changes on the `/shop/login` url, clear your cache:

```

php bin/console cache:clear

```

* **Admin** templates: Customization of the Country form view.

The default template for the Country form is: `SyliusAdminBundle:Country:\_form.html.twig`.

In order to override it you need to create your own: `templates/bundles/SyliusAdminBundle/Country/\_form.html.twig`.

Copy the contents of the original template to make your work easier. And then modify it to your needs.

```

<div class="ui segment">

 {{ form\_errors(form) }}

 {{ form\_row(form.code) }}

 {{ form\_row(form.enabled) }}

</div>

<div class="ui segment">

 {# You can add a headline for instance to see if you are changing things in the correct place. #}

 <h1>My Custom Headline</h1>

 <h4 class="ui dividing header">{{ 'sylius.ui.provinces'|trans }}</h4>

 {{ form\_row(form.provinces, {'label': false}) }}

</div>

```

Done! If you do not see any changes on the `/admin/countries/new` url, clear your cache:

```

php bin/console cache:clear

```

##### How to customize templates via events?[¶](#how-to-customize-templates-via-events "Permalink to this headline")

Sylius uses its own event mechanism called Sylius Template Events which implementation is based purely on Twig.

This (compared to the legacy way of using SonataBlockBundle) leads to:

* better performance - as it is no longer based on EventListeners

* less boilerplate code - no need to register more Listeners

* easier variable pass - now you just need to add it to configuration file

* extended configuration - now you can change if block is enabled, change its template, or even priority

Note

If you want to read more about the Sylius Template Events from developers/architectural perspective

check the [Github Issue](<https://github.com/Sylius/Sylius/issues/10997>) referring this feature.

We will now guide you through a simple way of customizing your template with Sylius Template Events.

###### How to locate template events?[¶](#how-to-locate-template-events "Permalink to this headline")

The events naming convention uses the routing to the place where we are adding it, but instead of `\_` we are using `.`,

followed by a slot name (like `sylius\_admin\_customer\_show` route results in the `sylius.admin.customer.show.slot\_name` events).

The slot name describes where exactly in the template’s structure should the event occur, it will be `before` or `after` certain elements.

Although when the resource name is not just one word (like `product\_variant`) then the underscore stays in the event prefix string.

Then `sylius\_admin\_product\_variant\_create` route will have the `sylius.admin.product\_variant.create.slot\_name` events.

Let’s see how the event is rendered in a default Sylius Admin template. This is the rendering of the event that occurs

on the create action of Resources, at the bottom of the page (after the content of the create form):

```

{# First we are setting the event\_prefix based on route as it was mentioned before #}

{% set event\_prefix = metadata.applicationName ~ '.admin.' ~ metadata.name ~ '.create' %}

{# And then the slot name is appended to the event\_prefix #}

{{ sylius\_template\_event([event\_prefix, 'sylius.admin.create'], \_context) }}

```

Note

Besides the events that are named based on routing, Sylius also has some other general events: those that will appear

on every Sylius admin or shop. Examples: `sylius.shop.layout.slot\_name` or `sylius.admin.layout.slot\_name`.

They are rendered in the `layout.html.twig` views for both Admin and Shop.

Tip

In order to find events in Sylius templates you can simply search for the `sylius\_template\_event` phrase in your

project’s directory.

###### How to locate rendered template event?[¶](#how-to-locate-rendered-template-event "Permalink to this headline")

####### With DevTools in your browser[¶](#with-devtools-in-your-browser "Permalink to this headline")

If you want to search easier for the event name you want to modify, the Sylius Template Events can be easily

found in your browser with the debug tools it provides.

Just use the `explore` (in Chrome browser) or its equivalent in other browsers to check the HTML code of your webpage.

Here you will be able to see commented blocks where the name of the template as well as the event name will be shown:

![_images/sylius_event_debug.png](_images/sylius_event_debug.png)

In the example above we were looking for the HTML responsible for rendering of the Sylius Logo. Mentioned markup is surrounded

by statements of where the event, as well as block, started.

What is more, we can see which twig template is responsible for rendering this block and what the priority of this rendering is.

![_images/sylius_logo_locate.png](_images/sylius_logo_locate.png)

This will have all the necessary information that you need for further customization.

####### With Symfony Profiler[¶](#with-symfony-profiler "Permalink to this headline")

The `Template events` section in Symfony Profiler gives you the list of events used to render the page with their blocks.

Besides all information about blocks mentioned in the above section, you will see one more especially beneficial when it

comes to optimization which is `Duration`.

![_images/sylius_template_events_metrics.png](_images/sylius_template_events_metrics.png)

###### How to use template events for customizations?[¶](#how-to-use-template-events-for-customizations "Permalink to this headline")

When you have found an event in the place where you want to add some content, here’s what you have to do.

Let’s assume that you would like to add some content after the header in the Sylius shop views.

You will need to look at the `SyliusShopBundle/Resources/views/layout.html.twig` template,

which is the basic layout of Sylius shop, and then in it find the appropriate event.

For the space below the header it will be `sylius.shop.layout.after\_header`.

* Create a Twig template file that will contain what you want to add.

```

{# templates/block.html.twig #}

<h1> Test Block Title </h1>

```

* And configure Sylius UI to display it for the chosen event:

```

# config/packages/sylius\_ui.yaml

sylius\_ui:

 events:

 sylius.shop.layout.after\_header:

 blocks:

 my\_block\_name: 'block.html.twig'

```

That’s it. Your new block should appear in the view.

Tip

Learn more about adding custom JS & CSS in the cookbook [here](index.html#document-book/frontend/managing-assets).

###### Passing variables to the template events[¶](#passing-variables-to-the-template-events "Permalink to this headline")

By default, Sylius Template Events provide all variables from the template. If you want to pass some additional

variables, you can do it with the `context` key in the configuration. Let’s greet our customers at the top of

the homepage:

```

{# templates/greeting.html.twig #}

<h2>{{ message }}</h2>

```

```

# config/packages/sylius\_ui.yaml

sylius\_ui:

 events:

 sylius.shop.homepage:

 blocks:

 greeting:

 template: 'greeting.html.twig'

 priority: 70

 context:

 message: 'Hello!'

```

However, this simple way of passing variables may not be sufficient when you want to pass some complex data that comes

as a result of application logic. Perhaps you would like to greet customers with their names. In such cases, you need to

define your own `Context Provider`.

####### Context Providers[¶](#context-providers "Permalink to this headline")

Context Providers are responsible for providing context to the template events. The default one is the

`DefaultContextProvider` which provides all variables from the template and from the context in the block’s

configuration. You can have multiple Context Providers and they will provide their context to the template events with

the given priority with the `sylius.ui.template\_event.context\_provider` tag.

Let’s do something fancier than just greeting customers with a name. Say happy birthday to the customer! To do so,

create a `GreetingContextProvider` that will provide the `message` variable from the example above but this time

depending on the customer’s birthday:

> 

> 

> ```

> <?php

> 

> declare(strict\_types=1);

> 

> namespace App\ContextProvider;

> 

> use Sylius\Bundle\UiBundle\ContextProvider\ContextProviderInterface;

> use Sylius\Bundle\UiBundle\Registry\TemplateBlock;

> use Sylius\Component\Customer\Context\CustomerContextInterface;

> 

> final class GreetingContextProvider implements ContextProviderInterface

> {

>     public function \_\_construct(private CustomerContextInterface $customerContext)

>     {

>     }

> 

>     public function provide(array $templateContext, TemplateBlock $templateBlock): array

>     {

>         $customer = $this->customerContext->getCustomer();

> 

>         if (null === $customer) {

>             return $templateContext;

>         }

> 

>         $customerName = $customer->getFirstName() ?? $customer->getFullName();

> 

>         if (

>             null === $customer->getBirthday() ||

>             $customer->getBirthday()->format("m-d") !== (new \DateTime())->format("m-d")

>         ) {

>             $templateContext['message'] = sprintf('Hello %s!', $customerName);

>         } else {

>             $templateContext['message'] = sprintf('Happy Birthday %s!', $customerName);

>         }

> 

>         return $templateContext;

>     }

> 

>     public function supports(TemplateBlock $templateBlock): bool

>     {

>         return 'sylius.shop.homepage' === $templateBlock->getEventName()

>             && 'greeting' === $templateBlock->getName();

>     }

> }

> 

> ```

> 

> 

> 

Register the new Context Provider as a service in the `config/services.yaml`:

> 

> 

> ```

> services:

>  # ...

> 

>  App\ContextProvider\GreetingContextProvider:

>  arguments:

>  - '@sylius.context.customer'

>  tags:

>  - { name: sylius.ui.template\_event.context\_provider }

> 

> ```

> 

> 

> 

Now if the customer’s birthday is today, they will be greeted with a happy birthday message.

![_images/sylius_template_events_greeting.png](_images/sylius_template_events_greeting.png)

###### What more can I do with the Sylius Template Events?[¶](#what-more-can-i-do-with-the-sylius-template-events "Permalink to this headline")

You might think that this is the only way of customisation with the events, but you can also do more.

1. Disabling blocks:

You can now disable some blocks that do not fit your usage, just put in config:

```

sylius\_ui:

 events:

 sylius.shop.layout.event\_with\_ugly\_block:

 blocks:

 the\_block\_i\_dont\_like:

 enabled: false

```

2. Change the priority of blocks:

In order to override the templates from vendor, or maybe you are developing plugin you can change the priority of a block:

```

sylius\_ui:

 events:

 sylius.shop.layout.vendor\_block:

 blocks:

 my\_important\_block:

 priority: 1

```

3. Access variables:

You can access variables by using the function:

```

{{ dump() }}

```

You can also access the resources and entities (in the correct views) variables:

```

# for example in products show view

{{ dump(product) }}

```

Or you can pass any variable from the template to the block and access it with function:

```

# Parent html

...

    {{ sylius_template_event('sylius.shop.product.show', {'customVariable': variable}) }}

...

```

```

# Template html

...

    {{ dump(customVariable) }}

...

```

4. Override block templates:

You can override the existing blocks by changing the config:

```

# config.yaml

sylius\_ui:

 events:

 sylius.shop.layout.header.grid:

 blocks:

 logo: 'logo.html.twig'

```

And adding your own template into templates/logo.html.twig folder.

Note

Check out the full example of overriding the template in [Shop Customizations](index.html#document-getting-started-with-sylius/shop-customizations)

###### How to use themes for customizations?[¶](#how-to-use-themes-for-customizations "Permalink to this headline")

You can refer to the theme documentation available here:

- [Themes (The book)](index.html#document-book/themes/themes)

- [SyliusThemeBundle (Bundle documentation)](https://github.com/Sylius/SyliusThemeBundle/blob/master/docs/index.md)

##### Global Twig variables[¶](#global-twig-variables "Permalink to this headline")

Each of the Twig templates in Sylius is provided with the `sylius` variable,

that comes from the [ShopperContext](https://github.com/Sylius/Sylius/blob/master/src/Sylius/Component/Core/Context/ShopperContext.php).

The **ShopperContext** is composed of `ChannelContext`, `CurrencyContext`, `LocaleContext` and `CustomerContext`.

Therefore it has access to the current channel, currency, locale and customer.

The variables available in Twig are:

| Twig variable | ShopperContext method name |

| --- | --- |

| sylius.channel | getChannel() |

| sylius.currencyCode | getCurrencyCode() |

| sylius.localeCode | getLocaleCode() |

| sylius.customer | getCustomer() |

###### How to use these Twig variables?[¶](#how-to-use-these-twig-variables "Permalink to this headline")

You can check for example what is the current channel by dumping the `sylius.channel` variable.

```

{{ dump(sylius.channel) }}

```

That’s it, this will dump the content of the current Channel object.

###### Good to know[¶](#good-to-know "Permalink to this headline")

See also

All the customizations can be done either in your application directly or in [Plugins](index.html#document-book/plugins/index)!

#### Customizing Translations[¶](#customizing-translations "Permalink to this headline")

Note

We’ve adopted a convention of overriding translations in the `translations` directory.

##### Why would you customize a translation?[¶](#why-would-you-customize-a-translation "Permalink to this headline")

If you would like to change any of the translation keys defined in Sylius in any desired language.

For example:

* change “Last name” into “Surname”

* change “Add to cart” into “Buy”

There are many other places where you can customize the text content of pages.

##### How to customize a translation?[¶](#how-to-customize-a-translation "Permalink to this headline")

Tip

You can browse the full implementation of these examples on [this GitHub Pull Request.](https://github.com/Sylius/Customizations/pull/17)

In order to customize a translation in your project:

**1.** If you don’t have it yet, create `translations/messages.en.yaml` for English translations.

Note

You can create different files for different locales (languages). For example `messages.pl.yaml` should hold only Polish translations,

as they will be visible when the current locale is `PL`. Check the [Locales](index.html#document-book/configuration/locales) docs for more information.

Tip

Don’t forget to clear the cache to see your new translations appear: `php bin/console cache:clear`.

**2.** In this file, configure the desired key and give it a translation.

If you would like to change the translation of “Email” into “Username” on the login form you have to

override its translation key which is `sylius.form.customer.email`.

```

sylius:

 form:

 customer:

 email: Username

```

Before

![_images/before_customizing_translation.png](_images/before_customizing_translation.png)

After

![_images/after_customizing_translation.png](_images/after_customizing_translation.png)

Tip

**How to check what the proper translation key is for your message:**

When you are on the page where you are trying to customize a translation, click the Translations icon in the Symfony Profiler.

In this section you can see all messages with their associated keys on that page.

![_images/translations.png](_images/translations.png)

###### Good to know[¶](#good-to-know "Permalink to this headline")

See also

All the customizations can be done either in your application directly or in [Plugins](index.html#document-book/plugins/index)!

#### Customizing Flashes[¶](#customizing-flashes "Permalink to this headline")

##### Why would you customize a flash?[¶](#why-would-you-customize-a-flash "Permalink to this headline")

If you would like to change any of the flash messages defined in Sylius in any desired language.

For example:

* change the content of a flash when you add resource in the admin

* change the content of a flash when you register in the shop

and many other places where you can customize the text content of the default flashes.

##### How to customize a flash message?[¶](#how-to-customize-a-flash-message "Permalink to this headline")

Tip

You can browse the full implementation of these examples on [this GitHub Pull Request.](https://github.com/Sylius/Customizations/pull/18)

In order to customize a resource flash in your project:

**1.** Create the `translations\flashes.en.yaml` for english contents of your flashes.

Note

You can create different files for different locales (languages). For example `flashes.pl.yaml` should hold only polish flashes,

as they will be visible when the current locale is `PL`. Check [Locales](index.html#document-book/configuration/locales) docs for more information.

**2.** In this file configure the desired flash key and give it a translation.

If you would like to change the flash message while updating a Taxon, you will need to configure the flash under

the `sylius.taxon.update` key:

```

sylius:

 taxon:

 update: This category has been successfully edited.

```

Before

![_images/flash_before_customization.png](_images/flash_before_customization.png)

After

![_images/flash_after_customization.png](_images/flash_after_customization.png)

###### Good to know[¶](#good-to-know "Permalink to this headline")

See also

All the customizations can be done either in your application directly or in [Plugins](index.html#document-book/plugins/index)!

#### Customizing State Machines[¶](#customizing-state-machines "Permalink to this headline")

Warning

Not familiar with the State Machine concept? Read the docs [here](index.html#document-book/architecture/state_machine)!

Note

**Customizing logic via State Machines vs. Events**

The logic in which Sylius operates can be customized in two ways. First of them is using the state machines: what is

really useful when you need to modify business logic for instance modify the flow of the checkout,

and the second is listening on the kernel events related to the entities, which is helpful for modifying the HTTP responses

visible directly to the user, like displaying notifications, sending emails.

##### How to customize a State Machine?[¶](#how-to-customize-a-state-machine "Permalink to this headline")

Tip

You can browse the full implementation of these examples on [this GitHub Pull Request.](https://github.com/Sylius/Customizations/pull/20)

###### How to add a new state?[¶](#how-to-add-a-new-state "Permalink to this headline")

Let’s assume that you would like to add a new **state** to [the Order state machine](index.html#document-book/orders/orders).

You will need to add these few lines to the `config/packages/\_sylius.yaml`:

```

# config/packages/\_sylius.yaml

winzou\_state\_machine:

 sylius\_order:

 states:

 new\_custom\_state: ~ # here name your state as you wish

```

After that your new step will be available alongside other steps that already were defined in that state machine.

Tip

Run `php bin/console debug:winzou:state-machine sylius\_order`

to check if the state machine has changed to your implementation.

###### How to add a new transition?[¶](#how-to-add-a-new-transition "Permalink to this headline")

Let’s assume that you would like to add a new **transition** to [the Order state machine](index.html#document-book/orders/orders),

that will allow moving from the `cancelled` state backwards to `new`. Let’s call it “restoring”.

You will need to add these few lines to the `config/packages/\_sylius.yaml`:

```

# config/packages/\_sylius.yaml

winzou\_state\_machine:

 sylius\_order:

 transitions:

 restore:

 from: [cancelled]

 to: new

```

After that your new transition will be available alongside other transitions that already were defined in that state machine.

Tip

Run `php bin/console debug:winzou:state-machine sylius\_order`

to check if the state machine has changed to your implementation.

###### How to remove a state and its transitions?[¶](#how-to-remove-a-state-and-its-transitions "Permalink to this headline")

Warning

If you are willing to remove a state or a transition you have to override **the whole states/transitions section**

of the state machine you are willing to modify. See how we do it in the [customization of the Checkout process](index.html#document-cookbook/shop/checkout).

###### How to add a new callback?[¶](#how-to-add-a-new-callback "Permalink to this headline")

Warning

From Sylius v1.11 we strongly encourage to use priorities to any callback you are adding.

If you declare too many callbacks per action, this could lead to unexpected execution behavior.

Let’s assume that you would like to add a new **callback** to [the Product Review state machine](index.html#document-book/products/product_reviews),

that will do something on an already defined transition.

You will need to add these few lines to the `config/packages/\_sylius.yaml`:

```

# config/packages/\_sylius.yaml

winzou\_state\_machine:

 sylius\_product\_review:

 callbacks:

 after:

 sylius\_update\_rating:

 # here you are choosing the transition on which the action should take place - we are using the one we have created before

 on: ["accept"]

 # use service and its method here

 do: ["@App\\ProductReview\\Mailer\\ConfirmationMailer", "sendEmail"]

 # this will be the object of an Order here

 args: ["object"]

 priority: 100

```

Tip

Declared priorities will be executed in ascending order. If you are adding callback to existing state, you can find declared states and callbacks

under path Sylius/Bundle/CoreBundle/Resources/config/app/state\_machine.

Tip

If you want to see the implementation of `ConfirmationMailer` check it on [this GitHub Pull Request.](https://github.com/Sylius/Customizations/pull/20)

After that your new callback will be available alongside other callbacks that already were defined in that state machine and will be called on the desired transition.

###### How to modify a callback?[¶](#how-to-modify-a-callback "Permalink to this headline")

If you would like to modify an existent callback of for example the state machine of ProductReviews,

so that it does not count the average rating but does something else - you need to add these few lines to the `config/packages/\_sylius.yaml`:

```

# config/packages/\_sylius.yaml

winzou\_state\_machine:

 sylius\_review:

 callbacks:

 after:

 update\_price:

 on: "accept"

 # here you can change the service and its method that is called for your own service

 do: ["@sylius.review.updater.your\_service", update]

 args: ["object"]

 priority: 100

```

###### How to disable a callback?[¶](#how-to-disable-a-callback "Permalink to this headline")

If you would like to turn off a callback of a state machine you need to set its `disabled` option to true.

On the example of the state machine of ProductReview, we can turn off the `update\_price` callback:

```

# config/packages/\_sylius.yaml

winzou\_state\_machine:

 sylius\_review:

 callbacks:

 after:

 update\_price:

 disabled: true

```

####### Good to know[¶](#good-to-know "Permalink to this headline")

See also

All the customizations can be done either in your application directly or in [Plugins](index.html#document-book/plugins/index)!

##### Learn more[¶](#learn-more "Permalink to this headline")

* [Winzou StateMachine Bundle](https://github.com/winzou/StateMachineBundle)

* [State Machine Concept](index.html#document-book/architecture/state_machine)

#### Customizing Grids[¶](#customizing-grids "Permalink to this headline")

Note

We assume that you are familiar with grids. If not check the documentation of the [GridBundle](https://github.com/Sylius/SyliusGridBundle/blob/master/docs/index.md) first.

Tip

You can browse the full implementation of these examples on [this GitHub Pull Request.](https://github.com/Sylius/Customizations/pull/19)

##### Why would you customize grids?[¶](#why-would-you-customize-grids "Permalink to this headline")

When you would like to change how the index view of an entity looks like in the administration panel,

then you have to override its grid.

* remove a field from a grid

* change a field of a grid

* reorder fields

* override an entire grid

##### How to customize grids?[¶](#how-to-customize-grids "Permalink to this headline")

Tip

One way to change anything in any grid in **Sylius** is to modify a special file in the `config/packages/` directory: `config/packages/\_sylius.yaml`.

###### How to customize fields of a grid?[¶](#how-to-customize-fields-of-a-grid "Permalink to this headline")

####### How to remove a field from a grid?[¶](#how-to-remove-a-field-from-a-grid "Permalink to this headline")

If you would like to remove a field from an existing Sylius grid, you will need to disable it in the `config/packages/\_sylius.yaml`.

Let’s imagine that we would like to hide the **title of product review** field on the `sylius\_admin\_product\_review` grid.

```

# config/packages/\_sylius.yaml

sylius\_grid:

 grids:

 sylius\_admin\_product\_review:

 fields:

 title:

 enabled: false

```

That’s all. Now the `title` field will be disabled (invisible).

####### How to modify a field of a grid?[¶](#how-to-modify-a-field-of-a-grid "Permalink to this headline")

If you would like to modify for instance a label of any field from a grid, that’s what you need to do:

```

# config/packages/\_sylius.yaml

sylius\_grid:

 grids:

 sylius\_admin\_product\_review:

 fields:

 date:

 label: "When was it added?"

```

Good practices is translate labels, look [here](index.html#document-customization/grid). how to do that

###### How to customize filters of a grid?[¶](#how-to-customize-filters-of-a-grid "Permalink to this headline")

####### How to remove a filter from a grid?[¶](#how-to-remove-a-filter-from-a-grid "Permalink to this headline")

If you would like to remove a filter from an existing Sylius grid, you will need to disable it in the `config/packages/\_sylius.yaml`.

Let’s imagine that we would like to hide the **titles filter of product reviews** on the `sylius\_admin\_product\_review` grid.

```

# config/packages/\_sylius.yaml

sylius\_grid:

 grids:

 sylius\_admin\_product\_review:

 filters:

 title:

 enabled: false

```

That’s all. Now the `title` filter will be disabled.

###### How to customize actions of a grid?[¶](#how-to-customize-actions-of-a-grid "Permalink to this headline")

####### How to remove an action from a grid?[¶](#how-to-remove-an-action-from-a-grid "Permalink to this headline")

If you would like to disable some actions in any grid, you just need to set its `enabled` option to `false` like below:

```

# config/packages/\_sylius.yaml

sylius\_grid:

 grids:

 sylius\_admin\_product\_review:

 actions:

 item:

 delete:

 type: delete

 enabled: false

```

####### How to modify an action of a grid?[¶](#how-to-modify-an-action-of-a-grid "Permalink to this headline")

If you would like to change the link to which an action button is redirecting, this is what you have to do:

Warning

The `show` button does not exist in the `sylius\_admin\_product` grid by default.

It is assumed that you already have it customized, and your grid has the `show` action.

```

# config/packages/\_sylius.yaml

sylius\_grid:

 grids:

 sylius\_admin\_product:

 actions:

 item:

 show:

 type: show

 label: Show in the shop

 options:

 link:

 route: sylius\_shop\_product\_show

 parameters:

 slug: resource.slug

```

The above grid modification will change the redirect of the `show` action to redirect to the shop, instead of admin show.

Also the label was changed here.

####### How to remove label of an action from a grid?[¶](#how-to-remove-label-of-an-action-from-a-grid "Permalink to this headline")

If you would like to remove label for some actions in any grid, you just need to set its `labeled` option to `false` like below:

```

# config/packages/\_sylius.yaml

sylius\_grid:

 grids:

 sylius\_admin\_product\_review:

 actions:

 item:

 delete:

 type: delete

 options:

 labeled: false

```

###### How to modify positions of fields, filters and actions in a grid?[¶](#how-to-modify-positions-of-fields-filters-and-actions-in-a-grid "Permalink to this headline")

For fields, filters and actions it is possible to easily change the order in which they are displayed in the grid.

See an example of fields order modification on the `sylius\_admin\_product\_review` grid below:

```

# config/packages/\_sylius.yaml

sylius\_grid:

 grids:

 sylius\_admin\_product\_review:

 fields:

 date:

 position: 5

 title:

 position: 6

 rating:

 position: 3

 status:

 position: 1

 reviewSubject:

 position: 2

 author:

 position: 4

```

##### Customizing grids by events[¶](#customizing-grids-by-events "Permalink to this headline")

There is also another way to customize grids: **via events**.

Every grid configuration dispatches an event when its definition is being converted.

For example, **sylius\_admin\_product** grid dispatches such an event:

```

sylius.grid.admin\_product # For the grid of products in admin

```

To show you an example of a grid customization using events, we will modify fields from a grid using that method.

Here are the steps, that you need to take:

**1.** In order to modify fields from the product grid in **Sylius** you have to create a `App\Grid\AdminProductsGridListener` class.

In the example below we are removing the `image` field and adding the `code` field to the `sylius\_admin\_product` grid.

```

<?php

namespace App\Grid;

use Sylius\Component\Grid\Event\GridDefinitionConverterEvent;

use Sylius\Component\Grid\Definition\Field;

final class AdminProductsGridListener

{

    public function editFields(GridDefinitionConverterEvent $event): void

    {

        $grid = $event->getGrid();

        // Remove

        $grid->removeField('image');

        // Add

        $codeField = Field::fromNameAndType('code', 'string');

        $codeField->setLabel('Code');

        // ...

        $grid->addField($codeField);

    }

}

```

**2.** After creating your class with a proper method for the grid customizations you need, subscribe your

listener to the `sylius.grid.admin\_product` event in the `config/services.yaml`.

```

# config/services.yaml

services:

 App\Grid\AdminProductsGridListener:

 tags:

 - { name: kernel.event\_listener, event: sylius.grid.admin\_product, method: editFields }

```

**3.** Result:

After these two steps your admin product grid should not have the image field.

###### Good to know[¶](#good-to-know "Permalink to this headline")

See also

All the customizations can be done either in your application directly or in [Plugins](index.html#document-book/plugins/index)!

##### Changes related to upgrade to GridBundle 1.10[¶](#changes-related-to-upgrade-to-gridbundle-1-10 "Permalink to this headline")

Since the SyliusGridBundle v1.10, all grids has options fetch\_join\_collection and use\_output\_walkers enabled by default.

According to our research it may fix a lot of pagination issues and improve big-database performance (1M+ rows) up to 70%,

but with the price of more queries required to be performed. If this trade-off is not worth it for you,

you may disable it by using the following configuration snippet:

```

# config/packages/\_sylius.yaml

sylius\_grid:

 grids:

 sylius\_admin\_address\_log\_entry:

 driver:

 name: doctrine/orm

 options:

 pagination:

 fetch\_join\_collection: false

 use\_output\_walkers: false

 sylius\_admin\_admin\_user:

 driver:

 name: doctrine/orm

 options:

 pagination:

 fetch\_join\_collection: false

 use\_output\_walkers: false

 sylius\_admin\_channel:

 driver:

 name: doctrine/orm

 options:

 pagination:

 fetch\_join\_collection: false

 use\_output\_walkers: false

 sylius\_admin\_country:

 driver:

 name: doctrine/orm

 options:

 pagination:

 fetch\_join\_collection: false

 use\_output\_walkers: false

 sylius\_admin\_currency:

 driver:

 name: doctrine/orm

 options:

 pagination:

 fetch\_join\_collection: false

 use\_output\_walkers: false

 sylius\_admin\_customer:

 driver:

 name: doctrine/orm

 options:

 pagination:

 fetch\_join\_collection: false

 use\_output\_walkers: false

 sylius\_admin\_customer\_group:

 driver:

 name: doctrine/orm

 options:

 pagination:

 fetch\_join\_collection: false

 use\_output\_walkers: false

 sylius\_admin\_customer\_order:

 extends: sylius\_admin\_order

 driver:

 options:

 pagination:

 fetch\_join\_collection: false

 use\_output\_walkers: false

 sylius\_admin\_exchange\_rate:

 driver:

 name: doctrine/orm

 options:

 pagination:

 fetch\_join\_collection: false

 use\_output\_walkers: false

 sylius\_admin\_inventory:

 driver:

 name: doctrine/orm

 options:

 pagination:

 fetch\_join\_collection: false

 use\_output\_walkers: false

 sylius\_admin\_locale:

 driver:

 name: doctrine/orm

 options:

 pagination:

 fetch\_join\_collection: false

 use\_output\_walkers: false

 sylius\_admin\_order:

 driver:

 name: doctrine/orm

 options:

 pagination:

 fetch\_join\_collection: false

 use\_output\_walkers: false

 sylius\_admin\_payment:

 driver:

 name: doctrine/orm

 options:

 pagination:

 fetch\_join\_collection: false

 use\_output\_walkers: false

 sylius\_admin\_payment\_method:

 driver:

 name: doctrine/orm

 options:

 pagination:

 fetch\_join\_collection: false

 use\_output\_walkers: false

 sylius\_admin\_product:

 driver:

 name: doctrine/orm

 options:

 pagination:

 fetch\_join\_collection: false

 use\_output\_walkers: false

 sylius\_admin\_product\_association\_type:

 driver:

 name: doctrine/orm

 options:

 pagination:

 fetch\_join\_collection: false

 use\_output\_walkers: false

 sylius\_admin\_product\_attribute:

 driver:

 name: doctrine/orm

 options:

 pagination:

 fetch\_join\_collection: false

 use\_output\_walkers: false

 sylius\_admin\_product\_option:

 driver:

 name: doctrine/orm

 options:

 pagination:

 fetch\_join\_collection: false

 use\_output\_walkers: false

 sylius\_admin\_product\_review:

 driver:

 name: doctrine/orm

 options:

 pagination:

 fetch\_join\_collection: false

 use\_output\_walkers: false

 sylius\_admin\_product\_variant:

 driver:

 name: doctrine/orm

 options:

 pagination:

 fetch\_join\_collection: false

 use\_output\_walkers: false

 sylius\_admin\_promotion:

 driver:

 name: doctrine/orm

 options:

 pagination:

 fetch\_join\_collection: false

 use\_output\_walkers: false

 sylius\_admin\_promotion\_coupon:

 driver:

 name: doctrine/orm

 options:

 pagination:

 fetch\_join\_collection: false

 use\_output\_walkers: false

 sylius\_admin\_shipment:

 driver:

 name: doctrine/orm

 options:

 pagination:

 fetch\_join\_collection: false

 use\_output\_walkers: false

 sylius\_admin\_shipping\_category:

 driver:

 name: doctrine/orm

 options:

 pagination:

 fetch\_join\_collection: false

 use\_output\_walkers: false

 sylius\_admin\_shipping\_method:

 driver:

 name: doctrine/orm

 options:

 pagination:

 fetch\_join\_collection: false

 use\_output\_walkers: false

 sylius\_admin\_tax\_category:

 driver:

 name: doctrine/orm

 options:

 pagination:

 fetch\_join\_collection: false

 use\_output\_walkers: false

 sylius\_admin\_tax\_rate:

 driver:

 name: doctrine/orm

 options:

 pagination:

 fetch\_join\_collection: false

 use\_output\_walkers: false

 sylius\_admin\_taxon:

 driver:

 name: doctrine/orm

 options:

 pagination:

 fetch\_join\_collection: false

 use\_output\_walkers: false

 sylius\_admin\_zone:

 driver:

 name: doctrine/orm

 options:

 pagination:

 fetch\_join\_collection: false

 use\_output\_walkers: false

 sylius\_shop\_account\_order:

 driver:

 name: doctrine/orm

 options:

 pagination:

 fetch\_join\_collection: false

 use\_output\_walkers: false

 sylius\_shop\_product:

 driver:

 name: doctrine/orm

 options:

 pagination:

 fetch\_join\_collection: false

 use\_output\_walkers: false

```

##### Learn more[¶](#learn-more "Permalink to this headline")

* [GridBundle documentation](https://github.com/Sylius/SyliusGridBundle/blob/master/docs/index.md)

#### Customizing Fixtures[¶](#customizing-fixtures "Permalink to this headline")

##### What are fixtures?[¶](#what-are-fixtures "Permalink to this headline")

Fixtures are just plain old PHP objects, that change system state during their execution - they can either

persist entities in the database, upload files, dispatch events or do anything you think is needed.

```

sylius\_fixtures:

 suites:

 my\_suite\_name:

 fixtures:

 my\_fixture: # Fixture name as a key

 priority: 0 # The higher priority is, the sooner the fixture will be executed

 options: ~ # Fixture options

```

They implement the `Sylius\Bundle\FixturesBundle\Fixture\FixtureInterface` and need to be registered under

the `sylius\_fixtures.fixture` tag in order to be used in suite configuration.

Note

The former interface extends the `ConfigurationInterface`, which is widely known from `Configuration` classes

placed under `DependencyInjection` directory in Symfony bundles.

##### Why would you customize fixtures?[¶](#why-would-you-customize-fixtures "Permalink to this headline")

There are two main use cases for customizing fixture suites, in each of them you can adapt the data of your shop to be realistic,

the default fixtures suite of Sylius is selling clothes, if you are selling food you’d probably need your own fixtures to show that:

> 

> * preparing test data for the development purposes like demo applications prepared for QA

> * preparing the shop configuration for the production instance

> 

> 

> 

##### How to modify the existing Sylius fixtures?[¶](#how-to-modify-the-existing-sylius-fixtures "Permalink to this headline")

In Sylius, fixtures are configured in `src/Sylius/Bundle/CoreBundle/Resources/config/app/fixtures.yml`.

It includes the `default` suite that is partially-configured.

If you are planning to modify the default fixtures applied by the `sylius:fixtures:load` command, modify the `config\packages\sylius\_fixtures.yaml` file.

###### Modifying the shop configuration (channels, currencies, payment and shipping methods)[¶](#modifying-the-shop-configuration-channels-currencies-payment-and-shipping-methods "Permalink to this headline")

```

sylius\_fixtures:

 suites:

 default: # this key is always called whenever the sylius:fixtures:load command is called, below we are extending it with new fixtures

 fixtures:

 currency:

 options:

 currencies: ['PLN','HUF']

 channel:

 options:

 custom:

 pl\_web\_store: # creating new channel

 name: "PL Web Store"

 code: "PL\_WEB"

 locales:

 - "%locale%"

 currencies:

 - "PLN"

 enabled: true

 hostname: "localhost"

 hun\_web\_store:

 name: "Hun Web Store"

 code: "HUN\_WEB"

 locales:

 - "%locale%"

 currencies:

 - "HUF"

 enabled: true

 hostname: "localhost"

 shipping\_method:

 options:

 custom:

 ups\_eu: # creating a new shipping\_method and adding channel to it

 code: "ups\_eu"

 name: "UPS\_eu"

 enabled: true

 channels:

 - "PL\_WEB"

 - "HUN\_WEB"

 payment\_method:

 options:

 custom:

 cash\_on\_delivery\_pl:

 code: "cash\_on\_delivery\_eu"

 name: "Cash on delivery\_eu"

 channels:

 - "PL\_WEB"

 bank\_transfer:

 code: "bank\_transfer\_eu"

 name: "Bank transfer\_eu"

 channels:

 - "PL\_WEB"

 - "HUN\_WEB"

 enabled: true

```

It is more complicated to create fixtures for products, because they have more dependencies (to Variants, Options etc.). In order to prepare a Product

you have to create not only the product itself but other related entities via their own factories.

Sylius delivers four ready implementations of `Product` fixtures, that have their relevant options (like sizes for T-shirts):

* `BookProductFixture`

* `MugProductFixture`

* `StickerProductFixture`

* `TshirtProductFixture`

You can modify their YAML fixture configs, but only within the capabilities delivered by those fixtures classes.

##### How to customize fixtures for customized models?[¶](#how-to-customize-fixtures-for-customized-models "Permalink to this headline")

Tip

The following example is based on [other example of extending an entity with a new field](https://github.com/Sylius/Customizations/pull/7).

You can browse the full implementation of this example on [this GitHub Pull Request](https://github.com/Sylius/Customizations/pull/23).

Let’s suppose you have extended `App\Entity\Shipping\ShippingMethod` with a new field `deliveryConditions`,

just like in the example mentioned above.

**1.** To cover that in fixtures, you will need to override the `ShippingMethodExampleFactory` and add this field:

```

<?php

// src/Fixture/Factory/ShippingMethodExampleFactory.php

namespace App\Fixture\Factory;

// ...

use Sylius\Bundle\CoreBundle\Fixture\Factory\ShippingMethodExampleFactory as BaseShippingMethodExampleFactory;

final class ShippingMethodExampleFactory extends BaseShippingMethodExampleFactory

{

    //...

    public function create(array $options = []): ShippingMethodInterface

    {

        /\*\* @var ShippingMethod $shippingMethod \*/

        $shippingMethod = parent::create($options);

        // Protect object if part of our objects don't have new field

        if (!isset($options['deliveryConditions'])) {

            return $shippingMethod;

        }

        foreach ($this->getLocales() as $localeCode) {

            $shippingMethod->setCurrentLocale($localeCode);

            $shippingMethod->setFallbackLocale($localeCode);

            $shippingMethod->setDeliveryConditions($options['deliveryConditions']);

        }

        return $shippingMethod;

    }

    protected function configureOptions(OptionsResolver $resolver): void

    {

        parent::configureOptions($resolver);

        $resolver

            ->setDefault('deliveryConditions', 'some\_default\_value')

            ->setAllowedTypes('deliveryConditions', ['null', 'string'])

        ;

    }

    private function getLocales(): iterable

    {

        /\*\* @var LocaleInterface[] $locales \*/

        $locales = $this->localeRepository->findAll();

        foreach ($locales as $locale) {

            yield $locale->getCode();

        }

    }

}

```

**2.** Extend the `Sylius\Bundle\CoreBundle\Fixture\ShippingMethodFixture` in `App\Entity\Fixture\ShippingMethodFixture`:

```

<?php

// src/Fixture/ShippingMethodFixture.php

namespace App\Fixture;

use Sylius\Bundle\CoreBundle\Fixture\ShippingMethodFixture as BaseShippingMethodFixture;

use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;

final class ShippingMethodFixture extends BaseShippingMethodFixture

{

    protected function configureResourceNode(ArrayNodeDefinition $resourceNode): void

    {

        parent::configureResourceNode($resourceNode);

        $resourceNode

            ->children()

                ->scalarNode('deliveryConditions')->end()

        ;

    }

}

```

**3.** Configure the services in the `config/services.yaml` file:

```

sylius.fixture.example\_factory.shipping\_method:

 class: App\Fixture\Factory\ShippingMethodExampleFactory

 arguments:

 - "@sylius.factory.shipping\_method"

 - "@sylius.repository.zone"

 - "@sylius.repository.shipping\_category"

 - "@sylius.repository.locale"

 - "@sylius.repository.channel"

 - "@sylius.repository.tax\_category"

 public: true

sylius.fixture.shipping\_method:

 class: App\Fixture\ShippingMethodFixture

 arguments:

 - "@sylius.manager.shipping\_method"

 - "@sylius.fixture.example\_factory.shipping\_method"

 tags:

 - { name: sylius\_fixtures.fixture }

```

Tip

When creating fixtures services manually, remember to turn off autowiring for them:

```

App\:

 resource: '../src/\*'

 exclude: '../src/{Entity,Fixture,Migrations,Tests,Kernel.php}'

```

If you leave autowiring on, errors like Fixture with name “your\_custom\_fixture” is already registered. will most probably appear. Your fixture service will register twice (as app.fixture.bla by you and as AppFixtureBlaFixture by DI autoconfigure).

**4.** Add new Shipping Methods with delivery conditions in `config/packages/fixtures.yaml`:

```

sylius\_fixtures:

 suites:

 default:

 fixtures:

 # ...

 shipping\_method: # our new configuration with the new field

 options:

 custom:

 geis:

 code: "geis"

 name: "Geis"

 enabled: true

 channels:

 - "PL\_WEB"

 deliveryConditions: "3-5 days"

```

##### Learn more[¶](#learn-more "Permalink to this headline")

* [The Book: Fixtures](index.html#document-book/architecture/fixtures)

* [FixturesBundle](https://github.com/Sylius/SyliusFixturesBundle/blob/master/docs/index.md)

#### Customizing Fixture Suites[¶](#customizing-fixture-suites "Permalink to this headline")

##### What are fixture suites?[¶](#what-are-fixture-suites "Permalink to this headline")

Suites are predefined groups of fixtures that can be run together. For example, they can be full shop configurations for manual tests purposes.

##### Why would you customize fixture suites?[¶](#why-would-you-customize-fixture-suites "Permalink to this headline")

> 

> * tailoring the default Sylius fixture suite to your needs (removing Orders for example)

> * creating your own fixture suite

> 

> 

> 

##### How to use suites?[¶](#how-to-use-suites "Permalink to this headline")

Complete list of suites can be shown with the `bin/console sylius:fixtures:list` command.

The `default` suite is loaded if `bin/console sylius:fixtures:load` command

is executed without any additional argument. If you are creating a new suite you must use this command providing the

name of your suite as an argument: `bin/console sylius:fixtures:load your\_custom\_suite`.

##### How to create custom fixture suites?[¶](#how-to-create-custom-fixture-suites "Permalink to this headline")

Tip

You can browse the full implementation of this example on [this GitHub Pull Request](https://github.com/Sylius/Customizations/pull/24).

Tip

If you want to create your fixtures with different locale than `en\_US` you must change the `locale` parameter in `config/services.yaml`.

```

parameters:

 locale: pl\_PL

```

**1.** Create the `config/packages/sylius\_fixtures.yaml` file and add the following code there:

```

sylius\_fixtures:

 suites:

 poland: # custom suite's name

 fixtures:

 currency:

 options:

 currencies: ['PLN'] # add desired currencies as an array

 geographical: # Countries, provinces and zones available in your store

 options:

 countries:

 - "PL"

 zones:

 PL:

 name: "Poland"

 countries:

 - "PL"

 channel:

 options:

 custom:

 pl\_web\_store:

 name: "PL Web Store"

 code: "PL\_WEB"

 locales: # choose the locale for this channel

 - "%locale%"

 currencies: # choose currencies for this channel

 - "PLN"

 enabled: true

 hostname: "localhost"

 shipping\_method: # create shipping methods and choose channels in which it is available

 options:

 custom:

 inpost:

 code: "inpost"

 name: "InPost"

 channels:

 - "PL\_WEB"

 zone: "PL"

```

**2.** Load your custom suite with `bin/console sylius:fixtures:load poland` command.

Tip

By default, a new fixture suite will not purge your database. If you want to run it always on a clear database,

add the `orm\_purger` listener under your custom suite name:

```

sylius\_fixtures:

 suites:

 poland:

 listeners:

 orm\_purger: ~

```

###### Learn more[¶](#learn-more "Permalink to this headline")

* [The Book: Fixtures](index.html#document-book/architecture/fixtures)

* [FixturesBundle](https://github.com/Sylius/SyliusFixturesBundle/blob/master/docs/index.md)

#### Customizing API[¶](#customizing-api "Permalink to this headline")

We are using the API Platform to create all endpoints in Sylius API.

API Platform allows configuring an endpoint by `yaml` and `xml` files or by annotations.

In this guide, you will learn how to customize Sylius API endpoints using `xml` configuration.

##### Introduction[¶](#introduction "Permalink to this headline")

##### How to prepare project for customization?[¶](#how-to-prepare-project-for-customization "Permalink to this headline")

If your project was created before v1.10, make sure your API Platform config follows the one below:

```

# config/packages/api\_platform.yaml

api\_platform:

 mapping:

 paths:

 - '%kernel.project\_dir%/vendor/sylius/sylius/src/Sylius/Bundle/ApiBundle/Resources/config/api\_resources'

 - '%kernel.project\_dir%/config/api\_platform'

 - '%kernel.project\_dir%/src/Entity'

 patch\_formats:

 json: ['application/merge-patch+json']

 swagger:

 versions: [3]

```

Also, if you’re planning to modify serialization add this code to framework config:

```

# config/packages/framework.yaml

#...

serializer:

 mapping:

 paths: [ '%kernel.project\_dir%/config/serialization' ]

```

###### How to add an additional endpoint?[¶](#how-to-add-an-additional-endpoint "Permalink to this headline")

Let’s assume that you want to add a new endpoint to the `Order` resource that will be dispatching a command.

If you want to customize any API resource, you need to copy the entire configuration of this resource from

`%kernel.project\_dir%/vendor/sylius/sylius/src/Sylius/Bundle/ApiBundle/Resources/config/api\_resources/` to `%kernel.project\_dir%/config/api\_platform`.

Add the following configuration in the config copied to `config/api\_platform/Order.xml`:

```

<collectionOperations>

 <collectionOperation name="custom\_operation">

 <attribute name="method">POST</attribute>

 <attribute name="path">/shop/orders/custom-operation</attribute>

 <attribute name="messenger">input</attribute>

 <attribute name="input">App\Command\CustomCommand</attribute>

 </collectionOperation>

</collectionOperations>

```

And that’s all, now you have a new endpoint with your custom logic.

Tip

Read more about API Platform endpoint configuration [here](https://api-platform.com/docs/core/operations/)

###### How to remove an endpoint?[¶](#how-to-remove-an-endpoint "Permalink to this headline")

Let’s assume that your shop is offering only digital products. Therefore, while checking out,

your customers do not need to choose a shipping method for their orders.

Thus you will need to modify the configuration file of the `Order` resource and remove the shipping method choosing endpoint from it.

To remove the endpoint you only need to delete the unnecessary configuration from your `config/api\_platform/Order.xml` which is a copied configuration file, that overwrites the one from Sylius.

```

<!-- delete this configuration -->

<itemOperation name="shop\_select\_shipping\_method">

 <!-- ... -->

</itemOperation>

```

###### How to rename an endpoint’s path?[¶](#how-to-rename-an-endpoint-s-path "Permalink to this headline")

If you want to change an endpoint’s path, you just need to change the `path` attribute in your config:

```

<itemOperations>

 <itemOperation name="admin\_get">

 <attribute name="method">GET</attribute>

 <attribute name="path">/admin/orders/renamed-path/{id}</attribute>

 </itemOperation>

</itemOperations>

```

###### How to modify the endpoints prefixes?[¶](#how-to-modify-the-endpoints-prefixes "Permalink to this headline")

Let’s assume that you want to have your own prefixes on paths (for example to be more consistent with the rest of your application).

As the first step you need to change the `paths` or `route\_prefix` attribute in all needed resources.

The next step is to modify the security configuration in `config/packages/security.yaml`, you need to overwrite the parameter:

```

parameters:

 sylius.security.new\_api\_shop\_route: "%sylius.security.new\_api\_route%/retail"

```

Warning

Changing prefix without security configuration update can expose confidential data (like customers addresses).

After these two steps you can start to use endpoints with new prefixes.

###### How to customize serialization?[¶](#how-to-customize-serialization "Permalink to this headline")

Let’s say that you want to change the serialized fields in your responses.

For an example we will use `Product` resource and customize its fields.

####### Adding a field to response[¶](#adding-a-field-to-response "Permalink to this headline")

Let’s say that you want to serialize the existing field named `averageRating` to `Product` in the admin response

so the administrator would be able to check what is the average rating of product.

First let’s create serialization configuration file named `Product.xml` in `config/serialization/Product.xml`

and add serialization group that is used by endpoint we want to modify, in this case the new `group` is called `admin:product:read`:

```

<?xml version="1.0" ?>

<serializer xmlns="http://symfony.com/schema/dic/serializer-mapping"

 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

 xsi:schemaLocation="http://symfony.com/schema/dic/serializer-mapping https://symfony.com/schema/dic/serializer-mapping/serializer-mapping-1.0.xsd"

>

 <class name="Sylius\Component\Core\Model\Product">

 <attribute name="averageRating">

 <group>admin:product:read</group>

 <group>shop:product:read</group>

 </attribute>

 </class>

</serializer>

```

Tip

You can create your own serialization group for every endpoint or use the one out of the box.

If you don’t know the name of group for endpoint you want to modify, you can find it by searching

for your class configuration file in %kernel.project\_dir%/vendor/sylius/sylius/src/Sylius/Bundle/ApiBundle/Resources/config/api\_resources`

and look for path that you want to modify.

Tip

The serialization groups from Sylius look this way to reflect: `user context`, `resource name` and `type of operation`.

After this change your response should be extended with new field:

```

{

 //...

 "id": 123,

 "code": "product\_code",

 "variants": [

 "/api/v2/shop/product-variants/product-variant-0",

 ],

 "averageRating": 3,

 //...

}

```

Tip

Read more about API Platform [serialization groups](https://api-platform.com/docs/core/serialization/#using-serialization-groups)

We were able to add a field that exists in `Product` class, but what if you want to extend it with custom fields?

Let’s customize response now with your custom fields serialized in response.

####### Adding a custom field to response[¶](#adding-a-custom-field-to-response "Permalink to this headline")

Let’s say that you want to add a new field named `additionalText` to `Customer`.

First we need to create a new serializer that will support this resource. Let’s name it `CustomerNormalizer`:

```

<?php

declare(strict\_types=1);

namespace App\Serializer;

use Sylius\Component\Core\Model\CustomerInterface;

use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;

use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;

use Symfony\Component\Serializer\Normalizer\NormalizerInterface;

use Webmozart\Assert\Assert;

final class CustomerNormalizer implements ContextAwareNormalizerInterface, NormalizerAwareInterface

{

    private NormalizerInterface $normalizer;

    public function \_\_construct(NormalizerInterface $normalizer)

    {

        $this->normalizer = $normalizer;

    }

    private const ALREADY\_CALLED = 'customer\_normalizer\_already\_called';

    public function normalize($object, $format = null, array $context = [])

    {

        Assert::isInstanceOf($object, CustomerInterface::class);

        Assert::keyNotExists($context, self::ALREADY\_CALLED);

        $context[self::ALREADY\_CALLED] = true;

        $data = $this->normalizer->normalize($object, $format, $context);

        return $data;

    }

    public function supportsNormalization($data, $format = null, $context = []): bool

    {

        if (isset($context[self::ALREADY\_CALLED])) {

            return false;

        }

        return $data instanceof CustomerInterface;

    }

}

```

And now let’s declare its service in config files:

```

# config/services.yaml

App\Serializer\CustomerNormalizer:

 arguments:

 - '@api\_platform.serializer.normalizer.item'

 tags:

 - { name: 'serializer.normalizer', priority: 100 }

```

Then we can add the new field:

```

//...

$data = $this->normalizer->normalize($object, $format, $context);

$data['additionalText'] = 'your custom text or logic that will be added to this field.';

return $data;

//...

```

Now your response should be extended with the new field:

```

{

 //...

 "id": 123,

 "email": "sylius@example.com",

 "firstName": "sylius",

 "additionalText": "my additional field with text",

 //...

}

```

But let’s consider another case where the Normalizer exists for a given Resource.

Here we will also add a new field named `additionalText` but this time to `Product`.

First, we need to create a serializer that will support our `Product` resource but in this case, we have a `ProductNormalizer` provided by Sylius.

Unfortunately, we cannot use more than one normalizer per resource, hence we will override the existing one.

Let’s then copy the code of ProductNormalizer from `vendor/sylius/sylius/src/Sylius/Bundle/ApiBundle/Serializer/ProductNormalizer.php` :

```

<?php

declare(strict\_types=1);

namespace App\Serializer;

use Sylius\Component\Core\Model\ProductInterface;

use Sylius\Component\Product\Resolver\ProductVariantResolverInterface;

use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;

use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;

use Symfony\Component\Serializer\Normalizer\NormalizerInterface;

use Webmozart\Assert\Assert;

final class ProductNormalizer implements ContextAwareNormalizerInterface, NormalizerAwareInterface

{

    private NormalizerInterface $normalizer;

    private ProductVariantResolverInterface $productVariantResolver;

    public function \_\_construct(

        NormalizerInterface $normalizer,

        ProductVariantResolverInterface $productVariantResolver

    ) {

        $this->normalizer = $normalizer;

        $this->productVariantResolver = $productVariantResolver;

    }

    private const ALREADY\_CALLED = 'product\_normalizer\_already\_called';

    public function normalize($object, $format = null, array $context = [])

    {

        Assert::isInstanceOf($object, ProductInterface::class);

        Assert::keyNotExists($context, self::ALREADY\_CALLED);

        $context[self::ALREADY\_CALLED] = true;

        $data = $this->normalizer->normalize($object, $format, $context);

        $variant = $this->productVariantResolver->getVariant($object);

        $data['defaultVariant'] = $variant === null ? null : $this->iriConverter->getIriFromItem($variant);

        return $data;

    }

    public function supportsNormalization($data, $format = null, $context = []): bool

    {

        if (isset($context[self::ALREADY\_CALLED])) {

            return false;

        }

        return $data instanceof ProductInterface;

    }

}

```

And now let’s declare its service in config files:

```

# config/services.yaml

App\Serializer\ProductNormalizer:

 arguments:

 - '@api\_platform.serializer.normalizer.item'

 - '@sylius.product\_variant\_resolver.default'

 tags:

 - { name: 'serializer.normalizer', priority: 100 }

```

Warning

As we can use only one Normalizer per resource we need to set priority for it, higher then the priority of the Sylius one.

You can find the priority value of the Sylius Normalizer in `src/Sylius/Bundle/ApiBundle/Resources/config/services/serializers.xml`

Then we can add the new field:

```

//...

$data = $this->normalizer->normalize($object, $format, $context);

$data['additionalText'] = 'your custom text or logic that will be added to this field.';

return $data;

//...

```

And your response should be extended with the new field:

```

{

 //...

 "id": 123,

 "code": "product\_code",

 "variants": [

 "/api/v2/shop/product-variants/product-variant-0",

 ],

 "additionalText": "my additional field with text",

 //...

}

```

####### Removing a field from a response[¶](#removing-a-field-from-a-response "Permalink to this headline")

Let’s say that for some reason you want to remove a field from serialization.

One possible solution could be that you use serialization groups.

Those will limit the fields from your resource, according to serialization groups that you will choose.

Tip

Read more about API Platform [serialization groups](https://api-platform.com/docs/core/serialization/#using-serialization-groups)

Let’s assume that `Product` resource returns such a response:

```

{

 //...

 "id": 123,

 "code": "product\_code",

 "variants": [

 "/api/v2/shop/product-variants/product-variant-0",

 ],

 "translations": {

 "en\_US": {

 "@id": "/api/v2/shop/product-translations/123",

 "@type": "ProductTranslation",

 "id": 123,

 "name": "product name",

 "slug": "product-name"

 }

}

```

Then let’s say you want to remove `translations`.

Utilising serialization groups to remove fields might be quite tricky as Symfony combines all of the serialization files into one.

The easiest solution to remove the field is to create a new serialization group, use it for the fields you want to have, and declare this group in the endpoint.

First, let’s add the `config/api\_platform/Product.xml` configuration file. See [How to add and remove endpoint](index.html#document-customization/api/adding_and_removing_endpoints) for more information.

Then let’s modify the endpoint. For this example, we will use GET item in the shop, but you can also create some custom endpoint:

```

<!--...-->

<itemOperation name="shop\_get">

 <attribute name="method">GET</attribute>

 <attribute name="path">/shop/products/{code}</attribute>

 <attribute name="openapi\_context">

 <attribute name="summary">Use code to retrieve a product resource.</attribute>

 </attribute>

 <attribute name="normalization\_context">

 <attribute name="groups">shop:product:read</attribute>

 </attribute>

</itemOperation>

<!--...-->

```

then let’s change the serialization group in `normalization\_context` attribute to shop:product:custom\_read:

```

<!--...-->

<attribute name="normalization\_context">

 <attribute name="groups">shop:product:custom_read</attribute>

</attribute>

<!--...-->

```

Now we can define all the fields we want to expose in the `config/serialization/Product.xml`:

```

<!--...-->

<attribute name="updatedAt">

 <group>shop:product:custom_read</group>

</attribute>

<!-- here `translation` attribute would be declared -->

<attribute name="mainTaxon">

 <group>shop:product:custom_read</group>

</attribute>

<!--...-->

```

Note

In xml example the `translations` is not declared with `<group>shop:product:custom\_read</group>` group, so endpoint won’t return this value.

The rest of the fields that we want to show have the new serialization group declared.

In cases, where you would like to remove small amount of fields, the serializer would be a way to go.

First step is to create a class as in `Adding a custom field to response` and register its service.

Then modify it’s logic with this code:

```

//...

$data = $this->normalizer->normalize($object, $format, $context);

unset($data['translations']); // removes `translations` from response

return $data;

//...

```

Now your response fields should look like this:

```

{

 //...

 "id": 123,

 "code": "product\_code",

 "variants": [

 "/api/v2/shop/product-variants/product-variant-0",

 ],

 // the translations which were here are now removed

}

```

####### Renaming a field of a response[¶](#renaming-a-field-of-a-response "Permalink to this headline")

Changing the name of response fields is very simple. In this example

let’s modify the `options` name to `optionValues`, that’s how response looks like now:

```

{

 //...

 "id": 123,

 "code": "product\_code",

 "product": "/api/v2/shop/products/product\_code",

 "options": [

 "/api/v2/shop/product-option-values/product\_size\_s"

 ],

 //...

}

```

The simplest method to achieve this is to modify the serialization configuration file that we’ve already created.

Let’s add to the `config/serialization/Product.xml` file config for `options` with a `serialized-name` attribute description:

```

<!--...-->

<attribute name="options">

 <group>admin:product:read</group>

 <group>shop:product:read</group>

</attribute>

<!--...-->

```

And just add a `serialized-name` into the attribute description with a new name:

```

<!--...-->

<attribute name="options" serialized-name="optionValues">

 <group>admin:product:read</group>

 <group>shop:product:read</group>

</attribute>

<!--...-->

```

You can also achieve this by utilising serializer class.

In this example we will modify it, so the name of field would be changed. Just add some custom logic:

```

//...

$data = $this->normalizer->normalize($object, $format, $context);

$data['optionValues'] = $data['options']; // this will change the name of your field

unset($data['options']); // optionally you can also remove old `options` field

return $data;

//...

```

And here we go, now your response should look like this:

```

{

 //...

 "id": 123,

 "code": "product\_code",

 "product": "/api/v2/shop/products/product\_code",

 "optionValues": [

 "/api/v2/shop/product-option-values/product\_size\_s"

 ],

 //...

}

```

###### Configuring endpoints using yaml[¶](#configuring-endpoints-using-yaml "Permalink to this headline")

To remove an endpoint from the API using YAML you need to specify the operation of which resource

should be removed in `config/api\_platform/config.yaml`.

If you want to remove, for example, the admin `GET` endpoint of `Zones`, you need to configure the `enabled` key in its yaml config.

```

'%sylius.model.zone.class%':

 collectionOperations:

 admin\_get:

 enabled: false

```

Using the `enabled` key you can also remove filters you don’t need.

```

'%sylius.model.product.class%':

 collectionOperations:

 shop\_get:

 filters:

 enabled: false

```

If you need to add a new filter in the yaml configuration, simply add this kind of code to `config/api\_platform/config.yaml`.

```

'%sylius.model.product.class%':

 collectionOperations:

 shop\_get:

 filters:

 - app.product\_new\_filter

```

To add a new operation, just specify it in the config file.

```

'%sylius.model.channel.class%':

 collectionOperations:

 my\_new\_operation:

 method: GET

 path: /shop/channels

 normalization\_context:

 groups: ['shop:channel:read']

```

You can also overwrite existing endpoints, for example let’s change admin\_get operation in order collectionOperations.

```

'%sylius.model.order.class%':

 collectionOperations:

 admin\_get:

 path: /admin/orders/new\_endpoint

 normalization\_context:

 groups: ['shop:channel:new\_group']

```

This way we can edit the existing endpoint and add custom normalization or change path.

Warning

By removing subresource operations, API Platform creates its own endpoints to a given subresource without our firewall.

To disable endpoint completely we need to overwrite xml configuration.

To remove subresource from Sylius, create new xml file with copied resource class and remove its subresource operations, for example Country.

```

<resources xmlns="https://api-platform.com/schema/metadata"

 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

 xsi:schemaLocation="https://api-platform.com/schema/metadata https://api-platform.com/schema/metadata/metadata-2.0.xsd"

>

 <resource class="%sylius.model.country.class%" shortName="Country">

 ...

 - <subresourceOperations>

 - <subresourceOperation name="provinces\_get\_subresource">

 - <attribute name="method">GET</attribute>

 - <attribute name="path">/admin/countries/{code}/provinces</attribute>

 - </subresourceOperation>

 - </subresourceOperations>

 ...

 </resource>

</resources>

```

* [How to add an additional endpoint?](index.html#document-customization/api/adding_and_removing_endpoints)

* [How to rename an endpoint’s path?](index.html#document-customization/api/modifying_endpoints)

* [How to customize serialization?](index.html#document-customization/api/serialization_customization)

* [Configuring endpoints using yaml](index.html#document-customization/api/customizing_endpoints_using_yaml)

##### Learn more[¶](#learn-more "Permalink to this headline")

* [API Platform serialization](https://api-platform.com/docs/core/serialization)

#### Tips & Tricks[¶](#tips-tricks "Permalink to this headline")

##### How to get Sylius Resource configuration from the container?[¶](#how-to-get-sylius-resource-configuration-from-the-container "Permalink to this headline")

There are some exceptions to the instructions of customizing models. In most cases the instructions

will get you exactly where you need to be, but when for example attempting to customize the `ShopUser`

model, you will see an error:

```

In ArrayNode.php line 331:

Unrecognized option "classes" under "sylius\_user.resources.user". Available option is "user".

```

In this case, when customizing the `ShopUser` model and using the following resource configuration:

```

sylius\_user:

 resources:

 user:

 classes:

 model: App\Entity\ShopUser

```

The error is displayed because the user entity is extended multiple times in the user bundle.

To find out the correct configuration, please run the following command:

```

bin/console debug:config SyliusUserBundle

```

The output of that command should look similar to:

```

Current configuration for "SyliusUserBundle"

============================================

sylius_user:

 driver: doctrine/orm

 resources:

 admin:

 user:

 classes:

 model: Sylius\Component\Core\Model\AdminUser

 repository: Sylius\Bundle\UserBundle\Doctrine\ORM\UserRepository

 form: Sylius\Bundle\CoreBundle\Form\Type\User\AdminUserType

 interface: Sylius\Component\User\Model\UserInterface

 controller: Sylius\Bundle\UserBundle\Controller\UserController

 factory: Sylius\Component\Resource\Factory\Factory

 ...

 shop:

 user:

 classes:

 model: Sylius\Component\Core\Model\ShopUser

 repository: Sylius\Bundle\CoreBundle\Doctrine\ORM\UserRepository

 form: Sylius\Bundle\CoreBundle\Form\Type\User\ShopUserType

 interface: Sylius\Component\User\Model\UserInterface

 controller: Sylius\Bundle\UserBundle\Controller\UserController

 factory: Sylius\Component\Resource\Factory\Factory

 ...

 oauth:

 user:

 classes:

 model: Sylius\Component\User\Model\UserOAuth

 interface: Sylius\Component\User\Model\UserOAuthInterface

 controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController

 factory: Sylius\Component\Resource\Factory\Factory

 ...

```

As you can see there is an extra layer in the configuration here.

Since in this example we’re attempting to customize the `ShopUser` entity, we need to use the following

configuration in `config/packages/\_sylius.yaml`:

```

sylius\_user:

 resources:

 shop:

 user:

 classes:

 model: App\Entity\ShopUser

```

This is how you should always be able to find out the correct configuration.

* [Customizing Models](index.html#document-customization/model)

* [Customizing Forms](index.html#document-customization/form)

* [Customizing Repositories](index.html#document-customization/repository)

* [Customizing Factories](index.html#document-customization/factory)

* [Customizing Controllers](index.html#document-customization/controller)

* [Customizing Validation](index.html#document-customization/validation)

* [Customizing Menus](index.html#document-customization/menu)

* [Customizing Templates](index.html#document-customization/template)

* [Customizing Translations](index.html#document-customization/translation)

* [Customizing Flashes](index.html#document-customization/flash)

* [Customizing State Machines](index.html#document-customization/state_machine)

* [Customizing Grids](index.html#document-customization/grid)

* [Customizing Fixtures](index.html#document-customization/fixtures)

* [Customizing Fixture Suites](index.html#document-customization/fixture_suites)

* [Customizing API](index.html#document-customization/api/index)

* [Tips & Tricks](index.html#document-customization/tips_and_tricks)

#### Good to know[¶](#good-to-know "Permalink to this headline")

See also

All the customizations can be done either in your application directly or in [Plugins](index.html#document-book/plugins/index)!

* [Customizing Models](index.html#document-customization/model)

* [Customizing Forms](index.html#document-customization/form)

* [Customizing Repositories](index.html#document-customization/repository)

* [Customizing Factories](index.html#document-customization/factory)

* [Customizing Controllers](index.html#document-customization/controller)

* [Customizing Validation](index.html#document-customization/validation)

* [Customizing Menus](index.html#document-customization/menu)

* [Customizing Templates](index.html#document-customization/template)

* [Customizing Translations](index.html#document-customization/translation)

* [Customizing Flashes](index.html#document-customization/flash)

* [Customizing State Machines](index.html#document-customization/state_machine)

* [Customizing Grids](index.html#document-customization/grid)

* [Customizing Fixtures](index.html#document-customization/fixtures)

* [Customizing Fixture Suites](index.html#document-customization/fixture_suites)

* [Customizing API](index.html#document-customization/api/index)

* [Tips & Tricks](index.html#document-customization/tips_and_tricks)

The Cookbook[¶](#the-cookbook "Permalink to this headline")

-----------------------------------------------------------

[The Cookbook](index.html#document-cookbook/index) is a collection of specific solutions for specific needs.

### The Cookbook[¶](#the-cookbook "Permalink to this headline")

The Sylius Cookbook is a collection of solution articles helping you with some specific, narrow problems.

#### CLI[¶](#cli "Permalink to this headline")

##### Handle multiple channels in CLI[¶](#handle-multiple-channels-in-cli "Permalink to this headline")

When we use directly or indirectly any service depending on a channel context, we are not able to define which channel we want to use, when there are more than two.

Your primary goal should be to avoid such cases, but if you have to, there is a way to do it.

###### What is our goal?[¶](#what-is-our-goal "Permalink to this headline")

We have to create a custom channel context available only from the CLI context. This channel context should allow setting a channel code (which we will do inside the console command). This way, we can use the channel context in our services.

###### 1. Custom Channel Context[¶](#custom-channel-context "Permalink to this headline")

First, we need to create a channel context. Let’s remind the requirements:

> 

> * available only from the CLI

> * there must be a way to pass in a channel code

> 

> 

> 

Our suggested solution is the following:

```

// src/Channel/Context/CliBasedChannelContext.php

<?php

declare(strict\_types=1);

namespace App\Channel\Context;

use Sylius\Component\Channel\Context\ChannelNotFoundException;

use Sylius\Component\Channel\Model\ChannelInterface;

use Sylius\Component\Channel\Repository\ChannelRepositoryInterface;

final class CliBasedChannelContext implements CliBasedChannelContextInterface

{

    private ?string $channelCode = null;

    public function \_\_construct(

        private ChannelRepositoryInterface $channelRepository,

    ) {

    }

    public function setChannelCode(?string $channelCode): void

    {

        $this->channelCode = $channelCode;

    }

    public function getChannelCode(): ?string

    {

        return $this->channelCode;

    }

    public function getChannel(): ChannelInterface

    {

        if ('cli' !== PHP\_SAPI || null === $this->channelCode) {

            throw new ChannelNotFoundException();

        }

        $channel = $this->channelRepository->findOneByCode($this->channelCode);

        if (null === $channel) {

            throw new ChannelNotFoundException();

        }

        return $channel;

    }

}

```

```

// src/Channel/Context/CliBasedChannelContextInterface.php;

<?php

declare(strict\_types=1);

namespace App\Channel\Context;

use Sylius\Component\Channel\Context\ChannelContextInterface;

interface CliBasedChannelContextInterface extends ChannelContextInterface

{

    public function setChannelCode(?string $channelCode): void;

    public function getChannelCode(): ?string;

}

```

Now, we have to configure our custom channel context as a service:

```

# config/services.yaml

services:

 App\Channel\Context\CliBasedChannelContextInterface:

 class: App\Channel\Context\CliBasedChannelContext

 arguments:

 - '@sylius.repository.channel'

 tags:

 - { name: 'sylius.context.channel', priority: -256 }

```

###### 2. Usage of the new custom channel context in a console command[¶](#usage-of-the-new-custom-channel-context-in-a-console-command "Permalink to this headline")

For our example, we will create a DummyCommand which will take a channel code as an option

and dispatch a dummy event. This event is handled by a subscriber using

the channel context to print the channel’s name.

You command might look like this:

```

// src/Console/Command/DummyCommand.php

<?php

declare(strict\_types=1);

namespace App\Console\Command;

use App\Channel\Context\CliBasedChannelContextInterface;

use App\Console\Command\Event\DummyEvent; // it is just a dummy event, nothing special there

use Symfony\Component\Console\Attribute\AsCommand;

use Symfony\Component\Console\Command\Command;

use Symfony\Component\Console\Input\InputInterface;

use Symfony\Component\Console\Input\InputOption;

use Symfony\Component\Console\Output\OutputInterface;

use Symfony\Component\EventDispatcher\EventDispatcherInterface;

#[AsCommand('app:dummy', description: 'Dummy command')]

class DummyCommand extends Command

{

 public function \_\_construct (

 private CliBasedChannelContextInterface $cliBasedChannelContext,

 private EventDispatcherInterface $dispatcher,

 ) {

 parent::\_\_construct();

 }

 protected function configure(): void

 {

 $this

 ->addOption('channel', 'c', InputOption::VALUE\_OPTIONAL, 'Channel code')

 ;

 }

 protected function execute(InputInterface $input, OutputInterface $output): int

 {

 if (null !== $channelCode = $input->getOption('channel')) {

 $this->cliBasedChannelContext->setChannelCode($channelCode);

 }

 // The subscriber just gets a channel from the channel context

 $this->dispatcher->dispatch(new DummyEvent());

 return Command::SUCCESS;

 }

}

```

The output of the example is following:

```

$ bin/console app:dummy -c MAGIC_WEB

Hi! I am Dummy Event Subscriber. I am using Channel Context.

Your channel name is: Magic Web Channel

$ bin/console app:dummy -c FASHION_WEB

Hi! I am Dummy Event Subscriber. I am using Channel Context.

Your channel name is: Fashion Web Store

```

* [Handle multiple channels in CLI](index.html#document-cookbook/cli/handle-multiple-channels-in-cli)

#### Entities[¶](#entities "Permalink to this headline")

##### How to add a custom model?[¶](#how-to-add-a-custom-model "Permalink to this headline")

In some cases you may be needing to add new models to your application in order to cover unique business needs.

The process of extending Sylius with new entities is simple and intuitive.

As an example we will take a **Supplier entity**, which may be really useful for shop maintenance.

###### 1. Define your needs[¶](#define-your-needs "Permalink to this headline")

A Supplier needs three essential fields: `name`, `description` and `enabled` flag.

###### 2. Generate the entity[¶](#generate-the-entity "Permalink to this headline")

Symfony, the framework Sylius uses, provides the [SymfonyMakerBundle](https://symfony.com/doc/current/bundles/SymfonyMakerBundle/index.html) that simplifies the process of adding a model.

Warning

Remember to have the `SymfonyMakerBundle` imported in the AppKernel, as it is not there by default.

You need to use such a command in your project directory.

With the Maker Bundle

```

php bin/console make:entity

```

The generator will ask you for the entity name and fields. See how it should look like to match our assumptions.

![_images/generating_entity.png](_images/generating_entity.png)

Note

You can encounter error when generating entity with Maker Bundle, this can be fixed with [Maker bundle force annotation fix](https://github.com/vklux/maker-bundle-force-annotation)

![_images/make_entity_error.png](_images/make_entity_error.png)

###### 3. Update the database using migrations[¶](#update-the-database-using-migrations "Permalink to this headline")

Assuming that your database was up-to-date before adding the new entity, run:

```

php bin/console doctrine:migrations:diff

```

This will generate a new migration file which adds the Supplier entity to your database.

Then update the database using the generated migration:

```

php bin/console doctrine:migrations:migrate

```

###### 4. Add ResourceInterface to your model class[¶](#add-resourceinterface-to-your-model-class "Permalink to this headline")

Go to the generated class file and make it implement the `ResourceInterface`:

```

<?php

namespace App\Entity;

use Sylius\Component\Resource\Model\ResourceInterface;

class Supplier implements ResourceInterface

{

    // ...

}

```

###### 5. Change repository to extend EntityRepository[¶](#change-repository-to-extend-entityrepository "Permalink to this headline")

Go to generated repository and make it extend `EntityRepository` and remove `\_\_construct`:

```

<?php

namespace App\Repository\Supply;

use App\Entity\Supply\Supplier;

use Doctrine\Persistence\ManagerRegistry;

use Sylius\Bundle\ResourceBundle\Doctrine\ORM\EntityRepository;

class SupplierRepository extends EntityRepository

{

    // ...

}

```

###### 6. Register your entity as a Sylius resource[¶](#register-your-entity-as-a-sylius-resource "Permalink to this headline")

If you don’t have it yet, create a file `config/packages/sylius\_resource.yaml`.

```

# config/packages/sylius\_resource.yaml

sylius\_resource:

 resources:

 app.supplier:

 driver: doctrine/orm # You can use also different driver here

 classes:

 model: App\Entity\Supplier

 repository: App\Repository\SupplierRepository

```

To check if the process was run correctly run such a command:

```

php bin/console debug:container | grep supplier

```

The output should be:

![_images/container_debug_supplier.png](_images/container_debug_supplier.png)

###### 7. Define grid structure for the new entity[¶](#define-grid-structure-for-the-new-entity "Permalink to this headline")

To have templates for your Entity administration out of the box you can use Grids. Here you can see how to configure a grid for the Supplier entity.

```

# config/packages/\_sylius.yaml

sylius\_grid:

 grids:

 app\_admin\_supplier:

 driver:

 name: doctrine/orm

 options:

 class: App\Entity\Supplier

 fields:

 name:

 type: string

 label: sylius.ui.name

 description:

 type: string

 label: sylius.ui.description

 enabled:

 type: twig

 label: sylius.ui.enabled

 options:

 template: "@SyliusUi/Grid/Field/enabled.html.twig"

 actions:

 main:

 create:

 type: create

 item:

 update:

 type: update

 delete:

 type: delete

```

###### 8. Define routing for entity administration[¶](#define-routing-for-entity-administration "Permalink to this headline")

Having a grid prepared we can configure routing for the entity administration:

```

# config/routes.yaml

app\_admin\_supplier:

 resource: |

 alias: app.supplier

 section: admin

 templates: "@SyliusAdmin\\Crud"

 redirect: update

 grid: app\_admin\_supplier

 vars:

 all:

 subheader: app.ui.supplier

 index:

 icon: 'file image outline'

 type: sylius.resource

 prefix: /admin

```

###### 9. Add entity administration to the admin menu[¶](#add-entity-administration-to-the-admin-menu "Permalink to this headline")

Tip

See [how to add links to your new entity administration in the administration menu](index.html#document-customization/menu).

###### 10. Check the admin panel for your changes[¶](#check-the-admin-panel-for-your-changes "Permalink to this headline")

Tip

To see what you can do with your new entity access the `https://localhost:8000/admin/suppliers/` url.

###### Learn more[¶](#learn-more "Permalink to this headline")

* [GridBundle documentation](https://github.com/Sylius/SyliusGridBundle/blob/master/docs/index.md)

* [ResourceBundle documentation](https://github.com/Sylius/SyliusResourceBundle/blob/master/docs/index.md)

* [Customization Guide](index.html#document-customization/index)

##### How to add a custom model accessible for respective channel administrators?[¶](#how-to-add-a-custom-model-accessible-for-respective-channel-administrators "Permalink to this headline")

Given that you are using Sylius Plus, the licensed edition of Sylius, you may have

the Administrators per Channel defined in your application. Thus when you add a new,

channel-based entity to it, you will need to enable this entity to be viewed only by the relevant channel admins.

###### 1. Define your custom model, our example will be the **Supplier entity**[¶](#define-your-custom-model-our-example-will-be-the-supplier-entity "Permalink to this headline")

In order to prepare a simple Entity follow [this guide](index.html#document-cookbook/entities/custom-model).

Remember to then add your entity to the admin menu. Adding a new entity to the admin menu

is described in the section `How to customize Admin Menu` of [this guide](index.html#document-customization/menu).

* Having your Supplier entity created, add a channel field with relation to the `Channel` entity:

```

/\*\*

 \* @var ChannelInterface

 \* @ORM\ManyToOne(targetEntity="Sylius\Plus\Entity\ChannelInterface")

 \* @ORM\JoinColumn(name="channel\_id", referencedColumnName="id", nullable=true)

 \*/

protected $channel;

public function getChannel(): ?ChannelInterface

{

    return $this->channel;

}

public function setChannel(?ChannelInterface $channel): void

{

    $this->channel = $channel;

}

```

* Assuming that your database was up-to-date before these changes, create a proper migration and use it:

```

php bin/console doctrine:migrations:diff

php bin/console doctrine:migrations:migrate

```

* Next, create a form type for your entity:

```

<?php

declare(strict\_types=1);

namespace App\Form\Type;

use Sylius\Bundle\ChannelBundle\Form\Type\ChannelChoiceType;

use Sylius\Bundle\ResourceBundle\Form\Type\AbstractResourceType;

use Sylius\Plus\ChannelAdmin\Application\Provider\AvailableChannelsForAdminProviderInterface;

use Symfony\Component\Form\Extension\Core\Type\TextType;

use Symfony\Component\Form\FormBuilderInterface;

final class SupplierType extends AbstractResourceType

{

    /\*\* @var AvailableChannelsForAdminProviderInterface \*/

    private $availableChannelsForAdminProvider;

    public function \_\_construct(

        string $dataClass,

        array $validationGroups,

        AvailableChannelsForAdminProviderInterface $availableChannelsForAdminProvider

    ) {

        parent::\_\_construct($dataClass, $validationGroups);

        $this->availableChannelsForAdminProvider = $availableChannelsForAdminProvider;

    }

    public function buildForm(FormBuilderInterface $builder, array $options)

    {

        $builder

            ->add('name', TextType::class, [

                'label' => 'Name'

            ])

            ->add('channel', ChannelChoiceType::class, [

                'choices' => $this->availableChannelsForAdminProvider->getChannels(),

                'label' => 'sylius.ui.channel',

            ])

        ;

    }

    public function getBlockPrefix(): string

    {

        return 'supplier';

    }

}

```

```

# config/services.yaml

App\Form\Type\SupplierType:

 arguments:

 - 'App\Entity\Supplier'

 - 'sylius'

 - '@Sylius\Plus\ChannelAdmin\Application\Provider\AvailableChannelsForAdminProviderInterface'

 tags: ['form.type']

```

The `Sylius\Plus\ChannelAdmin\Application\Provider\AvailableChannelsForAdminProviderInterface` service allows getting

a list of proper channels for the currently logged in admin user.

Remember to register `App\Form\SupplierType` for resource:

```

sylius_resource:

    resources:

        app.supplier:

            driver: doctrine/orm

            classes:

                model: App\Entity\Supplier

   +            form: App\Form\Type\SupplierType

```

###### 2. Restrict access to the entity for the respective channel administrator:[¶](#restrict-access-to-the-entity-for-the-respective-channel-administrator "Permalink to this headline")

Note

More information about using administrator roles (ACL/RBAC) can be found [here](index.html#document-book/customers/admin_user).

* Add supplier to restricted resources:

```

sylius\_plus:

 channel\_admin:

 restricted\_resources:

 supplier: ~

```

* Create `App\Checker\SupplierResourceChannelChecker` and tag this service with sylius\_plus.channel\_admin.resource\_channel\_checker:

Tip

If the created entity implements the `Sylius\Component\Channel\Model\ChannelAwareInterface` interface,

everything will work without having to do this step and create `SupplierResourceChannelChecker`.

```

<?php

declare(strict\_types=1);

namespace App\Checker;

use App\Entity\Supplier;

use Sylius\Plus\ChannelAdmin\Application\Checker\ResourceChannelCheckerInterface;

use Sylius\Plus\Entity\ChannelInterface;

final class SupplierResourceChannelChecker implements ResourceChannelCheckerInterface

{

    public function isFromChannel(object $resource, ChannelInterface $channel): bool

    {

        if ($resource instanceof Supplier && in\_array($resource->getChannel(), [$channel, null], true)) {

            return true;

        }

        return false;

    }

}

```

```

# config/services.yaml

App\Checker\SupplierResourceChannelChecker:

 tags:

 - { name: sylius\_plus.channel\_admin.resource\_channel\_checker }

```

After that, access to the resource should work properly with all restrictions.

* Next add `RestrictingSupplierListQueryBuilder`:

```

<?php

declare(strict\_types=1);

namespace App\Doctrine\ORM;

use Doctrine\ORM\QueryBuilder;

use Sylius\Bundle\ResourceBundle\Doctrine\ORM\EntityRepository;

use Sylius\Component\Core\Model\ChannelInterface;

use Sylius\Plus\ChannelAdmin\Application\Provider\AdminChannelProviderInterface;

final class RestrictingSupplierListQueryBuilder

{

    /\*\* @var AdminChannelProviderInterface \*/

    private $adminChannelProvider;

    /\*\* @var EntityRepository \*/

    private $supplierRepository;

    public function \_\_construct(

        AdminChannelProviderInterface $adminChannelProvider,

        EntityRepository $supplierRepository

    ) {

        $this->adminChannelProvider = $adminChannelProvider;

        $this->supplierRepository = $supplierRepository;

    }

    public function create(): QueryBuilder

    {

        $listQueryBuilder = $this->supplierRepository->createQueryBuilder('o');

        /\*\* @var ChannelInterface|null $channel \*/

        $channel = $this->adminChannelProvider->getChannel();

        if ($channel === null) {

            return $listQueryBuilder;

        }

        return $listQueryBuilder

            ->andWhere('o.channel = :channel')

            ->setParameter('channel', $channel)

        ;

    }

}

```

```

# config/services.yaml

App\Doctrine\ORM\RestrictingSupplierListQueryBuilder:

 public: true

 class: App\Doctrine\ORM\RestrictingSupplierListQueryBuilder

 arguments: ['@Sylius\Plus\ChannelAdmin\Application\Provider\AdminChannelProviderInterface', '@app.repository.supplier']

```

* Add method to the Suppliers grid:

```

 sylius\_grid:

 grids:

 app\_admin\_supplier:

 driver:

 name: doctrine/orm

 options:

 class: App\Entity\Supplier

+ repository:

+ method: [expr:service('App\\Doctrine\\ORM\\RestrictingSupplierListQueryBuilder'), create]

```

Well done! That’s it, now you have a Supplier entity, that is accessible within the Sylius Plus Administrators per Channel feature!

##### How to add a custom translatable model?[¶](#how-to-add-a-custom-translatable-model "Permalink to this headline")

In this guide we will create a new translatable model in our system, which is quite similar to [adding a simple model](index.html#document-cookbook/entities/custom-model),

although it requires some additional steps.

As an example we will take a **translatable Supplier entity**, which may be really useful for shop maintenance.

###### 1. Define your needs[¶](#define-your-needs "Permalink to this headline")

A Supplier needs three essential fields: `name`, `description` and `enabled` flag.

The **name and description** fields need to be translatable.

###### 2. Generate the SupplierTranslation entity[¶](#generate-the-suppliertranslation-entity "Permalink to this headline")

Symfony, the framework Sylius uses, provides the [Symfony MakerBundle](https://symfony.com/bundles/SymfonyMakerBundle/current/index.html),

that simplifies the process of adding a model.

Warning

Remember to have the `MakerBundle` imported in the AppKernel, as it is not there by default.

You need to use such a command in your project directory.

```

php bin/console make:entity

```

The generator will ask you for the entity name and fields. See how it should look like to match our assumptions.

![_images/generating_translation_entity.png](_images/generating_translation_entity.png)

As you can see we have provided only the desired translatable fields.

Below the final `SupplierTranslation` class is presented, it implements the `ResourceInterface`.

```

<?php

namespace App\Entity;

use Sylius\Component\Resource\Model\AbstractTranslation;

use Sylius\Component\Resource\Model\ResourceInterface;

class SupplierTranslation extends AbstractTranslation implements ResourceInterface

{

    /\*\*

 \* @var int

 \*/

    private $id;

    /\*\*

 \* @var string

 \*/

    private $name;

    /\*\*

 \* @var string

 \*/

    private $description;

    /\*\*

 \* @return int

 \*/

    public function getId()

    {

        return $this->id;

    }

    /\*\*

 \* @param string $name

 \*/

    public function setName($name)

    {

        $this->name = $name;

    }

    /\*\*

 \* @return string

 \*/

    public function getName()

    {

        return $this->name;

    }

    /\*\*

 \* @param string $description

 \*/

    public function setDescription($description)

    {

        $this->description = $description;

    }

    /\*\*

 \* @return string

 \*/

    public function getDescription()

    {

        return $this->description;

    }

}

```

###### 3. Generate the Supplier entity[¶](#generate-the-supplier-entity "Permalink to this headline")

While generating the entity, similarly to the way the translation was generated, we are providing only non-translatable fields.

In our case only the `enabled` field.

![_images/generating_basic_entity.png](_images/generating_basic_entity.png)

Having the stubs generated, we need to extend our class with a connection to SupplierTranslation.

* implement the `ResourceInterface`,

* implement the `TranslatableInterface`,

* use the `TranslatableTrait`,

* initialize the translations collection in the constructor,

* add the `createTranslation()` method,

* implement getters and setters for the properties that are held on the translation model.

As a result you should get such a `Supplier` class:

```

<?php

namespace App\Entity;

use Sylius\Component\Resource\Model\ResourceInterface;

use Sylius\Component\Resource\Model\TranslatableInterface;

use Sylius\Component\Resource\Model\TranslatableTrait;

class Supplier implements ResourceInterface, TranslatableInterface

{

    use TranslatableTrait {

        \_\_construct as private initializeTranslationsCollection;

    }

    public function \_\_construct()

    {

        $this->initializeTranslationsCollection();

    }

    /\*\*

 \* @var int

 \*/

    private $id;

    /\*\*

 \* @var bool

 \*/

    private $enabled;

    /\*\*

 \* @return int

 \*/

    public function getId()

    {

        return $this->id;

    }

    /\*\*

 \* @param string $name

 \*/

    public function setName($name)

    {

        $this->getTranslation()->setName($name);

    }

    /\*\*

 \* @return string

 \*/

    public function getName()

    {

        return $this->getTranslation()->getName();

    }

    /\*\*

 \* @param string $description

 \*/

    public function setDescription($description)

    {

        $this->getTranslation()->setDescription($description);

    }

    /\*\*

 \* @return string

 \*/

    public function getDescription()

    {

        return $this->getTranslation()->getDescription();

    }

    /\*\*

 \* @param boolean $enabled

 \*/

    public function setEnabled($enabled)

    {

        $this->enabled = $enabled;

    }

    /\*\*

 \* @return bool

 \*/

    public function getEnabled()

    {

        return $this->enabled;

    }

    /\*\*

 \* {@inheritdoc}

 \*/

    protected function createTranslation()

    {

        return new SupplierTranslation();

    }

}

```

###### 4. Register your entity together with translation as a Sylius resource[¶](#register-your-entity-together-with-translation-as-a-sylius-resource "Permalink to this headline")

If you don’t have it yet, create a file `config/packages/sylius\_resource.yaml`.

```

# config/packages/sylius\_resource.yaml

sylius\_resource:

 resources:

 app.supplier:

 driver: doctrine/orm # You can use also different driver here

 classes:

 model: App\Entity\Supplier

 translation:

 classes:

 model: App\Entity\SupplierTranslation

```

To check if the process was run correctly run such a command:

```

php bin/console debug:container | grep supplier

```

The output should be:

![_images/container_debug_supplier_translation.png](_images/container_debug_supplier_translation.png)

###### 5. Update the database using migrations[¶](#update-the-database-using-migrations "Permalink to this headline")

Assuming that your database was up-to-date before adding the new entity, run:

```

php bin/console doctrine:migrations:diff

```

This will generate a new migration file which adds the Supplier entity to your database.

Then update the database using the generated migration:

```

php bin/console doctrine:migrations:migrate

```

###### 6. Prepare new forms for your entity, that will be aware of its translation[¶](#prepare-new-forms-for-your-entity-that-will-be-aware-of-its-translation "Permalink to this headline")

You will need both `SupplierType` and `SupplierTranslationType`.

Let’s start with the translation type, as it will be included into the entity type.

```

<?php

namespace App\Form\Type;

use Sylius\Bundle\ResourceBundle\Form\Type\AbstractResourceType;

use Symfony\Component\Form\Extension\Core\Type\TextareaType;

use Symfony\Component\Form\Extension\Core\Type\TextType;

use Symfony\Component\Form\FormBuilderInterface;

class SupplierTranslationType extends AbstractResourceType

{

    /\*\*

 \* {@inheritdoc}

 \*/

    public function buildForm(FormBuilderInterface $builder, array $options)

    {

        $builder

            ->add('name', TextType::class)

            ->add('description', TextareaType::class, [

                'required' => false,

            ])

        ;

    }

    /\*\*

 \* {@inheritdoc}

 \*/

    public function getBlockPrefix()

    {

        return 'app\_supplier\_translation';

    }

}

```

On the `SupplierTranslationType` we need to define only the translatable fields.

Then let’s prepare the entity type, that will include the translation type.

```

<?php

namespace App\Form\Type;

use Sylius\Bundle\ResourceBundle\Form\Type\AbstractResourceType;

use Sylius\Bundle\ResourceBundle\Form\Type\ResourceTranslationsType;

use Sylius\Component\Resource\Translation\Provider\TranslationLocaleProviderInterface;

use Symfony\Component\Form\Extension\Core\Type\CheckboxType;

use Symfony\Component\Form\Extension\Core\Type\TextareaType;

use Symfony\Component\Form\Extension\Core\Type\TextType;

use Symfony\Component\Form\FormBuilderInterface;

class SupplierType extends AbstractResourceType

{

    /\*\*

 \* {@inheritdoc}

 \*/

    public function buildForm(FormBuilderInterface $builder, array $options)

    {

        $builder

            ->add('translations', ResourceTranslationsType::class, [

                'entry\_type' => SupplierTranslationType::class,

            ])

            ->add('enabled', CheckboxType::class, [

                'required' => false,

            ])

        ;

    }

    /\*\*

 \* {@inheritdoc}

 \*/

    public function getBlockPrefix()

    {

        return 'app\_supplier';

    }

}

```

###### 7. Register the new forms as services[¶](#register-the-new-forms-as-services "Permalink to this headline")

Before the newly created forms will be ready to use them, they need to be registered as services:

```

# config/services.yaml

services:

 app.supplier.form.type:

 class: App\Form\Type\SupplierType

 tags:

 - { name: form.type }

 arguments: ['%app.model.supplier.class%', ['sylius']]

 app.supplier\_translation.form.type:

 class: App\Form\Type\SupplierTranslationType

 tags:

 - { name: form.type }

 arguments: ['%app.model.supplier\_translation.class%', ['sylius']]

```

###### 8. Register the forms as resource forms of the Supplier entity[¶](#register-the-forms-as-resource-forms-of-the-supplier-entity "Permalink to this headline")

Extend the resource configuration of the `app.supplier` with forms:

```

# config/resources.yaml

sylius\_resource:

 resources:

 app.supplier:

 driver: doctrine/orm # You can use also different driver here

 classes:

 model: App\Entity\Supplier

 form: App\Form\Type\SupplierType

 translation:

 classes:

 model: App\Entity\SupplierTranslation

 form: App\Form\Type\SupplierTranslationType

```

###### 9. Define grid structure for the new entity[¶](#define-grid-structure-for-the-new-entity "Permalink to this headline")

To have templates for your Entity administration out of the box you can use Grids. Here you can see how to configure a grid for the Supplier entity.

```

# config/packages/\_sylius.yaml

sylius\_grid:

 grids:

 app\_admin\_supplier:

 driver:

 name: doctrine/orm

 options:

 class: App\Entity\Supplier

 fields:

 name:

 type: string

 label: sylius.ui.name

 sortable: translation.name

 enabled:

 type: twig

 label: sylius.ui.enabled

 options:

 template: "@SyliusUi/Grid/Field/enabled.html.twig"

 actions:

 main:

 create:

 type: create

 item:

 update:

 type: update

 delete:

 type: delete

```

###### 10. Create template[¶](#create-template "Permalink to this headline")

```

# App/Resources/views/Supplier/\_form.html.twig

{% from '@SyliusAdmin/Macro/translationForm.html.twig' import translationForm %}

{{ form\_errors(form) }}

{{ translationForm(form.translations) }}

{{ form\_row(form.enabled) }}

```

###### 11. Define routing for entity administration[¶](#define-routing-for-entity-administration "Permalink to this headline")

Having a grid prepared we can configure routing for the entity administration:

```

# config/routes.yaml

app\_admin\_supplier:

 resource: |

 alias: app.supplier

 section: admin

 templates: "@SyliusAdmin\\Crud"

 redirect: update

 grid: app\_admin\_supplier

 vars:

 all:

 subheader: app.ui.supplier

 templates:

 form: App:Supplier:\_form.html.twig

 index:

 icon: 'file image outline'

 type: sylius.resource

 prefix: admin

```

###### 12. Add entity administration to the admin menu[¶](#add-entity-administration-to-the-admin-menu "Permalink to this headline")

Tip

See [how to add links to your new entity administration in the administration menu](index.html#document-customization/menu).

###### 13. Check the admin panel for your changes[¶](#check-the-admin-panel-for-your-changes "Permalink to this headline")

Tip

To see what you can do with your new entity access the `http://localhost:8000/admin/suppliers/` url.

###### Learn more[¶](#learn-more "Permalink to this headline")

* [GridBundle documentation](https://github.com/Sylius/SyliusGridBundle/blob/master/docs/index.md)

* [ResourceBundle documentation](https://github.com/Sylius/SyliusResourceBundle/blob/master/docs/index.md)

* [Customization Guide](index.html#document-customization/index)

* [How to add a custom model?](index.html#document-cookbook/entities/custom-model)

* [How to add a custom model accessible for respective channel administrators?](index.html#document-cookbook/entities/custom-model-accessible-for-channel-admin)

* [How to add a custom translatable model?](index.html#document-cookbook/entities/custom-translatable-model)

#### Administration[¶](#administration "Permalink to this headline")

##### How to edit orders in Sylius?[¶](#how-to-edit-orders-in-sylius "Permalink to this headline")

Since time immemorial, people have been using trial and error methods to create or repeat processes that make their lives simpler.

Thanks to this, we live in a time where everything is easily available and within reach.

However, this time we will not focus on inventions, but on the mistakes that people still make.

Usually, e-commerce processes go smoothly, but from time to time there will be a customer who confuses something in his order (e.g. the size of the shorts they wear).

In this cookbook, we will try to show you how to easily fix this kind of mistake as a store administrator.

###### TL;DR[¶](#tl-dr "Permalink to this headline")

Quick general steps of how to easily solve this issue:

* From admin panel go to the order you want to edit

* Click on the customer and go to their show page

* Login as this customer whose order you want to edit

* Place a new order with the required modifications (like, changing the size of one of the products)

* Choose offline payment as the payment method (as we already got the money)

* or choose any payment method and set payment as completed on admin order page

* In the order notes you can add a note that it is a modified order and add the original order number

* In the admin panel fulfill the order - complete its payment

* Go back to the original order and cancel it

* Proceed with the new “modified” order

###### Step by step instruction[¶](#step-by-step-instruction "Permalink to this headline")

If you have troubles of going with shorter instruction, here we will go step by step so you are sure of what to do:

Note

The tutorial steps are done on Sylius demo environment under link: <https://demo.sylius.com>

If you want to test if Sylius suits you or try the steps, do not hesitate to check it out on your own.

####### Impersonate as user and create an order[¶](#impersonate-as-user-and-create-an-order "Permalink to this headline")

Let’s start with the steps that will let you impersonate a user and create new order as them.

First, log in as an administrator into the admin panel, go to `orders` tab, and find the incorrect order (in this example Order #000000021):

[![_images/orders-page.png](_images/orders-page.png)](_images/orders-page.png)

  

Let’s show it as the data in it will be useful for us in creation of the new order.

[![_images/order.png](_images/order.png)](_images/order.png)

  

In this order we can click on the name of the user to go directly to customer account.

Here we have an option `impersonate`, click it and now it would be best to open a shop page in new tab.

[![_images/customer.png](_images/customer.png)](_images/customer.png)

  

Now on the shop page, we can see the greeting with name and surname of customer. This indicates we are impersonated correctly.

[![_images/shop.png](_images/shop.png)](_images/shop.png)

  

Now you need to find products with correct variants, add them and proceed to checkout

[![_images/checkout.png](_images/checkout.png)](_images/checkout.png)

  

On the next checkout steps just fill out the data that was used in old order,

as a payment method - use one of the offline methods (if your shop supports one), or proceed with any - and we will complete payment manually.

You can also add a note to it that it is modification of original order.

In the end you will be greeted with message that your checkout has been completed.

[![_images/completed-checkout.png](_images/completed-checkout.png)](_images/completed-checkout.png)

  

Now let’s go back to the customer page (where you can click the `show orders` button) or the orders page where you can search for the just-created order.

Here look for `complete` button on payment tab:

[![_images/new-order.png](_images/new-order.png)](_images/new-order.png)

  

And let’s get back to the old order once more and `cancel` this order:

[![_images/old-order.png](_images/old-order.png)](_images/old-order.png)

  

From now you can proceed with new order with correct products.

* [How to edit orders in Sylius?](index.html#document-cookbook/administration/editing-orders)

#### Shop[¶](#shop "Permalink to this headline")

##### How to customize Sylius Checkout?[¶](#how-to-customize-sylius-checkout "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

###### Why would you override the Checkout process?[¶](#why-would-you-override-the-checkout-process "Permalink to this headline")

This is a common problem for many Sylius users. Sometimes the checkout process we have designed is not suitable for your custom business needs.

Therefore you need to learn how to modify it, when you will need to for example:

* remove shipping step - when you do not ship the products you sell,

* change the order of checkout steps,

* merge shipping and addressing step into one common step,

* or even make the whole checkout a one page process.

See how to do these things below:

###### How to remove a step from checkout?[¶](#how-to-remove-a-step-from-checkout "Permalink to this headline")

Let’s imagine that you are trying to create a shop that does not need shipping - it sells downloadable files only.

To meet your needs you will need to adjust checkout process. **What do you have to do then?** See below:

####### Overwrite the state machine of Checkout[¶](#overwrite-the-state-machine-of-checkout "Permalink to this headline")

Open the [CoreBundle/Resources/config/app/state\_machine/sylius\_order\_checkout.yml](https://github.com/Sylius/Sylius/blob/master/src/Sylius/Bundle/CoreBundle/Resources/config/app/state_machine/sylius_order_checkout.yml)

and place its content in the `src/Resources/SyliusCoreBundle/config/app/state\_machine/sylius\_order\_checkout.yml`

which is a [standard procedure of overriding configs in Symfony](https://symfony.com/doc/current/bundles/inheritance.html#overriding-resources-templates-routing-etc).

Remove the `shipping\_selected` and `shipping\_skipped` states, `select\_shipping` and `skip\_shipping` transitions.

Remove the `select\_shipping` and `skip\_shipping` transition from the `sylius\_process\_cart` callback.

```

# app/Resources/SyliusCoreBundle/config/app/state\_machine/sylius\_order\_checkout.yml

winzou\_state\_machine:

 sylius\_order\_checkout:

 class: "%sylius.model.order.class%"

 property\_path: checkoutState

 graph: sylius\_order\_checkout

 state\_machine\_class: "%sylius.state\_machine.class%"

 states:

 cart: ~

 addressed: ~

 payment\_skipped: ~

 payment\_selected: ~

 completed: ~

 transitions:

 address:

 from: [cart, addressed, payment\_selected, payment\_skipped]

 to: addressed

 skip\_payment:

 from: [addressed]

 to: payment\_skipped

 select\_payment:

 from: [addressed, payment\_selected]

 to: payment\_selected

 complete:

 from: [payment\_selected, payment\_skipped]

 to: completed

 callbacks:

 after:

 sylius\_process\_cart:

 on: ["address", "select\_payment"]

 do: ["@sylius.order\_processing.order\_processor", "process"]

 args: ["object"]

 sylius\_create\_order:

 on: ["complete"]

 do: ["@sm.callback.cascade\_transition", "apply"]

 args: ["object", "event", "'create'", "'sylius\_order'"]

 sylius\_save\_checkout\_completion\_date:

 on: ["complete"]

 do: ["object", "completeCheckout"]

 args: ["object"]

 sylius\_skip\_shipping:

 on: ["address"]

 do: ["@sylius.state\_resolver.order\_checkout", "resolve"]

 args: ["object"]

 priority: 1

 sylius\_skip\_payment:

 on: ["address"]

 do: ["@sylius.state\_resolver.order\_checkout", "resolve"]

 args: ["object"]

 priority: 1

```

Tip

To check if your new state machine configuration is overriding the old one run:

`php bin/console debug:winzou:state-machine` and check the configuration of `sylius\_order\_checkout`.

####### Adjust Checkout Resolver[¶](#adjust-checkout-resolver "Permalink to this headline")

The next step of customizing Checkout is to adjust the Checkout Resolver to match the changes you have made in the state machine.

```

# config/packages/sylius\_shop.yaml

sylius\_shop:

 checkout\_resolver:

 pattern: /checkout/.+

 route\_map:

 cart:

 route: sylius\_shop\_checkout\_address

 addressed:

 route: sylius\_shop\_checkout\_select\_payment

 payment\_selected:

 route: sylius\_shop\_checkout\_complete

 payment\_skipped:

 route: sylius\_shop\_checkout\_complete

```

####### Adjust Checkout Templates[¶](#adjust-checkout-templates "Permalink to this headline")

After you have got the resolver adjusted, modify the templates for checkout. You have to remove shipping from steps and

disable the hardcoded ability to go back to the shipping step and the number of steps being displayed in the checkout navigation.

You will achieve that by overriding two files:

* [ShopBundle/Resources/views/Checkout/\_steps.html.twig](https://github.com/Sylius/Sylius/blob/master/src/Sylius/Bundle/ShopBundle/Resources/views/Checkout/_steps.html.twig)

* [ShopBundle/Resources/views/Checkout/SelectPayment/\_navigation.html.twig](https://github.com/Sylius/Sylius/blob/master/src/Sylius/Bundle/ShopBundle/Resources/views/Checkout/SelectPayment/_navigation.html.twig)

```

{# templates/SyliusShopBundle/Checkout/\_steps.html.twig #}

{% if active is not defined or active == 'address' %}

 {% set steps = {'address': 'active', 'select\_payment': 'disabled', 'complete': 'disabled'} %}

{% elseif active == 'select\_payment' %}

 {% set steps = {'address': 'completed', 'select\_payment': 'active', 'complete': 'disabled'} %}

{% else %}

 {% set steps = {'address': 'completed', 'select\_payment': 'completed', 'complete': 'active'} %}

{% endif %}

{% set order\_requires\_payment = sylius\_is\_payment\_required(order) %}

{% set steps\_count = 'three' %}

{% if not order\_requires\_payment %}

 {% set steps\_count = 'two' %}

{% endif %}

<div class="ui {{ steps\_count }} steps">

 <a class="{{ steps['address'] }} step" href="{{ path('sylius\_shop\_checkout\_address') }}">

 <i class="map icon"></i>

 <div class="content">

 <div class="title">{{ 'sylius.ui.address'|trans }}</div>

 <div class="description">{{ 'sylius.ui.fill\_in\_your\_billing\_and\_shipping\_addresses'|trans }}</div>

 </div>

 </a>

 {% if order\_requires\_payment %}

 <a class="{{ steps['select\_payment'] }} step" href="{{ path('sylius\_shop\_checkout\_select\_payment') }}">

 <i class="payment icon"></i>

 <div class="content">

 <div class="title">{{ 'sylius.ui.payment'|trans }}</div>

 <div class="description">{{ 'sylius.ui.choose\_how\_you\_will\_pay'|trans }}</div>

 </div>

 </a>

 {% endif %}

 <div class="{{ steps['complete'] }} step" href="{{ path('sylius\_shop\_checkout\_complete') }}">

 <i class="checkered flag icon"></i>

 <div class="content">

 <div class="title">{{ 'sylius.ui.complete'|trans }}</div>

 <div class="description">{{ 'sylius.ui.review\_and\_confirm\_your\_order'|trans }}</div>

 </div>

 </div>

</div>

```

```

{# templates/SyliusShopBundle/Checkout/SelectPayment/\_navigation.html.twig #}

{% set enabled = order.payments|length %}

<div class="ui two column grid">

 <div class="column">

 <a href="{{ path('sylius\_shop\_checkout\_address') }}" class="ui large icon labeled button"><i class="arrow left icon"></i> {{ 'sylius.ui.change\_address'|trans }}</a>

 </div>

 <div class="right aligned column">

 <button type="submit" id="next-step" class="ui large primary icon labeled{% if not enabled %} disabled{% endif %} button">

 <i class="arrow right icon"></i>

 {{ 'sylius.ui.next'|trans }}

 </button>

 </div>

</div>

```

####### Overwrite routing for Checkout[¶](#overwrite-routing-for-checkout "Permalink to this headline")

Unfortunately there is no better way - you have to overwrite the whole routing for Checkout.

To do that copy the content of

[ShopBundle/Resources/config/routing/checkout.yml](https://github.com/Sylius/Sylius/blob/master/src/Sylius/Bundle/ShopBundle/Resources/config/routing/checkout.yml)

to the `app/Resources/SyliusShopBundle/config/routing/checkout.yml` file.

**Remove routing** of `sylius\_shop\_checkout\_select\_shipping`. The rest should remain the same.

```

# app/Resources/SyliusShopBundle/config/routing/checkout.yml

sylius\_shop\_checkout\_start:

 path: /

 methods: [GET]

 defaults:

 \_controller: Symfony\Bundle\FrameworkBundle\Controller\RedirectController:redirectAction

 route: sylius\_shop\_checkout\_address

sylius\_shop\_checkout\_address:

 path: /address

 methods: [GET, PUT]

 defaults:

 \_controller: sylius.controller.order:updateAction

 \_sylius:

 event: address

 flash: false

 template: SyliusShopBundle:Checkout:address.html.twig

 form:

 type: Sylius\Bundle\CoreBundle\Form\Type\Checkout\AddressType

 options:

 customer: expr:service('sylius.context.customer').getCustomer()

 repository:

 method: find

 arguments:

 - "expr:service('sylius.context.cart').getCart()"

 state\_machine:

 graph: sylius\_order\_checkout

 transition: address

sylius\_shop\_checkout\_select\_payment:

 path: /select-payment

 methods: [GET, PUT]

 defaults:

 \_controller: sylius.controller.order:updateAction

 \_sylius:

 event: payment

 flash: false

 template: SyliusShopBundle:Checkout:selectPayment.html.twig

 form: Sylius\Bundle\CoreBundle\Form\Type\Checkout\SelectPaymentType

 repository:

 method: find

 arguments:

 - "expr:service('sylius.context.cart').getCart()"

 state\_machine:

 graph: sylius\_order\_checkout

 transition: select\_payment

sylius\_shop\_checkout\_complete:

 path: /complete

 methods: [GET, PUT]

 defaults:

 \_controller: sylius.controller.order:updateAction

 \_sylius:

 event: complete

 flash: false

 template: SyliusShopBundle:Checkout:complete.html.twig

 repository:

 method: find

 arguments:

 - "expr:service('sylius.context.cart').getCart()"

 state\_machine:

 graph: sylius\_order\_checkout

 transition: complete

 redirect:

 route: sylius\_shop\_order\_pay

 parameters:

 tokenValue: resource.tokenValue

 form:

 type: Sylius\Bundle\CoreBundle\Form\Type\Checkout\CompleteType

 options:

 validation\_groups: 'sylius\_checkout\_complete'

```

Tip

If you do not see any changes run `php bin/console cache:clear`.

###### Learn more[¶](#learn-more "Permalink to this headline")

* [Checkout - concept Documentation](index.html#document-book/orders/checkout)

* [State Machine - concept Documentation](index.html#document-book/architecture/state_machine)

* [Customization Guide](index.html#document-customization/index)

##### How to change a redirect after the add to cart action?[¶](#how-to-change-a-redirect-after-the-add-to-cart-action "Permalink to this headline")

Currently **Sylius** by default is using route definition and **sylius-add-to-cart.js** script to handle redirect after successful add to cart action.

```

sylius\_shop\_partial\_cart\_add\_item:

 path: /add-item

 methods: [GET]

 defaults:

 \_controller: sylius.controller.order\_item:addAction

 \_sylius:

 template: $template

 factory:

 method: createForProduct

 arguments: [expr:service('sylius.repository.product').find($productId)]

 form:

 type: Sylius\Bundle\CoreBundle\Form\Type\Order\AddToCartType

 options:

 product: expr:service('sylius.repository.product').find($productId)

 redirect:

 route: sylius\_shop\_cart\_summary

 parameters: {}

```

```

$.fn.extend({

 addToCart: function () {

 var element = $(this);

 var href = $(element).attr('action');

 var redirectUrl = $(element).data('redirect');

 var validationElement = $('#sylius-cart-validation-error');

 $(element).api({

 method: 'POST',

 on: 'submit',

 cache: false,

 url: href,

 beforeSend: function (settings) {

 settings.data = $(this).serialize();

 return settings;

 },

 onSuccess: function (response) {

 validationElement.addClass('hidden');

 window.location.replace(redirectUrl);

 },

 onFailure: function (response) {

 validationElement.removeClass('hidden');

 var validationMessage = '';

 $.each(response.errors.errors, function (key, message) {

 validationMessage += message;

 });

 validationElement.html(validationMessage);

 $(element).removeClass('loading');

 },

 });

 }

});

```

If you want to have custom logic after cart add action you can use **ResourceControllerEvent** to set your custom response.

Let’s assume that you would like such a feature in your system:

```

<?php

final class ChangeRedirectAfterAddingToCartListener

{

    /\*\*

 \* @var RouterInterface

 \*/

    private $router;

    /\*\*

 \* @param RouterInterface $router

 \*/

    public function \_\_construct(RouterInterface $router)

    {

        $this->router = $router;

    }

    /\*\*

 \* @param ResourceControllerEvent $event

 \*/

    public function onSuccessfulAddToCart(ResourceControllerEvent $event)

    {

        if (!$event->getSubject() instanceof OrderItemInterface) {

            throw new \LogicException(

                sprintf('This listener operates only on order item, got "%s"', get\_class($event->getSubject()))

            );

        }

        $newUrl = $this->router->generate('your\_new\_route\_name', []);

        $event->setResponse(new RedirectResponse($newUrl));

    }

}

```

```

<service id="sylius.listener.change\_redirect\_after\_adding\_to\_cart" class="Sylius\Bundle\ShopBundle\EventListener\ChangeRedirectAfterAddingToCartListener">

 <argument type="service" id="router" />

 <tag name="kernel.event\_listener" event="sylius.order\_item.post\_add" method="onSuccessfulAddToCart" />

</service>

```

Next thing to do is handling it by your frontend application.

##### How to disable guest checkout?[¶](#how-to-disable-guest-checkout "Permalink to this headline")

Sometimes, depending on your use case, you may want to resign from the guest checkout feature provided by Sylius.

In order to require users to have an account and be logged in before they can make an order in your shop,

you have to turn on the firewalls on the `/checkout` urls.

To achieve that simple add this path to `access\_control` in the `security.yaml` file.

```

# config/packages/security.yaml

security:

 access\_control:

 - { path: "%sylius.security.shop\_regex%/checkout", role: ROLE\_USER }

```

That will do the trick. Now, when a guest user tries to click the checkout button in the cart,

they will be redirected to the login/registration page, where after they sign in/sign up they

will be redirected to the checkout addressing step.

##### How to disable guest checkout in new API?[¶](#how-to-disable-guest-checkout-in-new-api "Permalink to this headline")

Our new API let’s any anonymous user to proceed through the checkout out of the box (of course if API is enabled).

In order to disable checking out for anonymous user you have to change the same file but use different routes.

Here we will let user to only:

> 

> * Pick up a new cart

> * Have access to it by its token value

> * Let the user add items to the cart

> 

> 

> 

We will modify the same file as in example on top, the `security.yaml`:

```

# config/packages/security.yaml

security:

 access\_control:

 - { path: "%sylius.security.new\_api\_shop\_regex%/orders", method: POST, GET , role: IS\_AUTHENTICATED\_ANONYMOUSLY }

 - { path: "%sylius.security.new\_api\_shop\_regex%/orders/.\*/items", method: POST , role: IS\_AUTHENTICATED\_ANONYMOUSLY }

 - { path: "%sylius.security.new\_api\_shop\_regex%/.\*", role: ROLE\_USER }

```

Warning

The “main” path (in this example `"%sylius.security.new\_api\_shop\_regex%/.\*"` ) should be at the very end of the configuration

with the same route, otherwise this would not work.

Now when an anonymous user will try to use other checkout routes they will be informed that they are not authenticated:

```

# response

{

 "code": 401,

 "message": "JWT Token not found"

}

```

###### Learn more[¶](#learn-more "Permalink to this headline")

* [Sylius Checkout](index.html#document-book/orders/checkout)

##### How to disable localised URLs?[¶](#how-to-disable-localised-urls "Permalink to this headline")

URLs in Sylius are localised, this means they contain the `/locale` prefix with the current locale.

For example when the `English (United States)` locale is currently chosen in the channel, the URL of homepage will

look like that `localhost:8000/en\_US/`.

If you do not need localised URLs, this guide will help you to disable this feature.

**1.** Customise the application routing in the `config/routes/sylius\_shop.yaml`.

Replace:

```

# config/routes/sylius\_shop.yaml

sylius\_shop:

 resource: "@SyliusShopBundle/Resources/config/routing.yml"

 prefix: /{\_locale}

 requirements:

 \_locale: ^[A-Za-z]{2,4}(\_([A-Za-z]{4}|[0-9]{3}))?(\_([A-Za-z]{2}|[0-9]{3}))?$

sylius\_shop\_payum:

 resource: "@SyliusShopBundle/Resources/config/routing/payum.yml"

sylius\_shop\_default\_locale:

 path: /

 methods: [GET]

 defaults:

 \_controller: sylius.controller.shop.locale\_switch:switchAction

```

With:

```

# config/routes/sylius\_shop.yaml

sylius\_shop:

 resource: "@SyliusShopBundle/Resources/config/routing.yml"

sylius\_shop\_payum:

 resource: "@SyliusShopBundle/Resources/config/routing/payum.yml"

```

**2.** Customise SyliusShopBundle to use storage-based locale switching in the `config/packages/\_sylius.yaml`.

Replace :

```

# config/packages/\_sylius.yaml

sylius\_shop:

 product\_grid:

 include\_all\_descendants: true

```

With:

```

# config/packages/\_sylius.yaml

sylius\_shop:

 product\_grid:

 include\_all\_descendants: true

 locale\_switcher: storage

```

**3.** Adjust Sylius’ security shop regex in the `config/packages/security.yaml`

Add on the top of the file:

```

# config/packages/security.yaml

parameters:

 sylius.security.shop\_regex: "^(?:/(?!%sylius\_admin.path\_name%|api/.\*|api$|media/.\*)[^/]++)?"

```

So the file should look like this:

```

# config/packages/security.yaml

parameters:

 sylius.security.shop\_regex: "^(?:/(?!%sylius\_admin.path\_name%|api/.\*|api$|media/.\*)[^/]++)?"

security:

# rest of the file

```

##### How to render a menu of taxons (categories) in a view?[¶](#how-to-render-a-menu-of-taxons-categories-in-a-view "Permalink to this headline")

The way of rendering a menu of taxons is a supereasy reusable action, that you can adapt into any place you need.

###### How does it look like?[¶](#how-does-it-look-like "Permalink to this headline")

That’s a menu that you will find on the default Sylius homepage:

![_images/taxons_menu.png](_images/taxons_menu.png)

###### How to do it?[¶](#how-to-do-it "Permalink to this headline")

You can render such a menu wherever you have access to a `category` variable in the view, but also anywhere else.

The `findChildren` method of **TaxonRepository** takes a `parentCode` and nullable `locale`.

If `locale` parameter is not null the method returns also taxon’s translation based on given `locale`.

To render a simple menu of categories in any twig template use:

```

{{ render(url('sylius\_shop\_partial\_taxon\_index\_by\_code', {'code': 'category', 'template': '@SyliusShop/Taxon/\_horizontalMenu.html.twig'})) }}

```

You can of course customize the template or enclose the menu into html to make it look better.

That’s all. Done!

###### Learn more[¶](#learn-more "Permalink to this headline")

* [The Customization Guide](index.html#document-customization/index)

##### How to embed a list of products into a view?[¶](#how-to-embed-a-list-of-products-into-a-view "Permalink to this headline")

Let’s imagine that you would like to render **a list of 5 latest products by a chosen taxon**. Such an action can take place

on the category page. Here are the steps that you will need to take:

###### Create a new method for the product repository[¶](#create-a-new-method-for-the-product-repository "Permalink to this headline")

To cover the usecase we have imagined we will need a new method on the product repository: `findLatestByChannelAndTaxonCode()`.

Tip

First learn how to customize repositories in [the customization docs here](index.html#document-customization/repository).

The new repository method will take a channel object (retrieved from channel context), a taxon code and the count of items that you want to find.

Your extending repository class should look like that:

```

<?php

namespace App\Repository;

use Sylius\Bundle\CoreBundle\Doctrine\ORM\ProductRepository as BaseProductRepository;

use Sylius\Component\Core\Model\ChannelInterface;

class ProductRepository extends BaseProductRepository

{

    /\*\*

 \* {@inheritdoc}

 \*/

    public function findLatestByChannelAndTaxonCode(ChannelInterface $channel, $code, $count)

        {

            return $this->createQueryBuilder('o')

                ->innerJoin('o.channels', 'channel')

                ->andWhere('o.enabled = :enabled')

                ->andWhere('channel = :channel')

                ->innerJoin('o.productTaxons', 'productTaxons')

                ->addOrderBy('productTaxons.position', 'asc')

                ->innerJoin('productTaxons.taxon', 'taxon')

                ->andWhere('taxon.code = :code')

                ->setParameter('code', $code)

                ->setParameter('channel', $channel)

                ->setParameter('enabled', true)

                ->setMaxResults($count)

                ->getQuery()

                ->getResult();

        }

}

```

And should be registered in the `config/packages/sylius\_product.yaml` just like that:

```

sylius\_product:

 resources:

 product:

 classes:

 repository: App\Repository\ProductRepository

```

###### Configure routing for the action of products rendering[¶](#configure-routing-for-the-action-of-products-rendering "Permalink to this headline")

To be able to render a partial with the retrieved products configure routing for it in the `config/routes.yaml`:

```

# config/routes.yaml

app\_shop\_partial\_product\_index\_latest\_by\_taxon\_code:

 path: /latest/{code}/{count} # configure a new path that has all the needed variables

 methods: [GET]

 defaults:

 \_controller: sylius.controller.product::indexAction # you make a call on the Product Controller's index action

 \_sylius:

 template: $template

 repository:

 method: findLatestByChannelAndTaxonCode # here use the new repository method

 arguments:

 - "expr:service('sylius.context.channel').getChannel()"

 - $code

 - $count

```

###### Render the result of your new path in a template[¶](#render-the-result-of-your-new-path-in-a-template "Permalink to this headline")

Having a new path, you can call it in a twig template that has acces to a taxon. Remember that you need to have your **taxon as a variable available there**.

Render the list using a simple built-in template to try it out.

```

{{ render(url('app\_shop\_partial\_product\_index\_latest\_by\_taxon\_code', {'code': taxon.code, 'count': 5, 'template': '@SyliusShop/Product/\_horizontalList.html.twig'})) }}

```

Done. In the taxon view where you have rendered the new url you will see a simple list of 5 products from this taxon, ordered by position.

###### Learn more[¶](#learn-more "Permalink to this headline")

* [The Customization Guide](index.html#document-customization/index)

##### How to add Facebook login?[¶](#how-to-add-facebook-login "Permalink to this headline")

For integrating social login functionalities Sylius uses the [HWIOAuthBundle](https://github.com/hwi/HWIOAuthBundle/blob/master/Resources/doc/index.md).

Here you will find the tutorial for integrating Facebook login into Sylius:

###### Set up the HWIOAuthBundle[¶](#set-up-the-hwioauthbundle "Permalink to this headline")

* Add HWIOAuthBundle to your project:

```

composer require hwi/oauth-bundle php-http/httplug-bundle

```

php-http/httplug-bundle is optional, require this dependency if you don’t want to provide your own services.

For more information, please visit [Setting up HWIOAuthBundle](https://github.com/hwi/HWIOAuthBundle/blob/master/Resources/doc/1-setting_up_the_bundle.md#a-add-hwioauthbundle-to-your-project).

* Enable the bundle:

```

// config/bundles.php

return [

    // ...

    Http\HttplugBundle\HttplugBundle::class => ['all' => true], // If you require the php-http/httplug-bundle package.

    HWI\Bundle\OAuthBundle\HWIOAuthBundle::class => ['all' => true],

];

```

* Import the routing:

```

# config/routes.yaml

hwi\_oauth\_redirect:

 resource: "@HWIOAuthBundle/Resources/config/routing/redirect.xml"

 prefix: /connect

hwi\_oauth\_connect:

 resource: "@HWIOAuthBundle/Resources/config/routing/connect.xml"

 prefix: /connect

hwi\_oauth\_login:

 resource: "@HWIOAuthBundle/Resources/config/routing/login.xml"

 prefix: /login

facebook:

 path: /login/check-facebook

```

###### Configure the connection to Facebook[¶](#configure-the-connection-to-facebook "Permalink to this headline")

Note

To properly connect to Facebook you will need a [Facebook developer account](https://developers.facebook.com).

Having an account create a new [app for your website](https://developers.facebook.com/quickstarts/?platform=web).

In your app dashboard you will have the `client\_id` (App ID) and the `client\_secret` (App Secret),

which are needed for the configuration.

```

# config/packages/hwi\_oauth.yaml

hwi\_oauth:

 firewall\_names: [shop]

 resource\_owners:

 facebook:

 type: facebook

 client\_id: <client\_id>

 client\_secret: <client\_secret>

 scope: "email"

```

Sylius uses email as the username, that’s why we choose emails as `scope` for this connection.

Tip

If you cannot connect to your localhost with the Facebook app, configure its settings in such a way:

* **App Domain**: `localhost`

* Click `+Add Platform` and choose “Website” type.

* Provide the **Site URL** of the platform - your local server on which you run Sylius: `https://localhost:8000`

Alternatively, you could temporarily expose your localhost to be publicly accessible, using a tool like [ngrok](https://ngrok.com/).

Facebook app configuration would be similar to:

* **App Domain**: `abcde12345.ngrok.io`

* **Site URL** `https://abcde12345.ngrok.io`

###### Configure the security layer[¶](#configure-the-security-layer "Permalink to this headline")

As Sylius already has a service that implements the **OAuthAwareUserProviderInterface** - `sylius.oauth.user\_provider` - we can only

configure the oauth firewall.

Under the `security: firewalls: shop:` keys in the `security.yaml` configure like below:

```

# config/packages/security.yaml

security:

 firewalls:

 shop:

 oauth:

 resource\_owners:

 facebook: "/login/check-facebook"

 login\_path: sylius\_shop\_login

 use\_forward: false

 failure\_path: sylius\_shop\_login

 oauth\_user\_provider:

 service: sylius.oauth.user\_provider

 anonymous: true

```

###### Add facebook login button[¶](#add-facebook-login-button "Permalink to this headline")

You can for instance override the login template (`SyliusShopBundle/Resources/views/login.html.twig`) in the `templates/SyliusShopBundle/login.html.twig`

and add these lines to be able to login via Facebook.

```

<a href="{{ path('hwi\_oauth\_service\_redirect', {'service': 'facebook' }) }}">

 <span>Login with Facebook</span>

</a>

```

**Done!**

###### Learn more[¶](#learn-more "Permalink to this headline")

* [HWIOAuthBundle documentation](https://github.com/hwi/HWIOAuthBundle/blob/master/Resources/doc/index.md)

##### How to manage content in Sylius?[¶](#how-to-manage-content-in-sylius "Permalink to this headline")

###### Why do you need content management system?[¶](#why-do-you-need-content-management-system "Permalink to this headline")

Content management is one of the most important business aspects of modern eCommerce apps.

Providing store updates like new blog pages, banners and promotion images is responsible for building the conversion rate

either for new and existing clients.

###### Content management in Sylius[¶](#content-management-in-sylius "Permalink to this headline")

Sylius standard app does not come with a content management system. Our community has taken care of it.

As Sylius does have a convenient dev oriented plugin environment, the developers from [BitBag](https://bitbag.shop) decided to develop

their flexible CMS module. You can find it [here](https://github.com/BitBagCommerce/SyliusCmsPlugin).

Tip

The whole plugin has its own [demo page](https://cms.bitbag.shop/) with specific use cases. You can access

the [admin panel](https://cms.bitbag.shop/admin/)

with `login: sylius, password: sylius` credentials.

Inside the plugin, you will find:

* HTML, image and text blocks you can place in each Twig template

* Page resources

* Sections which you can use to create a blog, customer information, etc.

* FAQ module

A very handy feature of this plugin is that you can customize it for your specific needs like you do with each [Sylius model](index.html#document-customization/model).

###### Installation & usage[¶](#installation-usage "Permalink to this headline")

Find out more about how to install the plugin on [GitHub](https://github.com/BitBagCommerce/SyliusCmsPlugin) in the README file.

###### Learn more[¶](#learn-more "Permalink to this headline")

* [How to create a plugin for Sylius?](index.html#document-book/plugins/creating-plugin)

* [BitBag plugins](https://github.com/BitBagCommerce)

* [FriendsOfSylius plugins](https://github.com/FriendsOfSylius/SyliusGoose)

##### How to use Vue Storefront PWA with Sylius?[¶](#how-to-use-vue-storefront-pwa-with-sylius "Permalink to this headline")

Note

[What are PWAs (Progressive Web Apps)?](https://en.wikipedia.org/wiki/Progressive_web_app)

###### 2. What is Vue Storefront?[¶](#what-is-vue-storefront "Permalink to this headline")

Vue Storefront is an open-source, platform-agnostic, Progressive Web App (PWA) framework

for building e-commerce applications. It is built using Vue.js for building user interfaces.

You can think about it as a USB standard for eCommerce frontends - with Vue Storefront

you get what is required by a PWA standard in a nice, platform-agnostic environment meaning

it could be connected to any eCommerce backend, including Sylius.

###### 3. How to use Vue Storefront with Sylius?[¶](#how-to-use-vue-storefront-with-sylius "Permalink to this headline")

In order to use Vue Storefront with Sylius you can use the official integration approved

by both Sylius and Vue Storefront. The integration was built by [BitBag](https://www.bitbag.io),

Sylius’ leading partner.

The backend part of the integration can be accessed in the GitHub repository

at [BitBag’s organization](https://github.com/BitBagCommerce/SyliusVueStorefront2Plugin).

This part adds all the necessary GraphQL endpoints to Sylius, making it compatible with the Vue Storefront standards.

The frontend part could be found under the same organization’s package named

[SyliusVueStorefront2Frontend package](https://github.com/BitBagCommerce/SyliusVueStorefront2Frontend).

If you want to see it in action check out the [VSF Demo](https://vsf2-demo.bitbag.io).

###### 4. Installation - the backend part[¶](#installation-the-backend-part "Permalink to this headline")

This part refers to installing the API backend, based on the [SyliusVueStorefront2Plugin](https://github.com/BitBagCommerce/SyliusVueStorefront2Plugin).

Original content could be accessed at [this](https://github.com/BitBagCommerce/SyliusVueStorefront2Plugin/blob/main/doc/installation.md)

repository page.

* Require plugin with composer:

```

composer require ``bitbag/vue-storefront2-plugin``

```

* Add plugin dependencies to your `config/bundles.php` file:

```

return [

 ...

    BitBag\SyliusVueStorefront2Plugin\BitBagSyliusVueStorefront2Plugin::class => ['all' => true],

];

```

* Enable API in `config/services.yaml`:

```

sylius\_api:

 enabled: true

```

* Add plugin mapping path to your `config/packages/api\_platform.yaml` file as the last element:

```

api\_platform:

 mapping:

 paths:

 - '%kernel.project\_dir%/vendor/bitbag/vue-storefront2-plugin/src/Resources/api\_resources'

```

* Import serialization files in `config/packages/framework.yaml` file:

```

framework:

 serializer:

 mapping:

 paths:

 - '%kernel.project\_dir%/vendor/bitbag/vue-storefront2-plugin/src/Resources/serialization'

```

* Import plugin config files in `config/packages/bitbag\_sylius\_vue\_storefront2\_plugin.yaml`:

```

imports:

 - { resource: "@BitBagSyliusVueStorefront2Plugin/Resources/config/services.xml" }

```

You are free to adjust two parameters in the same file:

```

bitbag\_sylius\_vue\_storefront2:

 refresh\_token\_lifespan: 2592000 # default value

 test\_endpoint: 'http://127.0.0.1:8080/api/v2/graphql' # default value

```

* Add some external Doctrine mappings:

```

doctrine:

 orm:

 mappings:

 VueStorefront2:

 is\_bundle: false

 type: xml

 dir: '%kernel.project\_dir%/vendor/bitbag/vue-storefront2-plugin/src/Resources/doctrine/model'

 prefix: 'BitBag\SyliusVueStorefront2Plugin\Model'

 alias: BitBag\SyliusVueStorefront2Plugin

```

* Change the Sylius Taxon repository class to add some queries required by GraphQL in `config/packages/\_sylius.yaml`:

```

sylius\_taxonomy:

 resources:

 taxon:

 classes:

 repository: BitBag\SyliusVueStorefront2Plugin\Doctrine\Repository\TaxonRepository

```

* If you’re extending Sylius ProductAttributeValue entity:

Please use our trait inside: `BitBag\SyliusVueStorefront2Plugin\Model\ProductAttributeValueTrait`.

Otherwise, please create an entity, which uses the trait and setup the Sylius resource in `config/packages/\_sylius.yaml`.

Read more on how to customize models in a [different part of Sylius docs](https://sylius-older.readthedocs.io/en/latest/customization/model.html)

if you are not familiar with the process yet.

```

sylius\_attribute:

 driver: doctrine/orm

 resources:

 product:

 subject: Sylius\Component\Core\Model\Product

 attribute\_value:

 classes:

 model: App\Entity\ProductAttributeValue

```

* Add a new column to the ProductAttributeValue entity in your Doctrine configuration file:

We are using XML for Doctrine mappings but you are free to rewrite it to Annotations or YAML.

```

<?xml version="1.0" encoding="UTF-8"?>

<doctrine-mapping

 xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"

 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

 xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping

 http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd"

>

 <entity name="App\Entity\ProductAttributeValue" table="sylius\_product\_attribute\_value">

 <indexes>

 <index name="locale\_code" columns="locale\_code" />

 </indexes>

 </entity>

</doctrine-mapping>

```

* Import routing in `config/routes.yaml`:

```

bitbag\_sylius\_vue\_storefront2\_plugin:

 resource: "@BitBagSyliusVueStorefront2Plugin/Resources/config/routing.yml"

```

The process seems a bit complex but is required to set up everything properly.

If something went wrong in your instance at this stage make sure you followed this doc properly step by step.

and check this sample configuration in the plugin environment [plugin environment](https://github.com/BitBagCommerce/SyliusVueStorefront2Plugin/tree/main/tests/Application).

###### 4. Installation - the frontend part[¶](#installation-the-frontend-part "Permalink to this headline")

* Clone the frontend repository:

```

git clone git@github.com:BitBagCommerce/SyliusVueStorefront2Frontend.git && cd SyliusVueStorefront2Frontend

```

* Copy the `packages/theme/.env.dist` file to `packages/theme/.env` and configure your environment in the file:

* Install dependencies:

```

yarn install

```

* Run the application in dev mode:

```

yarn dev

```

For production mode use `yarn start` command instead.

That is it. You are now able to start your journey with Vue Storefront in your Sylius instance.

For more details follow [official Vue Storefront documentation](https://docs.vuestorefront.io/v2/).

* [How to customize Sylius Checkout?](index.html#document-cookbook/shop/checkout)

* [How to disable guest checkout?](index.html#document-cookbook/shop/disabling-guest-checkout)

* [How to add Facebook login?](index.html#document-cookbook/shop/facebook-login)

* [How to change a redirect after the add to cart action?](index.html#document-cookbook/shop/custom-redirect-after-cart-add-action)

* [How to render a menu of taxons (categories) in a view?](index.html#document-cookbook/shop/taxons-menu)

* [How to embed a list of products into a view?](index.html#document-cookbook/shop/embedding-products)

* [How to disable localised URLs?](index.html#document-cookbook/shop/disabling-localised-urls)

* [How to manage content in Sylius?](index.html#document-cookbook/shop/cms)

* [How to use Vue Storefront PWA with Sylius?](index.html#document-cookbook/shop/vue-storefront)

#### Payments[¶](#payments "Permalink to this headline")

##### How to configure PayPal Express Checkout?[¶](#how-to-configure-paypal-express-checkout "Permalink to this headline")

Warning

PayPal Express Checkout integration is deprecated. Take a look at the

[PayPal Commerce Platform](https://github.com/Sylius/PayPalPlugin) integration, which is now the default

PayPal-related gateway for Sylius.

One of the most frequently used payment methods in e-commerce is PayPal. Its configuration in Sylius is really simple.

###### Add a payment method with the Paypal Express gateway in the Admin Panel[¶](#add-a-payment-method-with-the-paypal-express-gateway-in-the-admin-panel "Permalink to this headline")

Note

To test this configuration properly you will need a [developer account on Paypal](https://developer.paypal.com).

* Create a new payment method choosing `Paypal Express Checkout` gateway from the gateways choice dropdown and enable it for chosen channels.

Go to the `https://localhost:8000/admin/payment-methods/new/paypal\_express\_checkout` url.

![_images/paypal_express_create.png](_images/paypal_express_create.png)

* Fill in the Paypal configuration form with your developer account data (`username`, `password` and `signature`).

* Save the new payment method.

###### Choosing Paypal Express method in Checkout[¶](#choosing-paypal-express-method-in-checkout "Permalink to this headline")

From now on Paypal Express will be available in Checkout in the channel you have created it for.

![_images/paypal_express_checkout.png](_images/paypal_express_checkout.png)

**Done!**

###### Learn more[¶](#learn-more "Permalink to this headline")

* [Payments concept documentation](index.html#document-book/orders/payments)

* [Payum - Project Documentation](https://github.com/Payum/Payum/blob/master/src/Payum/Core/Resources/docs/index.md)

Warning

On September 14, 2019 the Strong Customer Authentication (SCA) requirement has been introduced.

The implementation provided by Sylius Core was not *SCA Ready* and has been deprecated.

Please have a look at the [official documentation of Stripe regarding this topic](https://stripe.com/guides/strong-customer-authentication).

##### How to configure Stripe Credit Card payment?[¶](#how-to-configure-stripe-credit-card-payment "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

One of very important payment methods in e-commerce are credit cards. Payments via credit card are in Sylius supported by [Stripe](https://stripe.com/docs).

###### Install Stripe[¶](#install-stripe "Permalink to this headline")

Stripe is not available by default in Sylius, to have it you need to add its package via composer.

```

php composer require stripe/stripe-php:~4.1

```

###### Add a payment method with the Stripe gateway in the Admin Panel[¶](#add-a-payment-method-with-the-stripe-gateway-in-the-admin-panel "Permalink to this headline")

Note

To test this configuration properly you will need a [developer account on Stripe](https://dashboard.stripe.com/register).

* Create a new payment method, choosing the `Stripe Credit Card` gateway from the gateways choice dropdown and enable it for chosen channels.

Go to the `https://localhost:8000/admin/payment-methods/new/stripe\_checkout` url.

* Fill in the Stripe configuration form with your developer account data (`publishable\_key` and `secret\_key`).

* Save the new payment method.

Tip

If your are not sure how to do it check how we do it [for Paypal in this cookbook](index.html#document-cookbook/payments/paypal).

Warning

When your project is behind a loadbalancer and uses https you probably need to configure [trusted proxies](https://symfony.com/doc/current/deployment/proxies.html). Otherwise the payment will not succeed and the user will endlessly loopback to the payment page without any notice.

###### Choosing Stripe Credit Card method in Checkout[¶](#choosing-stripe-credit-card-method-in-checkout "Permalink to this headline")

From now on Stripe Credit Card will be available in Checkout in the channel you have added it to.

**Done!**

###### Learn more[¶](#learn-more "Permalink to this headline")

* [Payments concept documentation](index.html#document-book/orders/payments)

* [Payum - Project Documentation](https://github.com/Payum/Payum/blob/master/src/Payum/Core/Resources/docs/index.md)

##### How to encrypt gateway config stored in the database?[¶](#how-to-encrypt-gateway-config-stored-in-the-database "Permalink to this headline")

**1.** Add defuse/php-encryption to your project

.. code-block:

```

composer require defuse/php-encryption

```

**2.** Generate your Defuse Secret Key by executing the following script:

```

<?php

use Defuse\Crypto\Key;

require\_once 'vendor/autoload.php';

var\_dump(Key::createNewRandomKey()->saveToAsciiSafeString());

```

**3.** Store your generated key in a environmental variable in `.env`.

```

# .env

DEFUSE_SECRET: "YOUR_GENERATED_KEY"

```

**4.** Add the following code to the application configuration in the `config/packages/payum.yaml`.

```

# config/packages/payum.yaml

payum:

 dynamic\_gateways:

 encryption:

 defuse\_secret\_key: "%env(DEFUSE\_SECRET)%"

```

**5.** Existing gateway configs will be automatically encrypted when updated. New gateway configs will be encrypted by default.

##### How to authorize a payment before capturing.[¶](#how-to-authorize-a-payment-before-capturing "Permalink to this headline")

Sometimes, due to legal constraint in some countries, you’ll want to only authorize a payment and capture it later.

###### Authorizing payments[¶](#authorizing-payments "Permalink to this headline")

Sylius supports the use of Payums payment authorization <https://github.com/Payum/Payum/blob/master/docs/symfony/authorize.md>\_

Not all payment gateways support this and it is up to the payment plugin to make use of this functionality.

To use authorize status for your payments, your plugin must set a flag in it’s GatewayConfig called use\_authorize. This is easily done with a hidden input field.

```

class MyGatewayGatewayConfigurationType extends AbstractType

{

    /\*\*

 \* {@inheritdoc}

 \*/

    public function buildForm(FormBuilderInterface $builder, array $options): void

    {

        $builder

            // Add other config fields for your gateway here.

            // Enable the use of authorize. This can also be a normal select field if the gateway supports both.

            ->add('use\_authorize', HiddenType::class, [

                'data' => 1,

            ])

        ;

    }

}

```

###### Capture payment after authorizing[¶](#capture-payment-after-authorizing "Permalink to this headline")

As an admin, you can mark the payment as captured from the order view page or through the Payments API.

Capturing the payment in the gateway is up to the plugin, which can hook into the state machine or events.

Note

For an example of how this can be implemented see [the QuickPay gateway plugin](https://github.com/Setono/SyliusQuickpayPlugin).

##### How to integrate a Payment Gateway as a Plugin?[¶](#how-to-integrate-a-payment-gateway-as-a-plugin "Permalink to this headline")

Among all possible customizations, new gateway provider is one of the most common choices.

Payment processing complexity, regional limits and the amount of potential payment providers makes it hard for Sylius

core to keep up with all possible cases. A custom payment gateway is sometimes the only choice.

In the following example, a new gateway will be configured, which will send payment details to external API.

**1.** Set up a new plugin using the [PluginSkeleton](https://github.com/Sylius/PluginSkeleton).

> 

> 

> ```

> composer create-project sylius/plugin-skeleton ProjectName

> 

> ```

> 

> 

> 

**2.** The first step in the newly created repository would be to create a new Gateway Factory.

> 

> Prepare a gateway factory class in `src/Payum/SyliusPaymentGatewayFactory.php`:

> 

> 

> 

> ```

> // src/Payum/SyliusPaymentGatewayFactory.php

> 

> <?php

> 

> declare(strict\_types=1);

> 

> namespace Acme\SyliusExamplePlugin\Payum;

> 

> use Payum\Core\Bridge\Spl\ArrayObject;

> use Payum\Core\GatewayFactory;

> 

> final class SyliusPaymentGatewayFactory extends GatewayFactory

> {

>     protected function populateConfig(ArrayObject $config): void

>     {

>         $config->defaults([

>             'payum.factory\_name' => 'sylius\_payment',

>             'payum.factory\_title' => 'Sylius Payment',

>         ]);

>     }

> }

> 

> ```

> 

> 

> And at the end of `src/Resources/config/services.xml` or `src/Resources/config/services.yaml` add such a configuration for your gateway:

> 

> 

> 

> ```

> <!-- src/Resources/config/services.xml -->

> 

> <service id="app.sylius\_payment" class="Payum\Core\Bridge\Symfony\Builder\GatewayFactoryBuilder">

>  <argument>Acme\SyliusExamplePlugin\Payum\SyliusPaymentGatewayFactory</argument>

>  <tag name="payum.gateway\_factory\_builder" factory="sylius\_payment" />

> </service>

> 

> ```

> 

> 

> 

> ```

> # src/Resources/config/services.yaml

> 

> app.sylius\_payment:

>  class: Payum\Core\Bridge\Symfony\Builder\GatewayFactoryBuilder

>  arguments: [ Acme\SyliusExamplePlugin\Payum\SyliusPaymentGatewayFactory ]

>  tags:

>  - { name: payum.gateway\_factory\_builder, factory: sylius\_payment }

> 

> ```

> 

> 

> 

**3.** Next, one should create a configuration form, where authorization

(or some additional information, like sandbox mode) can be specified.

> 

> Create the configuration type in `src/Form/Type/SyliusGatewayConfigurationType.php`:

> 

> 

> 

> ```

> // src/Form/Type/SyliusGatewayConfigurationType.php

> 

> <?php

> 

> declare(strict\_types=1);

> 

> namespace Acme\SyliusExamplePlugin\Form\Type;

> 

> use Symfony\Component\Form\AbstractType;

> use Symfony\Component\Form\Extension\Core\Type\TextType;

> use Symfony\Component\Form\FormBuilderInterface;

> 

> final class SyliusGatewayConfigurationType extends AbstractType

> {

>     public function buildForm(FormBuilderInterface $builder, array $options): void

>     {

>         $builder->add('api\_key', TextType::class);

>     }

> }

> 

> ```

> 

> 

> And add its configuration to src/Resources/config/services.xml or `src/Resources/config/services.yaml`:

> 

> 

> 

> ```

> <!-- src/Resources/config/services.xml -->

> 

> <service id="Acme\SyliusExamplePlugin\Form\Type\SyliusGatewayConfigurationType">

>  <tag name="sylius.gateway\_configuration\_type" type="sylius\_payment" label="Sylius Payment" />

>  <tag name="form.type" />

> </service>

> 

> ```

> 

> 

> 

> ```

> # src/Resources/config/services.yaml

> 

> Acme\SyliusExamplePlugin\Form\Type\SyliusGatewayConfigurationType:

>  tags:

>  - { name: sylius.gateway\_configuration\_type, type: sylius\_payment, label: 'Sylius Payment' }

>  - { name: form.type }

> 

> ```

> 

> 

> 

**4.** To introduce support for new configuration fields, we need to create a value object which will be passed to action,

so we can use an API Key provided in form.

> 

> Create a new ValueObject in `src/Payum/SyliusApi.php`:

> 

> 

> 

> ```

> // src/Payum/SyliusApi.php

> 

> <?php

> 

> declare(strict\_types=1);

> 

> namespace Acme\SyliusExamplePlugin\Payum;

> 

> final class SyliusApi

> {

>     /\*\* @var string \*/

>     private $apiKey;

> 

>     public function \_\_construct(string $apiKey)

>     {

>         $this->apiKey = $apiKey;

>     }

> 

>     public function getApiKey(): string

>     {

>         return $this->apiKey;

>     }

> }

> 

> ```

> 

> 

> In `src/Payum/SyliusPaymentGatewayFactory.php` we need to add support for newly created `SyliusApi` VO by adding

> `$config['payum.api'] = function (ArrayObject $config) { return new SyliusApi($config['api\_key']); };` at the end of

> `populateConfig` method. Adjusted `SyliusPaymentGatewayFactory` class should look like this:

> 

> 

> 

> ```

> // src/Payum/SyliusPaymentGatewayFactory.php

> 

> <?php

> 

> declare(strict\_types=1);

> 

> namespace Acme\SyliusExamplePlugin\Payum;

> 

> use Payum\Core\Bridge\Spl\ArrayObject;

> use Payum\Core\GatewayFactory;

> 

> final class SyliusPaymentGatewayFactory extends GatewayFactory

> {

>     protected function populateConfig(ArrayObject $config): void

>     {

>         $config->defaults([

>             'payum.factory\_name' => 'sylius\_payment',

>             'payum.factory\_title' => 'Sylius Payment',

>         ]);

> 

>         $config['payum.api'] = function (ArrayObject $config) {

>             return new SyliusApi($config['api\_key']);

>         };

>     }

> }

> 

> ```

> 

> 

> From now on, your new Payment Gateway should be available in the admin panel.

> 

> 

> ![_images/new_gateway_configuration_type.png](_images/new_gateway_configuration_type.png)

> 

**5.** Configure new payment method in the admin panel

> 

> ![_images/new_payment_method.png](_images/new_payment_method.png)

> 

**6.** Configure required actions

> 

> We will create two actions: CaptureAction and StatusAction. The first one will be responsible for sending data to

> an external system:

> 

> 

> 

> > 

> > * payment amount

> > * currency

> > * API key configured in the previously created form

> > 

> > 

> > 

> 

> 

> while the second one will translate HTTP codes of the Response to a proper state of payment.

> 

> 

> 

**6.1.** Create `StatusAction` and add it to the `SyliusPaymentGatewayFactory`

> 

> In a gateway factory class in `src/Payum/SyliusPaymentGatewayFactory.php` we need to add

> `'payum.action.status' => new StatusAction(),` to config defaults. Adjusted `SyliusPaymentGatewayFactory` class

> should look like this:

> 

> 

> 

> ```

> // src/Payum/SyliusPaymentGatewayFactory.php

> 

> <?php

> 

> declare(strict\_types=1);

> 

> namespace Acme\SyliusExamplePlugin\Payum;

> 

> use Acme\SyliusExamplePlugin\Payum\Action\StatusAction;

> use Payum\Core\Bridge\Spl\ArrayObject;

> use Payum\Core\GatewayFactory;

> 

> final class SyliusPaymentGatewayFactory extends GatewayFactory

> {

>     protected function populateConfig(ArrayObject $config): void

>     {

>         $config->defaults([

>             'payum.factory\_name' => 'sylius\_payment',

>             'payum.factory\_title' => 'Sylius Payment',

>             'payum.action.status' => new StatusAction(),

>         ]);

> 

>         $config['payum.api'] = function (ArrayObject $config) {

>             return new SyliusApi($config['api\_key']);

>         };

>     }

> }

> 

> ```

> 

> 

> Now we need to create a `StatusAction` in `src/Payum/Action/StatusAction.php`:

> 

> 

> 

> ```

> // src/Payum/Action/StatusAction.php

> 

> <?php

> 

> declare(strict\_types=1);

> 

> namespace Acme\SyliusExamplePlugin\Payum\Action;

> 

> use Payum\Core\Action\ActionInterface;

> use Payum\Core\Exception\RequestNotSupportedException;

> use Payum\Core\Request\GetStatusInterface;

> use Sylius\Component\Core\Model\PaymentInterface as SyliusPaymentInterface;

> 

> final class StatusAction implements ActionInterface

> {

>     public function execute($request): void

>     {

>         RequestNotSupportedException::assertSupports($this, $request);

> 

>         /\*\* @var SyliusPaymentInterface $payment \*/

>         $payment = $request->getFirstModel();

> 

>         $details = $payment->getDetails();

> 

>         if (200 === $details['status']) {

>             $request->markCaptured();

> 

>             return;

>         }

> 

>         if (400 === $details['status']) {

>             $request->markFailed();

> 

>             return;

>         }

>     }

> 

>     public function supports($request): bool

>     {

>         return

>             $request instanceof GetStatusInterface &&

>             $request->getFirstModel() instanceof SyliusPaymentInterface

>         ;

>     }

> }

> 

> ```

> 

> 

> `StatusAction` will update the state of payment based on details provided by `CaptureAction`.

> Based on the value of the status code of the HTTP request, the payment status will be adjusted as follows:

> 

> 

> 

> > 

> > * HTTP 400 (Bad request) - payment has failed

> > * HTTP 200 (OK) - payment succeeded

> > 

> > 

> > 

> 

> 

> 

**6.2.** Create a service for handling the CaptureAction

> 

> 

> Warning

> 

> 

> An external request interceptor was used for training purposes. Please,

> visit [Beeceptor](https://beeceptor.com/). and supply `sylius-payment` as an endpoint name. If the service

> is not working, you can use [Post Test Server V2](https://ptsv2.com/). as well, but remember about adjusting

> the `https://sylius-payment.free.beeceptor.com` path.

> 

> 

> 

> This time we will start with creating a `CaptureAction` in `src/Payum/Action/CaptureAction.php`:

> 

> 

> 

> ```

> // src/Payum/Action/CaptureAction.php

> 

> <?php

> 

> declare(strict\_types=1);

> 

> namespace Acme\SyliusExamplePlugin\Payum\Action;

> 

> use Acme\SyliusExamplePlugin\Payum\SyliusApi;

> use GuzzleHttp\Client;

> use GuzzleHttp\Exception\RequestException;

> use Payum\Core\Action\ActionInterface;

> use Payum\Core\ApiAwareInterface;

> use Payum\Core\Exception\RequestNotSupportedException;

> use Payum\Core\Exception\UnsupportedApiException;

> use Sylius\Component\Core\Model\PaymentInterface as SyliusPaymentInterface;

> use Payum\Core\Request\Capture;

> 

> final class CaptureAction implements ActionInterface, ApiAwareInterface

> {

>     /\*\* @var Client \*/

>     private $client;

>     /\*\* @var SyliusApi \*/

>     private $api;

> 

>     public function \_\_construct(Client $client)

>     {

>         $this->client = $client;

>     }

> 

>     public function execute($request): void

>     {

>         RequestNotSupportedException::assertSupports($this, $request);

> 

>         /\*\* @var SyliusPaymentInterface $payment \*/

>         $payment = $request->getModel();

> 

>         try {

>             $response = $this->client->request('POST', 'https://sylius-payment.free.beeceptor.com', [

>                 'body' => json\_encode([

>                     'price' => $payment->getAmount(),

>                     'currency' => $payment->getCurrencyCode(),

>                     'api\_key' => $this->api->getApiKey(),

>                 ]),

>             ]);

>         } catch (RequestException $exception) {

>             $response = $exception->getResponse();

>         } finally {

>             $payment->setDetails(['status' => $response->getStatusCode()]);

>         }

>     }

> 

>     public function supports($request): bool

>     {

>         return

>             $request instanceof Capture &&

>             $request->getModel() instanceof SyliusPaymentInterface

>         ;

>     }

> 

>     public function setApi($api): void

>     {

>         if (!$api instanceof SyliusApi) {

>             throw new UnsupportedApiException('Not supported. Expected an instance of ' . SyliusApi::class);

>         }

> 

>         $this->api = $api;

>     }

> }

> 

> ```

> 

> 

> And at the end of `src/Resources/config/services.xml` or src/Resources/config/services.yaml` add such a configuration for your capture action:

> 

> 

> 

> ```

> <!-- src/Resources/config/services.xml -->

> 

> <service id="Acme\SyliusExamplePlugin\Payum\Action\CaptureAction" public=true>

>  <argument type="service" id="sylius.http\_client" />

>  <tag name="payum.action" factory="sylius\_payment" alias="payum.action.capture" />

> </service>

> 

> ```

> 

> 

> 

> ```

> # src/Resources/config/services.yaml

> 

> Acme\SyliusExamplePlugin\Payum\Action\CaptureAction:

>  public: true

>  arguments:

>  - '@sylius.http\_client'

>  tags:

>  - { name: payum.action, factory: sylius\_payment, alias: payum.action.capture }

> 

> ```

> 

> 

> Your shop is ready to handle the first checkout with your newly created gateway!

> 

> 

> 

> Tip

> 

> 

> On both previously mentioned interceptors, you may configure a status code of the response.

> Check the behavior of Sylius for 400 status code (HTTP Bad Request) as well!

> 

> 

> 

> 

###### Learn more[¶](#learn-more "Permalink to this headline")

* [Order payments documentation](index.html#document-book/orders/payments)

* [Payum documentation](https://github.com/Payum/Payum/blob/master/docs/index.md)

* [Mollie payment integration](https://github.com/BitBagCommerce/SyliusMolliePlugin/)

##### How to customize a Credit Memo?[¶](#how-to-customize-a-credit-memo "Permalink to this headline")

Customizing a downloadable credit memo is a really common task, which leverages the extendability of traditional Symfony applications.

This cookbook includes four exemplary customizations with a varying degree of difficulty and impact.

###### Customizing Credit Memo’s template[¶](#customizing-credit-memo-s-template "Permalink to this headline")

The first exemplary customization is to change background color of the heading of line items table.

**1.** Copy `vendor/sylius/refund-plugin/src/Resources/views/Download/creditMemo.html.twig` into `templates/bundles/SyliusRefundPlugin/Download/creditMemo.html.twig`.

**2.** Change CSS styling included in the template:

> 

> 

> ```

> <style>

>  /\* ... \*/

>  .credit-memo table tr.heading td { background: #0d71bb; border-bottom: 1px solid #ddd; font-weight: bold; }

>  /\* ... \*/

> </style>

> 

> ```

> 

> 

> 

###### How to change the logo in the Credit Memo?[¶](#how-to-change-the-logo-in-the-credit-memo "Permalink to this headline")

Credit Memo’s logo is by default displayed as Sylius logo.

Changing it is done by modifying the `SYLIUS\_REFUND\_LOGO\_FILE` environment variable.

You can achieve that by updating it’s path in your `.env` file like in example below:

> 

> 

> ```

> SYLIUS_REFUND_LOGO_FILE=%kernel.project_dir%/public/assets/custom-logo.png

> 

> ```

> 

> 

> 

Make sure to clear the cache each time the configuration is changed.

![_images/customized-pdf.png](_images/customized-pdf.png)

###### How to add more graphics to the Credit Memo?[¶](#how-to-add-more-graphics-to-the-credit-memo "Permalink to this headline")

In case you would like to add an extra graphic to your Credit Memo twig template, it is super important to provide access to this file.

Let’s say you would like to add second image to the Credit Memo.

You may face the problem that `wkhtmltopdf` from version 0.12.6 disables access to the local files by default.

Fortunately, there are two options to deal with it:

* Update the `config/packages/knp\_snappy.yaml` file by adding access to local files globally:

> 

> 

> ```

> knp\_snappy:

>  pdf:

>  options:

>  enable-local-file-access: true

> 

> ```

> 

> 

>

* Specify the exact list of accessible files. As you may have noticed, the logo displays correctly even though local file access is not enabled.

This is because we handle it by specifying the exact list of allowed files.

The list can be replaced with the `sylius\_refund.pdf\_generator.allowed\_files` parameter in the `config/packages/\_sylius.yaml`:

> 

> 

> ```

> sylius\_refund:

>  pdf\_generator:

>  allowed\_files:

>  - '%env(default:default\_logo\_file:resolve:SYLIUS\_REFUND\_LOGO\_FILE)%'

>  - 'path/image.png'

>  - 'directory/with/allowed/files'

> 

> ```

> 

> 

>

###### Displaying additional Customer’s data on Credit Memo[¶](#displaying-additional-customer-s-data-on-credit-memo "Permalink to this headline")

There might be some cases in which you want to get access to order or customer data.

You can access these through `creditMemo.order` or `creditMemo.order.customer` respectively.

**1.** Copy `vendor/sylius/refund-plugin/src/Resources/views/Download/creditMemo.html.twig` into `templates/bundles/SyliusRefundPlugin/Download/creditMemo.html.twig`.

**2.** Customize buyer’s data included in the template:

> 

> 

> ```

> <td>

>     {{ 'sylius_refund.ui.buyer'|trans([], 'messages', creditMemo.localeCode) }}<br/>

>     <strong>{{ from.fullName }} </strong><br/>

>     <!-- ... -->

>     {{ creditMemo.order.customer.phoneNumber }}<br/>

>     <!-- ... -->

> </td>

> 

> ```

> 

> 

> 

###### Displaying additional line item data (such as gross unit price) on Credit Memo[¶](#displaying-additional-line-item-data-such-as-gross-unit-price-on-credit-memo "Permalink to this headline")

By default, a credit memo does not include unit gross price in the line items table - however, it is provided within

line items data included with credit memo.

**1.** Copy `vendor/sylius/refund-plugin/src/Resources/views/Download/creditMemo.html.twig` into `templates/bundles/SyliusRefundPlugin/Download/creditMemo.html.twig`.

**2.** Customize products table data by adding one column in the template:

> 

> 

> ```

> <tr class="heading">

>     <!-- ... -->

>     <td>{{ 'sylius_refund.ui.unit_net_price'|trans([], 'messages', creditMemo.localeCode) }}</td>

>     <td>{{ 'app.ui.unit_gross_price'|trans([], 'messages', creditMemo.localeCode) }}</td>

>     <td>{{ 'sylius_refund.ui.net_value'|trans([], 'messages', creditMemo.localeCode) }}</td>

>     <!-- ... -->

> </tr>

> 

> {% for item in creditMemo.lineItems %}

>     <tr class="item">

>         <!-- ... -->

>         <td>{{ '%0.2f'|format(item.unitNetPrice/100) }}</td>

>         <td>{{ '%0.2f'|format(item.unitGrossPrice/100) }}</td>

>         <td>{{ '%0.2f'|format(item.netValue/100) }}</td>

>         <!-- ... -->

>     </tr>

> {% endfor %}

> 

> ```

> 

> 

> 

**3.** Add missing translations for newly added string in `translations/messages.en.yml`:

> 

> 

> ```

> app:

>  ui:

>  unit\_gross\_price: Unit gross price

> 

> ```

> 

> 

> 

###### Displaying additional elements on Credit Memo[¶](#displaying-additional-elements-on-credit-memo "Permalink to this headline")

Warning

This section applies only for RefundPlugin in version v1.0.0-RC.10 or above.

There might be a case when you want to extend the credit memo with additional field.

**1.** Copy `vendor/sylius/refund-plugin/src/Resources/views/Download/creditMemo.html.twig` into `templates/bundles/SyliusRefundPlugin/Download/creditMemo.html.twig`.

**2.** Customize credit memo template to include the reason:

> 

> 

> ```

> <div class="credit-memo">

>     Reason: {{ creditMemo.reason }}

> 

>     <!-- ... -->

> </div>

> 

> ```

> 

> 

> 

**3.** Override the default credit memo model in `src/Entity/Refund/CreditMemo.php`:

> 

> 

> ```

> <?php

> 

> declare(strict\_types=1);

> 

> namespace App\Entity\Refund;

> 

> use Doctrine\ORM\Mapping as ORM;

> use Sylius\RefundPlugin\Entity\CreditMemo as BaseCreditMemo;

> 

> /\*\*

>  \* @ORM\Entity

>  \* @ORM\Table(name="sylius\_refund\_credit\_memo")

>  \*/

> class CreditMemo extends BaseCreditMemo

> {

>     /\*\*

>  \* @ORM\Column

>  \*

>  \* @var string|null

>  \*/

>     private $reason;

> 

>     public function getReason(): ?string

>     {

>         return $this->reason;

>     }

> 

>     public function setReason(?string $reason): void

>     {

>         $this->reason = $reason;

>     }

> }

> 

> ```

> 

> 

> 

**4.** Configure ResourceBundle to use overridden model in `config/packages/sylius\_refund.yaml`:

> 

> 

> ```

> sylius\_resource:

>  resources:

>  sylius\_refund.credit\_memo:

>  classes:

>  model: App\Entity\Refund\CreditMemo

> 

> ```

> 

> 

> 

**5.** Assuming that your database was up-to-date before these changes, create a proper migration and use it:

```

php bin/console doctrine:migrations:diff

php bin/console doctrine:migrations:migrate

```

**6.** Decorate credit memo generator to set the reason while generating the invoice. Create a class in `src/Refund/CreditMemoGenerator.php`:

> 

> 

> ```

> <?php

> 

> declare(strict\_types=1);

> 

> namespace App\Refund;

> 

> use App\Entity\Refund\CreditMemo;

> use Sylius\Component\Core\Model\OrderInterface;

> use Sylius\RefundPlugin\Entity\CreditMemoInterface;

> use Sylius\RefundPlugin\Generator\CreditMemoGeneratorInterface;

> 

> final class CreditMemoGenerator implements CreditMemoGeneratorInterface

> {

>     /\*\* @var CreditMemoGeneratorInterface \*/

>     private $creditMemoGenerator;

> 

>     public function \_\_construct(CreditMemoGeneratorInterface $creditMemoGenerator)

>     {

>         $this->creditMemoGenerator = $creditMemoGenerator;

>     }

> 

>     public function generate(OrderInterface $order, int $total, array $units, array $shipments, string $comment): CreditMemoInterface

>     {

>         /\*\* @var CreditMemo $creditMemo \*/

>         $creditMemo = $this->creditMemoGenerator->generate($order, $total, $units, $shipments, $comment);

>         $creditMemo->setReason('Charged too much');

> 

>         return $creditMemo;

>     }

> }

> 

> ```

> 

> 

> 

**7.** And then configure Symfony’s dependency injection to use that class in `config/services.yaml`:

> 

> 

> ```

> services:

>  # ...

> 

>  App\Refund\CreditMemoGenerator:

>  decorates: 'Sylius\RefundPlugin\Generator\CreditMemoGenerator'

>  arguments:

>  - '@App\Refund\CreditMemoGenerator.inner'

> 

> ```

> 

> 

> 

###### Displaying additional elements on Credit Memo by embedding a controller[¶](#displaying-additional-elements-on-credit-memo-by-embedding-a-controller "Permalink to this headline")

There might be times when you want to calculate some extra data on-the-fly or get some which are not connected on

entity level with credit memo.

**1.** Copy `vendor/sylius/refund-plugin/src/Resources/views/Download/creditMemo.html.twig` into `templates/bundles/SyliusRefundPlugin/Download/creditMemo.html.twig`.

**2.** Embed a controller in the credit memo template:

> 

> 

> ```

> <div class="credit-memo">

>     Some unique data: {{ render(controller('App\\Controller\\FooController::extraData', { 'creditMemo': creditMemo })) }}

> 

>     <!-- ... -->

> </div>

> 

> ```

> 

> 

> 

**3.** Create the referenced controller in a file called `src/Controller/FooController.php`:

> 

> 

> ```

> <?php

> 

> declare(strict\_types=1);

> 

> namespace App\Controller;

> 

> use Sylius\RefundPlugin\Entity\CreditMemoInterface;

> use Symfony\Component\HttpFoundation\Response;

> use Twig\Environment;

> 

> final class FooController

> {

>     /\*\* @var Environment \*/

>     private $twig;

> 

>     public function \_\_construct(Environment $twig)

>     {

>         $this->twig = $twig;

>     }

> 

>     public function extraData(CreditMemoInterface $creditMemo): Response

>     {

>         return new Response($this->twig->render('CreditMemo/extraData.html.twig', [

>             'creditMemo' => $creditMemo,

>             // Customise it to your needs, this one makes no sense

>             'extraData' => $creditMemo->getNetValueTotal() \* random\_int(0, 42),

>         ]));

>     }

> }

> 

> ```

> 

> 

> 

**4.** Created the template referenced in the controller in a file called `templates/CreditMemo/extraData.html.twig`:

> 

> 

> ```

> <strong>{{ extraData }}</strong>

> 

> ```

> 

> 

> 

##### How to have the Credit Memos created after the Refund Payments?[¶](#how-to-have-the-credit-memos-created-after-the-refund-payments "Permalink to this headline")

Note

This cookbook requires having the [Refund Plugin](https://github.com/Sylius/RefundPlugin) installed in your application.

Tip

Read about the features of Refund Plugin in the documentation [here](index.html#document-book/orders/refunds).

By default the refund payments are created right after the credit memos have been created.

Although one may need to change it due to business requirements.

Let’s see how to achieve this!

###### Credit Memos created after the Refund Payments[¶](#credit-memos-created-after-the-refund-payments "Permalink to this headline")

All you need to do is to override the priority in service declaration in the config file.

Give the CreditMemoProcessManager, which is responsible for the Credit Memo generation, the lowest possible priority (`0`).

The priorites of services are interpreted in the descending order, thus this change will make it run after the service responsible for

Refund Payments.

```

# config/services.yaml

services:

 Sylius\RefundPlugin\ProcessManager\CreditMemoProcessManager:

 arguments: ['@sylius.command\_bus']

 tags:

 - { name: sylius\_refund.units\_refunded.process\_step, priority: 0 }

```

You can also achieve it the other way round, by giving the service responsible for Payments

- `RefundPaymentProcessManager` - the highest priority, let it be `200`.

```

# config/services.yaml

services:

 Sylius\RefundPlugin\ProcessManager\RefundPaymentProcessManager:

 arguments:

 - '@Sylius\RefundPlugin\StateResolver\OrderFullyRefundedStateResolverInterface'

 - '@Sylius\RefundPlugin\Provider\RelatedPaymentIdProviderInterface'

 - '@Sylius\RefundPlugin\Factory\RefundPaymentFactoryInterface'

 - '@doctrine.orm.default\_entity\_manager'

 - '@sylius.event\_bus'

 tags:

 - { name: sylius\_refund.units\_refunded.process\_step, priority: 200 }

```

The process managers will work according to the new priorities (descending), and as a result, all Refund Payments will be created before their Credit Memos.

Tip

You can find the default config of all the services run in the the refund process in

`%kernel.project\_dir%/vendor/sylius/refund-plugin/src/Resources/config/services/event\_bus.xml`

tagged as `sylius\_refund.units\_refunded.process\_step`

###### Learn more[¶](#learn-more "Permalink to this headline")

* [The refund process - details](https://github.com/Sylius/RefundPlugin#post-refunding-process)

##### How to customize the refund form?[¶](#how-to-customize-the-refund-form "Permalink to this headline")

Note

This cookbook describes customization of a feature available only with [Sylius/RefundPlugin](https://github.com/Sylius/RefundPlugin/) installed.

A refund form is the form in which, as an Administrator, you can specify the exact amounts of money that will be refunded to a Customer.

###### Why would you customize the refund form?[¶](#why-would-you-customize-the-refund-form "Permalink to this headline")

Refund Plugin provides a generic solution for refunding orders, it is enough for a basic refund but many shops need more custom functionalities.

For example, one may need to add refund payments scheduling, as they may be paid once a month.

###### How to add a field to the refund form?[¶](#how-to-add-a-field-to-the-refund-form "Permalink to this headline")

The refund form is a form used to create the Refund Payment, thus in order to add a field to this form,

you need to first add it to the Refund Payment’s model.

Refunds are processed with such a flow: `command -> handler -> event -> listener`, and this flow we will also need to customize in order to process the data from the new field.

In this customization, we will be extending the refund form with a `scheduledAt` field,

which might be used then for scheduling the payments in the payment gateway.

**1. Add the custom field to the Refund Payment:**

Extended refund payment should look like this:

```

<?php

declare(strict\_types=1);

namespace App\Entity\Refund;

use Doctrine\ORM\Mapping as ORM;

use Sylius\RefundPlugin\Entity\RefundPayment as BaseRefundPayment;

/\*\*

 \* @ORM\Entity

 \* @ORM\Table(name="sylius\_refund\_refund\_payment")

 \*/

class RefundPayment extends BaseRefundPayment implements RefundPaymentInterface

{

    /\*\*

 \* @var \DateTimeInterface|null

 \*

 \* @ORM\Column(type="datetime", nullable="true", name="scheduled\_at")

 \*/

    protected $scheduledAt;

    public function getScheduledAt(): ?\DateTimeInterface

    {

        return $this->scheduledAt;

    }

    public function setScheduledAt(\DateTimeInterface $scheduledAt): void

    {

        $this->scheduledAt = $scheduledAt;

    }

}

```

It should implement a new interface:

```

<?php

declare(strict\_types=1);

namespace App\Entity\Refund;

use Sylius\RefundPlugin\Entity\RefundPaymentInterface as BaseRefundPaymentInterface;

interface RefundPaymentInterface extends BaseRefundPaymentInterface

{

    public function getScheduledAt(): ?\DateTimeInterface;

    public function setScheduledAt(\DateTimeInterface $date): void;

}

```

Remember to update resource configuration:

```

# config/packages/sylius\_refund.yaml

sylius\_resource:

 resources:

 sylius\_refund.refund\_payment:

 classes:

 model: App\Entity\Refund\RefundPayment

 interface: App\Entity\Refund\RefundPaymentInterface

```

And update the database:

```

php bin/console doctrine:migrations:diff

php bin/console doctrine:migrations:migrate

```

**2. Modify the refund form:**

Once we have the new field on the Refund Payment, we will need to display its input on the refund form.

We need to overwrite the template `orderRefunds.html.twig` from Refund Plugin.

To achieve that copy the entire `orderRefunds.html.twig` to `templates/bundles/SyliusRefundPlugin/orderRefunds.html.twig`:

```

mkdir templates/bundles/SyliusRefundPlugin

cp vendor/sylius/refund-plugin/src/Resources/views/orderRefunds.html.twig templates/bundles/SyliusRefundPlugin

```

Then add:

```

<div class="field">

 <label for="scheduled-at">Scheduled at</label>

 <input type="date" name="sylius\_scheduled\_at" id="scheduled-at" />

</div>

```

**3. Adjust the ``RefundUnits`` command:**

We want the refund payments to be created with our extra `scheduledAt` date, therefore we need to provide this data in command,

We will extend the `RefundUnits` command from Refund Plugin and add the new value:

```

<?php

declare(strict\_types=1);

namespace App\Command;

use Sylius\RefundPlugin\Command\RefundUnits as BaseRefundUnits;

final class RefundUnits extends BaseRefundUnits

{

    /\*\* @var \DateTimeInterface|null \*/

    private $scheduledAt;

    public function \_\_construct(

        string $orderNumber,

        array $units,

        array $shipments,

        int $paymentMethodId,

        string $comment,

        ?\DateTimeInterface $scheduledAt

    ) {

        parent::\_\_construct($orderNumber, $units, $shipments, $paymentMethodId, $comment);

        $this->scheduledAt = $scheduledAt;

    }

    public function getScheduledAt(): ?\DateTimeInterface

    {

        return $this->scheduledAt;

    }

    public function setScheduledAt(?\DateTimeInterface $scheduledAt): void

    {

        $this->scheduledAt = $scheduledAt;

    }

}

```

**4. Update the ``RefundUnitsCommandCreator``:**

The controller related to the refund form dispatches the `RefundUnits` command, and there is a service that creates a command from request,

so we need to overwrite the `Sylius\RefundPlugin\Creator\RefundUnitsCommandCreator`:

```

<?php

declare(strict\_types=1);

namespace App\Creator;

use App\Command\RefundUnits;

use Sylius\RefundPlugin\Command\RefundUnits as BaseRefundUnits;

use Sylius\RefundPlugin\Converter\RefundUnitsConverterInterface;

use Sylius\RefundPlugin\Creator\RefundUnitsCommandCreatorInterface;

use Sylius\RefundPlugin\Exception\InvalidRefundAmount;

use Sylius\RefundPlugin\Model\OrderItemUnitRefund;

use Sylius\RefundPlugin\Model\RefundType;

use Sylius\RefundPlugin\Model\ShipmentRefund;

use Symfony\Component\HttpFoundation\Request;

use Webmozart\Assert\Assert;

final class RefundUnitsCommandCreator implements RefundUnitsCommandCreatorInterface

{

    /\*\* @var RefundUnitsConverterInterface \*/

    private $refundUnitsConverter;

    public function \_\_construct(RefundUnitsConverterInterface $refundUnitsConverter)

    {

        $this->refundUnitsConverter = $refundUnitsConverter;

    }

    public function fromRequest(Request $request): BaseRefundUnits

    {

        Assert::true($request->attributes->has('orderNumber'), 'Refunded order number not provided');

        $units = $this->refundUnitsConverter->convert(

            $request->request->has('sylius\_refund\_units') ? $request->request->all()['sylius\_refund\_units'] : [],

            RefundType::orderItemUnit(),

            OrderItemUnitRefund::class

        );

        $shipments = $this->refundUnitsConverter->convert(

            $request->request->has('sylius\_refund\_shipments') ? $request->request->all()['sylius\_refund\_shipments'] : [],

            RefundType::shipment(),

            ShipmentRefund::class

        );

        if (count($units) === 0 && count($shipments) === 0) {

            throw InvalidRefundAmount::withValidationConstraint('sylius\_refund.at\_least\_one\_unit\_should\_be\_selected\_to\_refund');

        }

        /\*\* @var string $comment \*/

        $comment = $request->request->get('sylius\_refund\_comment', '');

        // here we need to return the new RefundUnits command, with new data

        return new RefundUnits(

            $request->attributes->get('orderNumber'),

            $units,

            $shipments,

            (int) $request->request->get('sylius\_refund\_payment\_method'),

            $comment,

            new \DateTime($request->request->get('sylius\_scheduled\_at'))

        );

    }

}

```

And register the new service:

```

# config/services.yaml

Sylius\RefundPlugin\Creator\RefundUnitsCommandCreatorInterface:

 class: App\Creator\RefundUnitsCommandCreator

 arguments:

 - '@Sylius\RefundPlugin\Converter\RefundUnitsConverterInterface'

```

**5. Modify the ``RefundUnitsHandler``:**

Now, when we have a new command, we also need to overwrite the related command handler:

```

<?php

declare(strict\_types=1);

namespace App\CommandHandler;

use Sylius\Component\Core\Model\OrderInterface;

use Sylius\Component\Core\Repository\OrderRepositoryInterface;

use App\Command\RefundUnits;

use App\Event\UnitsRefunded;

use Sylius\RefundPlugin\Refunder\RefunderInterface;

use Sylius\RefundPlugin\Validator\RefundUnitsCommandValidatorInterface;

use Symfony\Component\Messenger\MessageBusInterface;

use Webmozart\Assert\Assert;

final class RefundUnitsHandler

{

    /\*\* @var RefunderInterface \*/

    private $orderUnitsRefunder;

    /\*\* @var RefunderInterface \*/

    private $orderShipmentsRefunder;

    /\*\* @var MessageBusInterface \*/

    private $eventBus;

    /\*\* @var OrderRepositoryInterface \*/

    private $orderRepository;

    /\*\* @var RefundUnitsCommandValidatorInterface \*/

    private $refundUnitsCommandValidator;

    public function \_\_construct(

        RefunderInterface $orderUnitsRefunder,

        RefunderInterface $orderShipmentsRefunder,

        MessageBusInterface $eventBus,

        OrderRepositoryInterface $orderRepository,

        RefundUnitsCommandValidatorInterface $refundUnitsCommandValidator

    ) {

        $this->orderUnitsRefunder = $orderUnitsRefunder;

        $this->orderShipmentsRefunder = $orderShipmentsRefunder;

        $this->eventBus = $eventBus;

        $this->orderRepository = $orderRepository;

        $this->refundUnitsCommandValidator = $refundUnitsCommandValidator;

    }

    public function \_\_invoke(RefundUnits $command): void

    {

        $this->refundUnitsCommandValidator->validate($command);

        $orderNumber = $command->orderNumber();

        /\*\* @var OrderInterface $order \*/

        $order = $this->orderRepository->findOneByNumber($orderNumber);

        $refundedTotal = 0;

        $refundedTotal += $this->orderUnitsRefunder->refundFromOrder($command->units(), $orderNumber);

        $refundedTotal += $this->orderShipmentsRefunder->refundFromOrder($command->shipments(), $orderNumber);

        /\*\* @var string|null $currencyCode \*/

        $currencyCode = $order->getCurrencyCode();

        Assert::notNull($currencyCode);

        $this->eventBus->dispatch(new UnitsRefunded(

            $orderNumber,

            $command->units(),

            $command->shipments(),

            $command->paymentMethodId(),

            $refundedTotal,

            $currencyCode,

            $command->comment(),

            $command->getScheduledAt()

        ));

    }

}

```

And register it:

```

# config/services.yaml

Sylius\RefundPlugin\CommandHandler\RefundUnitsHandler:

 class: App\CommandHandler\RefundUnitsHandler

 arguments:

 - '@Sylius\RefundPlugin\Refunder\OrderItemUnitsRefunder'

 - '@Sylius\RefundPlugin\Refunder\OrderShipmentsRefunder'

 - '@sylius.event\_bus'

 - '@sylius.repository.order'

 - '@Sylius\RefundPlugin\Validator\RefundUnitsCommandValidatorInterface'

 tags:

 - { name: messenger.message\_handler, bus: sylius.command\_bus }

```

**6. Modify the ``UnitsReturned`` event:**

In previous command handler we are dispatching a new event so now we need to create this event and related event handler:

event:

```

<?php

declare(strict\_types=1);

namespace App\Event;

use Sylius\RefundPlugin\Event\UnitsRefunded as BaseUnitsRefunded;

final class UnitsRefunded extends BaseUnitsRefunded

{

    /\*\* @var \DateTimeInterface \*/

    protected $scheduledAt;

    public function \_\_construct(

        string $orderNumber,

        array $units,

        array $shipments,

        int $paymentMethodId,

        int $amount,

        string $currencyCode,

        string $comment,

        \DateTime $scheduledAt

    ) {

        parent::\_\_construct($orderNumber, $units, $shipments, $paymentMethodId, $amount, $currencyCode, $comment);

        $this->scheduledAt = $scheduledAt;

    }

    public function getScheduledAt(): \DateTimeInterface

    {

        return $this->scheduledAt;

    }

}

```

And process manager to handle the new event:

```

<?php

declare(strict\_types=1);

namespace App\ProcessManager;

use App\Entity\Refund\RefundPaymentInterface as AppRefundPaymentInterface;

use Doctrine\ORM\EntityManagerInterface;

use Sylius\Component\Core\Model\OrderInterface;

use Sylius\Component\Core\Model\PaymentMethodInterface;

use Sylius\Component\Core\Repository\OrderRepositoryInterface;

use Sylius\Component\Core\Repository\PaymentMethodRepositoryInterface;

use Sylius\RefundPlugin\Entity\RefundPaymentInterface;

use Sylius\RefundPlugin\Event\RefundPaymentGenerated;

use Sylius\RefundPlugin\Event\UnitsRefunded;

use Sylius\RefundPlugin\Factory\RefundPaymentFactoryInterface;

use Sylius\RefundPlugin\ProcessManager\UnitsRefundedProcessStepInterface;

use Sylius\RefundPlugin\Provider\RelatedPaymentIdProviderInterface;

use Sylius\RefundPlugin\StateResolver\OrderFullyRefundedStateResolverInterface;

use Symfony\Component\Messenger\MessageBusInterface;

use Webmozart\Assert\Assert;

final class RefundPaymentProcessManager implements UnitsRefundedProcessStepInterface

{

    /\*\* @var OrderFullyRefundedStateResolverInterface \*/

    private $orderFullyRefundedStateResolver;

    /\*\* @var RelatedPaymentIdProviderInterface \*/

    private $relatedPaymentIdProvider;

    /\*\* @var RefundPaymentFactoryInterface \*/

    private $refundPaymentFactory;

    /\*\* @var OrderRepositoryInterface \*/

    private $orderRepository;

    /\*\* @var PaymentMethodRepositoryInterface \*/

    private $paymentMethodRepository;

    /\*\* @var EntityManagerInterface \*/

    private $entityManager;

    /\*\* @var MessageBusInterface \*/

    private $eventBus;

    public function \_\_construct(

        OrderFullyRefundedStateResolverInterface $orderFullyRefundedStateResolver,

        RelatedPaymentIdProviderInterface $relatedPaymentIdProvider,

        RefundPaymentFactoryInterface $refundPaymentFactory,

        OrderRepositoryInterface $orderRepository,

        PaymentMethodRepositoryInterface $paymentMethodRepository,

        EntityManagerInterface $entityManager,

        MessageBusInterface $eventBus

    ) {

        $this->orderFullyRefundedStateResolver = $orderFullyRefundedStateResolver;

        $this->relatedPaymentIdProvider = $relatedPaymentIdProvider;

        $this->refundPaymentFactory = $refundPaymentFactory;

        $this->orderRepository = $orderRepository;

        $this->paymentMethodRepository = $paymentMethodRepository;

        $this->entityManager = $entityManager;

        $this->eventBus = $eventBus;

    }

    public function next(UnitsRefunded $unitsRefunded): void

    {

        /\*\* @var OrderInterface|null $order \*/

        $order = $this->orderRepository->findOneByNumber($unitsRefunded->orderNumber());

        Assert::notNull($order);

        /\*\* @var PaymentMethodInterface|null $paymentMethod \*/

        $paymentMethod = $this->paymentMethodRepository->find($unitsRefunded->paymentMethodId());

        Assert::notNull($paymentMethod);

        /\*\* @var AppRefundPaymentInterface $refundPayment \*/

        $refundPayment = $this->refundPaymentFactory->createWithData(

            $order,

            $unitsRefunded->amount(),

            $unitsRefunded->currencyCode(),

            RefundPaymentInterface::STATE\_NEW,

            $paymentMethod

        );

        $refundPayment->setScheduledAt($unitsRefunded->getScheduledAt());

        $this->entityManager->persist($refundPayment);

        $this->entityManager->flush();

        $this->eventBus->dispatch(new RefundPaymentGenerated(

            $refundPayment->getId(),

            $unitsRefunded->orderNumber(),

            $unitsRefunded->amount(),

            $unitsRefunded->currencyCode(),

            $unitsRefunded->paymentMethodId(),

            $this->relatedPaymentIdProvider->getForRefundPayment($refundPayment)

        ));

        $this->orderFullyRefundedStateResolver->resolve($unitsRefunded->orderNumber());

    }

}

```

And register it:

```

Sylius\RefundPlugin\ProcessManager\RefundPaymentProcessManager:

 class: App\ProcessManager\RefundPaymentProcessManager

 arguments:

 - '@Sylius\RefundPlugin\StateResolver\OrderFullyRefundedStateResolverInterface'

 - '@Sylius\RefundPlugin\Provider\RelatedPaymentIdProviderInterface'

 - '@sylius\_refund.factory.refund\_payment'

 - '@sylius.repository.order'

 - '@sylius.repository.payment\_method'

 - '@doctrine.orm.default\_entity\_manager'

 - '@sylius.event\_bus'

 tags:

 - {name: sylius\_refund.units\_refunded.process\_step, priority: 50}

```

**7. Display the new field on the refund payment:**

And as the last step, we need to overwrite the template `\_refundPayments.html.twig` from Refund Plugin.

Copy the entire `\_refundPayments.html.twig` to `templates/bundles/SyliusRefundPlugin/Order/Admin/\_refundPayments.html.twig`:

```

mkdir -p templates/bundles/SyliusRefundPlugin/Order/Admin

cp vendor/sylius/refund-plugin/src/Resources/views/Order/Admin/_refundPayments.html.twig templates/bundles/SyliusRefundPlugin/Order/Admin/

```

And replace `header` with:

```

<div class="header">

 {{ refund\_payment.paymentMethod  }} {%  if refund\_payment.scheduledAt is not null %} (Payment should be made in {{ refund\_payment.scheduledAt|date('Y-M-d') }}) {% endif %}

</div>

```

And that’s it, we have a new field on Refund Payment with a “scheduled at” date (when admin/payment gateway

should make the payment), in your application, you probably will add crone to automatize it.

##### How to add another type of refund?[¶](#how-to-add-another-type-of-refund "Permalink to this headline")

Note

This cookbook describes customization of a feature available only with [Sylius/RefundPlugin](https://github.com/Sylius/RefundPlugin/) installed.

###### Why would you add type of refund?[¶](#why-would-you-add-type-of-refund "Permalink to this headline")

Refund Plugin provides a generic solution for refunding orders, it is enough for a basic refund but many shops need more custom functionalities.

For example, one may need to add loyalty points as a different refund type than order item unit and shipment.

###### How to implement a new type of refund?[¶](#how-to-implement-a-new-type-of-refund "Permalink to this headline")

In the current implementation, there are 2 basic types that are defined in RefundPlugin:

> 

> * order item unit

> * shipment

> 

> 

> 

If you would like to add another one, e.g. `loyalty`, which might be used then to refund the loyalty points.

You need to first add it to the RefundType enum.

**1. Add the new type to the RefundType and RefundTypeInterface:**

Extended RefundTypeInterface should look like this:

```

<?php

declare(strict\_types=1);

namespace App\Model\Refund;

interface RefundTypeInterface

{

    public const LOYALTY = 'loyalty';

    public static function loyalty(): self;

}

```

And extended RefundType should look like:

```

<?php

declare(strict\_types=1);

namespace App\Model\Refund;

use Sylius\RefundPlugin\Model\RefundType as BaseRefundType;

final class RefundType extends BaseRefundType implements RefundTypeInterface

{

    public static function loyalty(): self

    {

        return new self(self::LOYALTY);

    }

}

```

You need also to set the parameter with new RefundType in your configuration file:

```

# config/packages/sylius\_refund.yaml

parameters:

 sylius\_refund.refund\_type: App\Model\Refund\RefundType

```

**2. Overwrite RefundEnumType to use your extended RefundType:**

Extended RefundEnumType should look like this:

```

<?php

declare(strict\_types=1);

namespace App\Entity\Refund\Type;

use App\Model\Refund\RefundType;

use Sylius\RefundPlugin\Entity\Type\RefundEnumType as BaseRefundEnumType;

use Sylius\RefundPlugin\Model\RefundTypeInterface;

final class RefundEnumType extends BaseRefundEnumType

{

    protected function createType($value): RefundTypeInterface

    {

        return new RefundType($value);

    }

}

```

And set the parameter with new RefundEnumType in your configuration file:

```

# config/packages/sylius\_refund.yaml

parameters:

 sylius\_refund.refund\_enum\_type: App\Entity\Refund\Type\RefundEnumType

```

**3. Modify the refund flow:**

Once we have the new type of refund added, we will need to use it and display its input on the refund form.

You can achieve this by using [Cookbook - How to customize the refund form?](index.html#document-cookbook/payments/custom-field-on-refund-payment)

and add in handler your custom logic for refunding e.g. loyalty points.

##### How to add another implementation of UnitRefundInterface?[¶](#how-to-add-another-implementation-of-unitrefundinterface "Permalink to this headline")

Note

This cookbook describes customization of a feature available only with [Sylius/RefundPlugin](https://github.com/Sylius/RefundPlugin/) installed.

###### Why would you add a new unit?[¶](#why-would-you-add-a-new-unit "Permalink to this headline")

In the current implementation, there are 2 basic unit refunds in RefundPlugin:

> 

> * OrderItemUnitRefund

> * ShipmentRefund

> 

> 

> 

But what if you want to refund something else, or refund the whole item instead of the item unit? Hopefully, you’ll do it

quite easily by creating a new implementation of UnitRefundInterface and a few tagged services that will make use of

it.

###### How to implement a new unit refund?[¶](#how-to-implement-a-new-unit-refund "Permalink to this headline")

Let’s say that you don’t operate on item units, but you want to refund entire items. You will need to create a new

refund model that will implement UnitRefundInterface. To do so, a few things need to be done. Besides creating

the OrderItemRefund, we will also need to create [a new refund type](index.html#document-cookbook/payments/custom-type-of-refund) and make everything work

together.

**1. Create a new refund type:**

This has been already described in the (How to add another type of refund?)[custom-type-of-refund] cookbook.

The code for the new refund type is as follows:

```

<?php

declare(strict\_types=1);

namespace App\Entity\Refund;

class RefundType extends \Sylius\RefundPlugin\Model\RefundType

{

    public const ORDER\_ITEM = 'order\_item';

    public static function orderItem(): self

    {

        return new self(self::ORDER\_ITEM);

    }

}

```

Then the RefundEnumType needs to base on our new RefundType:

```

<?php

declare(strict\_types=1);

namespace App\Entity\Refund\Type;

use App\Entity\Refund\RefundType;

use Sylius\RefundPlugin\Model\RefundTypeInterface;

final class RefundEnumType extends \Sylius\RefundPlugin\Entity\Type\RefundEnumType

{

    protected function createType($value): RefundTypeInterface

    {

        return new RefundType($value);

    }

}

```

To make it work, we change the value of two parameters to the following:

```

parameters:

 sylius\_refund.refund\_enum\_type: App\Entity\Refund\Type\RefundEnumType

 sylius\_refund.refund\_type: App\Entity\Refund\RefundType

```

**2. Add a new unit refund:**

Now, having the new refund type, we can create a new refund model implementing UnitRefundInterface:

```

<?php

declare(strict\_types=1);

namespace App\Entity\Refund;

use Sylius\RefundPlugin\Model\UnitRefundInterface;

final class OrderItemRefund implements UnitRefundInterface

{

    public function \_\_construct(private int $itemId, private int $total)

    {

    }

    public function id(): int

    {

        return $this->itemId;

    }

    public function total(): int

    {

        return $this->total;

    }

    public static function type(): RefundType

    {

        return RefundType::orderItem();

    }

}

```

**3. Disable OrderItemUnitRefund:**

As our new behavior bases on the order items, we need to disable the current behavior that is based on the order item

units. We can achieve that by disabling the converters and the refunder by removing tags from the services:

```

Sylius\RefundPlugin\Converter\OrderItemUnitLineItemsConverter:

 tags: []

Sylius\RefundPlugin\Converter\RequestToOrderItemUnitRefundConverter:

 tags: []

Sylius\RefundPlugin\Refunder\OrderItemUnitsRefunder:

 tags: []

```

**4. Create the OrderItemTotalProvider:**

RefundPlugin doesn’t know anything about order items, so we need to tell them how to retrieve the total of the order.

```

<?php

declare(strict\_types=1);

namespace App\Provider;

use Sylius\Component\Core\Model\OrderItemInterface;

use Sylius\Component\Resource\Repository\RepositoryInterface;

use Sylius\RefundPlugin\Provider\RefundUnitTotalProviderInterface;

use Webmozart\Assert\Assert;

final class OrderItemTotalProvider implements RefundUnitTotalProviderInterface

{

    public function \_\_construct(private RepositoryInterface $orderItemRepository)

    {

    }

    public function getRefundUnitTotal(int $id): int

    {

        /\*\* @var OrderItemInterface $orderItem \*/

        $orderItem = $this->orderItemRepository->find($id);

        Assert::notNull($orderItem);

        return $orderItem->getTotal();

    }

}

```

As you can see, it just gets the order item and returns its total. Now a piece of configuration:

```

App\Provider\OrderItemTotalProvider:

 arguments:

 - '@sylius.repository.order\_item'

 tags: [{ name: 'sylius\_refund.refund\_unit\_total\_provider', refund\_type: 'order\_item' }]

```

**5. Create the ItemRefunded event with a listener:**

As we are refunding the order items, we need to update the payment state of the order.

The event itself will be dispatched by the OrderItemsRefunder later.

The ItemRefunded event is as follows:

```

<?php

declare(strict\_types=1);

namespace App\Event;

class ItemRefunded

{

    public function \_\_construct(private string $orderNumber)

    {

    }

    public function orderNumber(): string

    {

        return $this->orderNumber;

    }

}

```

the listener:

```

<?php

declare(strict\_types=1);

namespace App\Listener;

use App\Event\ItemRefunded;

use Sylius\RefundPlugin\StateResolver\OrderPartiallyRefundedStateResolverInterface;

final class ItemRefundedEventListener

{

    public function \_\_construct(private OrderPartiallyRefundedStateResolverInterface $orderPartiallyRefundedStateResolver)

    {

    }

    public function \_\_invoke(ItemRefunded $itemRefunded): void

    {

        $this->orderPartiallyRefundedStateResolver->resolve($itemRefunded->orderNumber());

    }

}

```

and the configuration:

```

App\Listener\ItemRefundedEventListener:

 arguments:

 - '@Sylius\RefundPlugin\StateResolver\OrderPartiallyRefundedStateResolverInterface'

 tags: [{ name: 'messenger.message\_handler', bus: 'sylius.event\_bus' }]

```

**6. Create the OrderItemsRefunder:**

Refunder will make use of previously created event by dispatching it at the end of refunding process. The refunding

process basically processes the OrderItemRefund objects one by one to create a refund for each of them.

```

<?php

declare(strict\_types=1);

namespace App\Refunder;

use App\Entity\Refund\OrderItemRefund;

use App\Event\ItemRefunded;

use Sylius\RefundPlugin\Creator\RefundCreatorInterface;

use Sylius\RefundPlugin\Filter\UnitRefundFilterInterface;

use Sylius\RefundPlugin\Model\UnitRefundInterface;

use Sylius\RefundPlugin\Refunder\RefunderInterface;

use Symfony\Component\Messenger\MessageBusInterface;

final class OrderItemsRefunder implements RefunderInterface

{

    public function \_\_construct(

        private RefundCreatorInterface $refundCreator,

        private MessageBusInterface $eventBus,

        private UnitRefundFilterInterface $unitRefundFilter,

    ) {

    }

    public function refundFromOrder(array $units, string $orderNumber): int

    {

        $units = $this->unitRefundFilter->filterUnitRefunds($units, OrderItemRefund::class);

        $refundedTotal = 0;

        /\*\* @var UnitRefundInterface $unit \*/

        foreach ($units as $unit) {

            $this->refundCreator->\_\_invoke(

                $orderNumber,

                $unit->id(),

                $unit->total(),

                $unit->type()

            );

            $refundedTotal += $unit->total();

        }

        $this->eventBus->dispatch(new ItemRefunded($orderNumber));

        return $refundedTotal;

    }

}

```

Now add a tag to the service:

```

App\Refunder\OrderItemsRefunder:

 arguments:

 - '@Sylius\RefundPlugin\Creator\RefundCreatorInterface'

 - '@sylius.event\_bus'

 - '@Sylius\RefundPlugin\Filter\UnitRefundFilterInterface'

 tags: ['sylius\_refund.refunder']

```

**7. Create the OrderItemLineItemsConverter:**

RefundPlugin generates a credit memo based on a refund that was made. However, as it’s handled under the hood by processing

line items, we need to provide a converter that will convert the OrderItemRefund objects to the LineItem objects.

```

<?php

declare(strict\_types=1);

namespace App\Converter;

use App\Entity\Refund\OrderItemRefund;

use Sylius\Component\Core\Model\OrderItemInterface;

use Sylius\Component\Resource\Repository\RepositoryInterface;

use Sylius\RefundPlugin\Converter\LineItemsConverterUnitRefundAwareInterface;

use Sylius\RefundPlugin\Entity\LineItem;

use Sylius\RefundPlugin\Entity\LineItemInterface;

use Sylius\RefundPlugin\Provider\TaxRateProviderInterface;

use Webmozart\Assert\Assert;

final class OrderItemLineItemsConverter implements LineItemsConverterUnitRefundAwareInterface

{

    public function \_\_construct(

        private RepositoryInterface $orderItemRepository,

        private TaxRateProviderInterface $taxRateProvider

    ) {

    }

    public function convert(array $units): array

    {

        Assert::allIsInstanceOf($units, $this->getUnitRefundClass());

        $lineItems = [];

        /\*\* @var OrderItemRefund $unit \*/

        foreach ($units as $unit) {

            $lineItems = $this->addLineItem($this->convertUnitRefundToLineItem($unit), $lineItems);

        }

        return $lineItems;

    }

    public function getUnitRefundClass(): string

    {

        return OrderItemRefund::class;

    }

    private function convertUnitRefundToLineItem(OrderItemRefund $unitRefund): LineItemInterface

    {

        /\*\* @var OrderItemInterface|null $orderItem \*/

        $orderItem = $this->orderItemRepository->find($unitRefund->id());

        Assert::notNull($orderItem);

        Assert::lessThanEq($unitRefund->total(), $orderItem->getTotal());

        $grossValue = $unitRefund->total();

        $taxAmount = (int) ($grossValue \* $orderItem->getTaxTotal() / $orderItem->getTotal());

        $netValue = $grossValue - $taxAmount;

        /\*\* @var string|null $productName \*/

        $productName = $orderItem->getProductName();

        Assert::notNull($productName);

        return new LineItem(

            $productName,

            1,

            $netValue,

            $grossValue,

            $netValue,

            $grossValue,

            $taxAmount,

            $this->taxRateProvider->provide($orderItem)

        );

    }

    /\*\*

 \* @param LineItemInterface[] $lineItems

 \*

 \* @return LineItemInterface[]

 \*/

    private function addLineItem(LineItemInterface $newLineItem, array $lineItems): array

    {

        foreach ($lineItems as $lineItem) {

            if ($lineItem->compare($newLineItem)) {

                $lineItem->merge($newLineItem);

                return $lineItems;

            }

        }

        $lineItems[] = $newLineItem;

        return $lineItems;

    }

}

```

and the configuration:

```

App\Converter\OrderItemLineItemsConverter:

 arguments:

 - '@sylius.repository.order\_item'

 - '@Sylius\RefundPlugin\Provider\TaxRateProviderInterface'

 tags: ['sylius\_refund.line\_item\_converter']

```

**8. Create the RequestToOrderItemRefundConverter:**

Similar to the previous step, we need to provide a converter that will convert the request to the OrderItemRefund

objects.

```

<?php

declare(strict\_types=1);

namespace App\Converter;

use App\Entity\Refund\OrderItemRefund;

use Sylius\RefundPlugin\Converter\RefundUnitsConverterInterface;

use Sylius\RefundPlugin\Converter\RequestToRefundUnitsConverterInterface;

use Symfony\Component\HttpFoundation\Request;

final class RequestToOrderItemRefundConverter implements RequestToRefundUnitsConverterInterface

{

    public function \_\_construct(private RefundUnitsConverterInterface $refundUnitsConverter)

    {

    }

    /\*\*

 \* @return OrderItemRefund[]

 \*/

    public function convert(Request $request): array

    {

        return $this->refundUnitsConverter->convert(

            $request->request->all()['sylius\_refund\_items'] ?? [],

            OrderItemRefund::class

        );

    }

}

```

and the configuration:

```

App\Converter\RequestToOrderItemRefundConverter:

 arguments:

 - '@Sylius\RefundPlugin\Converter\RefundUnitsConverterInterface'

 tags: ['sylius\_refund.request\_to\_refund\_units\_converter']

```

It’s almost done! If you want to be able to refund the order items in the admin panel, one more step is needed.

**9. Adjust the order refund form to the current state:**

Under the templates/Admin/OrderRefund directory, create the \_items.html.twig file. The template could look like this:

```

{% import '@SyliusAdmin/Common/Macro/money.html.twig' as money %}

{% for item in order.items %}

    {% set variant = item.variant %}

    {% set product = variant.product %}

    <tr class="unit">

        <td class="single line">

            {% include '@SyliusAdmin/Product/\_info.html.twig' %}

        </td>

        <td class="right aligned total">

            <span class="unit-total">{{ money.format(item.total, order.currencyCode) }}</span>

            {% set refundedTotal = unit\_refunded\_total(item.id, constant('App\\Entity\\Refund\\RefundType::ORDER\_ITEM')) %}

            {% if refundedTotal != 0 %}

                <br/>

                <strong>{{ 'sylius\_refund.ui.refunded'|trans }}:</strong>

                <span class="unit-refunded-total">{{ money.format(refundedTotal, order.currencyCode) }}</span>

            {% endif %}

        </td>

        <td class="aligned collapsing partial-refund">

            {% set inputName = "sylius\_refund\_items["~item.id~"][amount]" %}

            {% set hiddenInputName = "sylius\_refund\_items["~item.id~"][partial-id]" %}

            <div class="ui labeled input">

                <div class="ui label">{{ order.currencyCode|sylius\_currency\_symbol }}</div>

                <input data-refund-input type="number" step="0.01" name="{{ inputName }}" {% if not can\_unit\_be\_refunded(item.id, constant('App\\Entity\\Refund\\RefundType::ORDER\_ITEM')) %} disabled{% endif %}/>

            </div>

        </td>

        <td class="aligned collapsing">

            <button data-refund="{{ unit\_refund\_left(item.id, constant('App\\Entity\\Refund\\RefundType::ORDER\_ITEM'), item.total) }}" type="button" class="ui button primary" {% if not can\_unit\_be\_refunded(item.id, constant('App\\Entity\\Refund\\RefundType::ORDER\_ITEM')) %}disabled{% endif %}>

                {{ 'sylius\_refund.ui.refund'|trans }}

            </button>

        </td>

    </tr>

{% endfor %}

```

The template above will be used in sylius\_refund.admin.order.refund.form.table.body template event as e.g.

custom\_items block. Remember to disable items block, which handles order item units by the occasion.

```

sylius\_ui:

 events:

 sylius\_refund.admin.order.refund.form.table.body:

 blocks:

 items: false

 custom\_items:

 template: "Admin/OrderRefund/\_items.html.twig"

 priority: 10

```

Great! Now you can refund the order items instead order item units in the admin panel.

##### How to customize the order invoice?[¶](#how-to-customize-the-order-invoice "Permalink to this headline")

Note

This cookbook describes customization of a feature available only with [Sylius/InvoicingPlugin](https://github.com/Sylius/InvoicingPlugin/) installed.

The invoicing plugin lets you generate invoice on admin panel and download PDF with it. This plugin also sends an email with this invoice

as well as let the administrator to delegate an email resend.

###### Why would you customize the invoice?[¶](#why-would-you-customize-the-invoice "Permalink to this headline")

Invoicing Plugin provides a generic solution for generating invoices for orders, it is enough for a basic invoicing functionality

but many shops need this feature customized for its needs.

For example, one may need to change the look of it, or add some more data.

###### Getting started[¶](#getting-started "Permalink to this headline")

Before you start make sure that you have:

1. [Sylius/InvoicingPlugin](https://github.com/Sylius/InvoicingPlugin/) installed.

2. [Wkhtmltopdf](https://wkhtmltopdf.org/) package installed, because most of pdf generation is done with it.

###### How to change the logo in the Invoice?[¶](#how-to-change-the-logo-in-the-invoice "Permalink to this headline")

In order to change the logo on the invoice, set up the `SYLIUS\_INVOICING\_LOGO\_FILE` environment variable.

Example custom configuration:

```

SYLIUS_INVOICING_LOGO_FILE=%kernel.project_dir%/public/assets/custom-logo.png

```

Make sure to clear the cache each time the configuration is changed.

![_images/pdf_with_custom_logo.png](_images/pdf_with_custom_logo.png)

###### How to add more graphics to the Invoice?[¶](#how-to-add-more-graphics-to-the-invoice "Permalink to this headline")

In case you would like to add an extra graphic to your Invoice twig template, it is super important to provide access to this file.

Let’s say you would like to add second image to the Invoice.

You may face the problem that `wkhtmltopdf` from version 0.12.6 disables access to the local files by default.

Fortunately, there are two options to deal with it:

* Update the `config/packages/knp\_snappy.yaml` file by adding access to local files globally:

> 

> 

> ```

> knp\_snappy:

>  pdf:

>  options:

>  enable-local-file-access: true

> 

> ```

> 

> 

>

* Specify the exact list of accessible files. As you may have noticed, the logo displays correctly even though local file access is not enabled.

This is because we handle it by specifying the exact list of allowed files.

The list can be replaced with the `sylius\_refund.pdf\_generator.allowed\_files` parameter in the `config/packages/\_sylius.yaml`:

> 

> 

> ```

> sylius\_invoicing:

>  pdf\_generator:

>  allowed\_files:

>  - '%env(default:default\_logo\_file:resolve:SYLIUS\_INVOICING\_LOGO\_FILE)%'

>  - 'path/image.png'

>  - 'directory/with/allowed/files'

> 

> ```

> 

> 

>

###### How to customize the invoice appearance?[¶](#how-to-customize-the-invoice-appearance "Permalink to this headline")

There might be need to change how the invoices look like, f.e. there might be different logo dimension, some colours changed

on the tables, or maybe the order of fields might be changed.

First let’s prepare the HTML’s so we can modify them. This command will copy only the PDF template:

```

mkdir templates/bundles/SyliusInvoicingPlugin/Invoice/Download

cp vendor/sylius/invoicing-plugin/src/Resources/views/Invoice/Download/pdf.html.twig templates/bundles/SyliusInvoicingPlugin/Download

```

But you can also copy all of the templates with:

```

mkdir templates/bundles/SyliusInvoicingPlugin

cp -r vendor/sylius/invoicing-plugin/src/Resources/views/Invoice templates/bundles/SyliusInvoicingPlugin/

```

In directory `templates/bundles/SyliusInvoicingPlugin/Invoice` you can find now few files.

Let’s modify the generated PDF. In this case we will modify the file from `Download/pdf.html.twig`.

This is how the default PDF looks like (don’t worry this is not a real address of Myrna, or is it?):

![_images/pdf_before.png](_images/pdf_before.png)

Now with some magic of HTML and CSS we can modify this template, as an example we can change the color of background to `red` by changing

```

<!--...-->

<div class="invoice-box" style="background-color: red">

<!--...-->

</div>

```

and after this change we are graced with this masterpiece:

![_images/pdf_after.png](_images/pdf_after.png)

Warning

Every PDF that you generate is stored and then extracted so it won’t be created again. If you want to see the changes

go to `private/invoices` and remove the generated PDF. You should see the changes of your file when you generate it again.

Note

You can also modify the view on administrator page by changing code inside `show.html.twig` and related templates

Note

You can learn more about customizing templates at [Customization Guide](index.html#document-customization/index)

###### How to add additional fields to invoice?[¶](#how-to-add-additional-fields-to-invoice "Permalink to this headline")

Let’s say that you need (or not) some more fields. In this example we will add the customer phone number.

Because we are basing upon the existing field, there should be no problem adding it to document - just place a line into

`Download/pdf.html.twig` file. The `Phone Number` field is quite nested so you need to add `invoice.order.customer.phoneNumber`

to retrieve it:

```

<!--...-->

 {{ invoice.billingData.city }}<br/>

 {{ invoice.order.customer.phoneNumber }}<br/>

 {{ invoice.billingData.countryCode}}

<!--...-->

```

And as a result we can see that phone number has been added just after the city:

![_images/pdf_phone.png](_images/pdf_phone.png)

Note

You can also create some validation (for example if customer has no phone number) so the field won’t be shown.

If you want to learn more about twig - visit [twig](https://twig.symfony.com/).

###### How to change the appearance of invoice tables?[¶](#how-to-change-the-appearance-of-invoice-tables "Permalink to this headline")

By default on lower right corner of invoice we are displaying `total` of ordered items and shipment.

Lets create now a new row where we will show `Products total` where only price for products will be shown.

First let’s add the new table row between other `totals` in `pdf.html.twig`

```

<!--...-->

 <tr class="totals">

 <!--tr body-->

 </tr>

 <tr class="totals">

 <td colspan="5"></td>

 <td colspan="2" >{{ 'sylius\_invoicing\_plugin.ui.products\_total'|trans([], 'messages', invoice.localeCode) }}:</td>

 <td>{{ '%0.2f'|format(invoice.order.itemsTotal/100) }}</td>

 <td>{{ invoice.currencyCode }}</td>

 </tr>

 <tr class="totals bold">

 </tr>

<!--...-->

```

And now add the translation by creating file `translations/messages.en.yaml` and adding:

```

sylius\_invoicing\_plugin:

 ui:

 products\_total: 'Products total'

```

after this changes your PDF’s total table should look like this:

![_images/pdf_total.png](_images/pdf_total.png)

###### How to extend Invoice with custom logic?[¶](#how-to-extend-invoice-with-custom-logic "Permalink to this headline")

With default behavior and some simple customization it should be quite simple to achieve the Invoice you are looking for.

But life is not so straightforward as we all would like, and you are in need to create some custom logic for your needs.

Scary process isn’t it? Well not exactly, let’s create some custom logic for your invoice in this step.

First we need a class with our logic that will extend current Invoice:

```

<?php

declare(strict\_types=1);

namespace App\Entity\Invoice;

use Doctrine\ORM\Mapping as ORM;

use Sylius\InvoicingPlugin\Entity\Invoice as BaseInvoice;

/\*\*

 \* @ORM\Entity

 \* @ORM\Table(name="sylius\_invoicing\_plugin\_invoice")

 \*/

class Invoice extends BaseInvoice implements InvoiceInterface

{

    public function customFunction(): mixed

    {

        /\*\* your custom logic \*/

    }

}

```

And if there is a need you can also create an interface that will extend the base one:

```

<?php

declare(strict\_types=1);

namespace App\Entity\Invoice;

use Sylius\InvoicingPlugin\Entity\InvoiceInterface as BaseInvoiceInterface;

interface InvoiceInterface extends BaseInvoiceInterface

{

    public function customFunction(): mixed;

}

```

Now let’s add those classes to the configuration:

```

# config/packages/\_sylius.yaml

sylius\_invoicing:

resources:

 invoice:

 classes:

 model: App\Entity\Invoice\Invoice

 interface: App\Entity\Invoice\InvoiceInterface

```

Note

Don’t forget to update your database if you are changing/adding fields.

Now you can show a new invoice table on PDF with some changes just like in chapters before.

##### How to have the Invoice generated after the payment is paid?[¶](#how-to-have-the-invoice-generated-after-the-payment-is-paid "Permalink to this headline")

Note

This cookbook describes customization of a feature available only with [Sylius/InvoicingPlugin](https://github.com/Sylius/InvoicingPlugin/) installed.

The invoicing plugin lets you generate and download PDF invoices regarding orders. In its default behavior the invoice

is generated right after the customer creates and places the order.

In this cookbook, we will describe how to change this behavior, so the invoice will be created right after the order would be paid.

###### Why would you customize the invoice generation?[¶](#why-would-you-customize-the-invoice-generation "Permalink to this headline")

In the default case the order items should not be changed after completing order. But let’s say that your shop is customized with some logic

which is not out of the box. Maybe one of these changes will let you change the order after it is placed?

In this case, it would be better to have an invoice generated after a particular step (in the case of this cookbook - after the order is paid).

###### Getting started[¶](#getting-started "Permalink to this headline")

Before you start, make sure that you have:

1. [Sylius/InvoicingPlugin](https://github.com/Sylius/InvoicingPlugin/) installed.

2. [Wkhtmltopdf](https://wkhtmltopdf.org/) package installed, because most of pdf generation is done with it.

###### How to customize the invoice generation?[¶](#how-to-customize-the-invoice-generation "Permalink to this headline")

The concept is quite straightforward and it is based on Symfony events and event listeners.

We need to create 3 classes and declare them in config files.

Let’s start first with EventListeners that will override the default ones:

**1.** The `OrderPaymentPaidListener` will create an invoice and check if it does not exist:

```

<?php

declare(strict\_types=1);

namespace App\EventListener;

use Sylius\InvoicingPlugin\Command\SendInvoiceEmail;

use Sylius\InvoicingPlugin\Creator\InvoiceCreatorInterface;

use Sylius\InvoicingPlugin\Event\OrderPaymentPaid;

use Sylius\InvoicingPlugin\Exception\InvoiceAlreadyGenerated;

use Symfony\Component\Messenger\MessageBusInterface;

final class OrderPaymentPaidListener

{

    /\*\* @var InvoiceCreatorInterface \*/

    private $invoiceCreator;

    /\*\* @var MessageBusInterface \*/

    private $commandBus;

    public function \_\_construct(InvoiceCreatorInterface $invoiceCreator, MessageBusInterface $commandBus)

    {

        $this->invoiceCreator = $invoiceCreator;

        $this->commandBus = $commandBus;

    }

    public function \_\_invoke(OrderPaymentPaid $event): void

    {

        try {

            $this->invoiceCreator->\_\_invoke($event->orderNumber(), $event->date());

        } catch (InvoiceAlreadyGenerated $exception) {

        }

        $this->commandBus->dispatch(new SendInvoiceEmail($event->orderNumber()));

    }

}

```

**2.** The `NoInvoiceOnOrderPlacedListener` will not do anything (what a lazy boy), but as we are changing the behavior, it still has very important role:

```

<?php

declare(strict\_types=1);

namespace App\EventListener;

use Sylius\InvoicingPlugin\Event\OrderPlaced;

final class NoInvoiceOnOrderPlacedListener

{

    public function \_\_invoke(OrderPlaced $event): void

    {

        // intentionally left blank

    }

}

```

**3.** Last but not least `OrderPaymentPaidProducer` which will dispatch an event at a correct moment:

```

<?php

declare(strict\_types=1);

namespace App\Producer;

use Sylius\Component\Core\Model\OrderInterface;

use Sylius\Component\Core\Model\PaymentInterface;

use Sylius\InvoicingPlugin\DateTimeProvider;

use Sylius\InvoicingPlugin\Event\OrderPaymentPaid;

use Symfony\Component\Messenger\MessageBusInterface;

final class OrderPaymentPaidProducer

{

    /\*\* @var MessageBusInterface \*/

    private $eventBus;

    /\*\* @var DateTimeProvider \*/

    private $dateTimeProvider;

    public function \_\_construct(

        MessageBusInterface $eventBus,

        DateTimeProvider $dateTimeProvider

    ) {

        $this->eventBus = $eventBus;

        $this->dateTimeProvider = $dateTimeProvider;

    }

    public function \_\_invoke(PaymentInterface $payment): void

    {

        /\*\* @var OrderInterface|null $order \*/

        $order = $payment->getOrder();

        if ($order === null) {

            return;

        }

        /\*\* @var string $number \*/

        $number = $order->getNumber();

        $this->eventBus->dispatch(new OrderPaymentPaid($number, $this->dateTimeProvider->\_\_invoke()));

    }

}

```

**4.** Last thing that we need to do is to register new services in a container:

```

# config/services.yaml

services:

 sylius\_invoicing\_plugin.listener.order\_payment\_paid:

 class: App\EventListener\OrderPaymentPaidListener

 arguments:

 - '@sylius\_invoicing\_plugin.creator.invoice'

 - '@sylius.command\_bus'

 tags:

 - { name: messenger.message\_handler }

 sylius\_invoicing\_plugin.event\_listener.order\_placed:

 class: App\EventListener\NoInvoiceOnOrderPlacedListener

 tags:

 - { name: messenger.message\_handler }

 sylius\_invoicing\_plugin.event\_producer.order\_payment\_paid:

 class: App\Producer\OrderPaymentPaidProducer

 arguments:

 - '@sylius.event\_bus'

 - '@sylius\_invoicing\_plugin.date\_time\_provider'

 public: true

```

After these changes, the invoice will be generated after the order is paid, not just after it is placed.

![_images/before_payment.png](_images/before_payment.png)

* [How to configure PayPal Express Checkout?](index.html#document-cookbook/payments/paypal)

* [How to configure Stripe Credit Card payment?](index.html#document-cookbook/payments/stripe)

* [How to encrypt gateway config stored in the database?](index.html#document-cookbook/payments/encrypting-gateway-config)

* [How to authorize a payment before capturing.](index.html#document-cookbook/payments/authorize)

* [How to integrate a Payment Gateway as a Plugin?](index.html#document-cookbook/payments/custom-payment-gateway)

* [How to customize a Credit Memo?](index.html#document-cookbook/payments/customizing-credit-memo)

* [How to have the Credit Memos created after the Refund Payments?](index.html#document-cookbook/payments/refund-process)

* [How to customize the refund form?](index.html#document-cookbook/payments/custom-field-on-refund-payment)

* [How to add another type of refund?](index.html#document-cookbook/payments/custom-type-of-refund)

* [How to add another implementation of UnitRefundInterface?](index.html#document-cookbook/payments/custom-unit-refund)

* [How to customize the order invoice?](index.html#document-cookbook/payments/custom-invoice)

* [How to have the Invoice generated after the payment is paid?](index.html#document-cookbook/payments/generating-invoice-after-payment)

#### Emails[¶](#emails "Permalink to this headline")

##### How to send a custom e-mail?[¶](#how-to-send-a-custom-e-mail "Permalink to this headline")

Note

This cookbook is suitable for a clean [sylius-standard installation](index.html#document-book/installation/installation).

For more general tips, while using [SyliusMailerBundle](https://github.com/Sylius/SyliusMailerBundle/blob/master/docs/index.md)

go to [Sending configurable e-mails in Symfony Blogpost](https://sylius.com/blog/sending-configurable-e-mails-in-symfony).

Currently **Sylius** is sending e-mails only in a few “must-have” cases - see [E-mails documentation](index.html#document-book/architecture/emails).

Of course these cases may not be sufficient for your business needs. If so, you will need to create your own custom e-mails inside the system.

On a basic example we will now teach how to do it.

Let’s assume that you would like such a feature in your system:

```

Feature: Sending a notification email to the administrator when a product is out of stock

    In order to be aware which products become out of stock

    As an Administrator

    I want to be notified via email when products become out of stock

```

To achieve that you will need to:

###### 1. Create a new e-mail that will be sent:[¶](#create-a-new-e-mail-that-will-be-sent "Permalink to this headline")

* prepare a template for your email in the `templates/Email`.

```

{# templates/Email/out\_of\_stock.html.twig #}

{% block subject %}

 One of your products has become out of stock.

{% endblock %}

{% block body %}

 {% autoescape %}

 The {{ variant.name }} variant is out of stock!

 {% endautoescape %}

{% endblock %}

```

* configure the email under `sylius\_mailer:` in the `config/packages/sylius\_mailer.yaml`.

```

# config/packages/sylius\_mailer.yaml

sylius\_mailer:

 sender:

 name: Example.com

 address: no-reply@example.com

 emails:

 out\_of\_stock:

 subject: "A product has become out of stock!"

 template: "Email/out\_of\_stock.html.twig"

```

###### 2. Create an Email Manager class:[¶](#create-an-email-manager-class "Permalink to this headline")

* It will need the **EmailSender**, the **AvailabilityChecker** and the **AdminUser Repository**.

* It will operate on the **Order** where it needs to check each OrderItem, get their **ProductVariants** and check if they are available.

```

<?php

namespace App\EmailManager;

use Sylius\Component\Core\Model\OrderInterface;

use Sylius\Component\Inventory\Checker\AvailabilityCheckerInterface;

use Sylius\Component\Mailer\Sender\SenderInterface;

use Sylius\Component\Resource\Repository\RepositoryInterface;

final class OutOfStockEmailManager

{

    /\*\* @var SenderInterface \*/

    private $emailSender;

    /\*\* @var AvailabilityCheckerInterface \*/

    private $availabilityChecker;

    /\*\* @var RepositoryInterface $adminUserRepository \*/

    private $adminUserRepository;

    public function \_\_construct(

        SenderInterface $emailSender,

        AvailabilityCheckerInterface $availabilityChecker,

        RepositoryInterface $adminUserRepository

    ) {

        $this->emailSender = $emailSender;

        $this->availabilityChecker = $availabilityChecker;

        $this->adminUserRepository = $adminUserRepository;

    }

    public function sendOutOfStockEmail(OrderInterface $order): void

    {

        // get all admins, but remember to put them into an array

        $admins = $this->adminUserRepository->findAll()->toArray();

        foreach ($order->getItems() as $item) {

            $variant = $item->getVariant();

            $stockIsSufficient = $this->availabilityChecker->isStockSufficient($variant, 1);

            if ($stockIsSufficient) {

                continue;

            }

            foreach ($admins as $admin) {

                $this->emailSender->send('out\_of\_stock', [$admin->getEmail()], ['variant' => $variant]);

            }

        }

    }

}

```

###### 3. Register the manager as a service:[¶](#register-the-manager-as-a-service "Permalink to this headline")

```

# config/packages/\_sylius.yaml

services:

 App\EmailManager\OutOfStockEmailManager:

 arguments: ['@sylius.email\_sender', '@sylius.availability\_checker', '@sylius.repository.admin\_user']

```

###### 4. Customize the state machine callback of Order’s Payment:[¶](#customize-the-state-machine-callback-of-order-s-payment "Permalink to this headline")

```

# config/packages/\_sylius.yaml

winzou\_state\_machine:

 sylius\_order\_payment:

 callbacks:

 after:

 app\_out\_of\_stock\_email:

 on: ["pay"]

 do: ["@app.email\_manager.out\_of\_stock", "sendOutOfStockEmail"]

 args: ["object"]

```

**Done!**

###### Learn More[¶](#learn-more "Permalink to this headline")

* [Emails Concept](index.html#document-book/architecture/emails)

* [State Machine Concept](index.html#document-book/architecture/state_machine)

* [Customization Guide - State Machine](index.html#document-customization/state_machine)

* [Sending configurable e-mails in Symfony Blogpost](https://sylius.com/blog/sending-configurable-e-mails-in-symfony)

##### How to customize email templates per channel?[¶](#how-to-customize-email-templates-per-channel "Permalink to this headline")

Note

This cookbook is suitable for a clean [sylius-standard installation](index.html#document-book/installation/installation).

For more general tips, while using [SyliusMailerBundle](https://github.com/Sylius/SyliusMailerBundle/blob/master/docs/index.md)

go to [Sending configurable e-mails in Symfony Blogpost](https://sylius.com/blog/sending-configurable-e-mails-in-symfony).

It is a common use-case to customize email templates depending on a channel in which an action was performed.

1. Pick a template to customize

You can find the list of all email templates and their data on [emails](index.html#document-book/architecture/emails) documentation page.

Then, override that template following our [template customization](index.html#document-customization/template) guide.

In this cookbook, let’s assume that we want to customize the order confirmation email located at `@SyliusShopBundle/Email/orderConfirmation.html.twig`.

This requires creating a new template in `templates/bundles/SyliusShopBundle/Email/orderConfirmation.html.twig`.

2. Add an `if` statement for simple customizations

The simplest customization might be done with an `if` statement in the template.

```

<!-- templates/bundles/SyliusShopBundle/Email/orderConfirmation.html.twig -->

{% block subject %}Topic{% endblock %}

{% block body %}{% autoescape %}

 {% if channel.code is same as ('TOY\_STORE') %}

 Thanks for buying one of our toys!

 {% else %}

 Thanks for buying!

 {% endif %}

 Your order no. {{ order.number }} has been successfully placed.

{% endautoescape %}{% endblock %}

```

3. Extract templates for more flexibility (optional)

If you require more flexibility, you can extract templates to standalone files.

```

<!-- templates/bundles/SyliusShopBundle/Email/orderConfirmation.html.twig -->

{% block subject %}Topic{% endblock %}

{% block body %}{% autoescape %}

 {% include ['/Email/OrderConfirmation/' ~ sylius.channel.code ~ '.html.twig', '/Email/OrderConfirmation/\_default.html.twig'] %}

{% endautoescape %}{% endblock %}

```

The code snippet above will first try to load email body template based on channel code and will fall back to default template

if not found.

It is required now to create the default template in `templates/Email/OrderConfirmation/\_default.html.twig` (this path is

the one defined above).

```

<!-- templates/Email/OrderConfirmation/\_default.html.twig -->

Your order no. {{ order.number }} has been successfully placed.

```

And also the one specific for `TOY\_STORE` channel in `templates/Email/OrderConfirmation/TOY\_STORE.html.twig`:

```

<!-- templates/Email/OrderConfirmation/TOY\_STORE.html.twig -->

Thanks for buying one of our toys!

Your order with number {{ order.number }} is currently being processed.

```

This way allows to keep independent templates with email contents based on the channel.

##### How to disable the order confirmation email?[¶](#how-to-disable-the-order-confirmation-email "Permalink to this headline")

In some usecases you may be wondering if it is possible to completely turn off the order confirmation email after the order complete.

This is a complicated situation, because we need to be precise what is our expected result:

* [to disable that email in the system completely](#disabling-the-email-in-the-configuration),

* [to send a different email on the complete action of an order instead of the order confirmation email](#disabling-the-listener-responsible-for-that-action),

Below a few ways to disable that email are presented:

###### Disabling the email in the configuration[¶](#disabling-the-email-in-the-configuration "Permalink to this headline")

There is a pretty straightforward way to disable an e-mail using just a few lines of yaml:

```

# config/packages/sylius\_mailer.yaml

sylius\_mailer:

 emails:

 order\_confirmation:

 enabled: false

```

That’s all. With that configuration the order confirmation email will not be sent.

###### Disabling the listener responsible for that action[¶](#disabling-the-listener-responsible-for-that-action "Permalink to this headline")

To easily turn off the sending of the order confirmation email you will need to disable the `OrderCompleteListener` service.

This can be done via a CompilerPass.

```

<?php

namespace App\DependencyInjection\Compiler;

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;

use Symfony\Component\DependencyInjection\ContainerBuilder;

final class MailPass implements CompilerPassInterface

{

    public function process(ContainerBuilder $container): void

    {

        $container->removeDefinition('sylius.listener.order\_complete');

    }

}

```

The above compiler pass needs to be added to your kernel in the `src/Kernel.php` file:

```

<?php

namespace App;

use App\DependencyInjection\Compiler\MailPass;

// ...

final class Kernel extends BaseKernel

{

    // ...

    public function build(ContainerBuilder $container): void

    {

        parent::build($container);

        $container->addCompilerPass(new MailPass());

    }

}

```

That’s it, we have removed the definition of the listener that is responsible for sending the order confirmation email.

###### Learn more[¶](#learn-more "Permalink to this headline")

* [Compiler passes in the Symfony documentation](https://symfony.com/doc/current/service_container/compiler_passes.html)

##### How to configure mailer?[¶](#how-to-configure-mailer "Permalink to this headline")

There are many services used for sending transactional emails in web applications. You can find for instance

[Mailjet](https://www.mailjet.com), [Mandrill](https://www.mandrill.com) or [SendGrid](https://sendgrid.com) among them.

In Sylius emails are configured the Symfony way, so you can get inspired by the Symfony guides to those mailing services.

Basically to start sending emails via a mailing service you will need to:

1. **Create an account on a mailing service.**

2. **In the your** .env file **modify variable** `MAILER\_URL`

```

MAILER_URL=gmail://username:password@localhost

```

Emails delivery is disable for test, dev and stage environments by default. The prod environment has delivery turned

on by default, so there is nothing to worry about if you did not change anything about it.

**That’s pretty much all! All the other issues are dependent on the service you are using.**

Warning

Remember that the parameters like username or password must not be committed publicly to your repository.

Save them as environment variables on your server.

###### Learn More[¶](#learn-more "Permalink to this headline")

* [Emails Concept](index.html#document-book/architecture/emails)

* [Sending configurable e-mails in Symfony Blogpost](https://sylius.com/blog/sending-configurable-e-mails-in-symfony)

* [How to configure mailer?](index.html#document-cookbook/emails/mailer)

* [How to send a custom e-mail?](index.html#document-cookbook/emails/custom-email)

* [How to customize email templates per channel?](index.html#document-cookbook/emails/custom-email-template-per-channel)

* [How to disable the order confirmation email?](index.html#document-cookbook/emails/disabling-order-confirmation-email)

#### Promotions[¶](#promotions "Permalink to this headline")

##### How to add a custom cart promotion rule?[¶](#how-to-add-a-custom-cart-promotion-rule "Permalink to this headline")

Adding new, custom rules to your shop is a common usecase. You can imagine for instance, that you have some customers

in your shop that you distinguish as premium. And for these premium customers you would like to give special promotions.

For that you will need a new PromotionRule that will check if the customer is premium or not.

###### Create a new cart promotion rule[¶](#create-a-new-cart-promotion-rule "Permalink to this headline")

The new Rule needs a RuleChecker class:

```

<?php

namespace App\Promotion\Checker\Rule;

use Sylius\Component\Promotion\Checker\Rule\RuleCheckerInterface;

use Sylius\Component\Promotion\Model\PromotionSubjectInterface;

class PremiumCustomerRuleChecker implements RuleCheckerInterface

{

    const TYPE = 'premium\_customer';

    /\*\*

 \* {@inheritdoc}

 \*/

    public function isEligible(PromotionSubjectInterface $subject, array $configuration): bool

    {

        return $subject->getCustomer()->isPremium();

    }

}

```

###### Prepare a configuration form type for your new rule[¶](#prepare-a-configuration-form-type-for-your-new-rule "Permalink to this headline")

To be able to configure a cart promotion with your new rule you will need a form type for the admin panel.

Create the configuration form type class:

```

<?php

namespace App\Form\Type\Rule;

use Symfony\Component\Form\AbstractType;

class PremiumCustomerConfigurationType extends AbstractType

{

    /\*\*

 \* {@inheritdoc}

 \*/

    public function getBlockPrefix()

    {

        return 'app\_promotion\_rule\_premium\_customer\_configuration';

    }

}

```

And configure it in the `config/services.yaml`:

```

# config/services.yaml

app.form.type.promotion\_rule.premium\_customer\_configuration:

 class: App\Form\Type\Rule\PremiumCustomerConfigurationType

 tags:

 - { name: form.type }

```

Register the new rule checker as a service in the `config/services.yaml`:

```

# config/services.yaml

services:

 app.promotion\_rule\_checker.premium\_customer:

 class: App\Promotion\Checker\Rule\PremiumCustomerRuleChecker

 tags:

 - { name: sylius.promotion\_rule\_checker, type: premium\_customer, form\_type: App\Form\Type\Rule\PremiumCustomerConfigurationType, label: Premium customer }

```

That’s all. You will now be able to choose the new rule while creating a new promotion.

Tip

Depending on the type of rule that you would like to configure you may need to configure its form fields.

See how we do it [here for example](https://github.com/Sylius/Sylius/blob/master/src/Sylius/Bundle/PromotionBundle/Form/Type/Rule/ItemTotalConfigurationType.php).

###### Learn more[¶](#learn-more "Permalink to this headline")

* [Customization Guide](index.html#document-customization/index)

* [Cart Promotions Concept Documentation](index.html#document-book/orders/cart-promotions)

##### How to add a custom cart promotion action?[¶](#how-to-add-a-custom-cart-promotion-action "Permalink to this headline")

Let’s assume that you would like to have a cart promotion that gives **100% discount on the cheapest item in the cart**.

See what steps need to be taken to achieve that:

###### Create a new cart promotion action[¶](#create-a-new-cart-promotion-action "Permalink to this headline")

You will need a new class `CheapestProductDiscountPromotionActionCommand`.

It will give a discount equal to the unit price of the cheapest item. That’s why it needs to have the Proportional Distributor and

the Adjustments Applicator. The `execute` method applies the discount and distributes it properly on the totals.

This class needs also a `isConfigurationValid()` method which was omitted in the snippet below.

```

<?php

namespace App\Promotion\Action;

use Sylius\Component\Core\Promotion\Action\DiscountPromotionActionCommand;

class CheapestProductDiscountPromotionActionCommand extends DiscountPromotionActionCommand

{

    const TYPE = 'cheapest\_item\_discount';

    /\*\*

 \* @var ProportionalIntegerDistributorInterface

 \*/

    private $proportionalDistributor;

    /\*\*

 \* @var UnitsPromotionAdjustmentsApplicatorInterface

 \*/

    private $unitsPromotionAdjustmentsApplicator;

    /\*\*

 \* @param ProportionalIntegerDistributorInterface $proportionalIntegerDistributor

 \* @param UnitsPromotionAdjustmentsApplicatorInterface $unitsPromotionAdjustmentsApplicator

 \*/

    public function \_\_construct(

        ProportionalIntegerDistributorInterface $proportionalIntegerDistributor,

        UnitsPromotionAdjustmentsApplicatorInterface $unitsPromotionAdjustmentsApplicator

    ) {

        $this->proportionalDistributor = $proportionalIntegerDistributor;

        $this->unitsPromotionAdjustmentsApplicator = $unitsPromotionAdjustmentsApplicator;

    }

    /\*\*

 \* {@inheritdoc}

 \*/

    public function execute(PromotionSubjectInterface $subject, array $configuration, PromotionInterface $promotion)

    {

        if (!$subject instanceof OrderInterface) {

            throw new UnexpectedTypeException($subject, OrderInterface::class);

        }

        $items = $subject->getItems();

        $cheapestItem = $items->first();

        $itemsTotals = [];

        foreach ($items as $item) {

            $itemsTotals[] = $item->getTotal();

            $cheapestItem = ($item->getVariant()->getPrice() < $cheapestItem->getVariant()->getPrice()) ? $item : $cheapestItem;

        }

        $splitPromotion = $this->proportionalDistributor->distribute($itemsTotals, -1 \* $cheapestItem->getVariant()->getPrice());

        $this->unitsPromotionAdjustmentsApplicator->apply($subject, $promotion, $splitPromotion);

    }

    /\*\*

 \* {@inheritdoc}

 \*/

    public function getConfigurationFormType()

    {

        return CheapestProductDiscountConfigurationType::class;

    }

}

```

###### Prepare a configuration form type for the admin panel[¶](#prepare-a-configuration-form-type-for-the-admin-panel "Permalink to this headline")

The new action needs a form type to be available in the admin panel, while creating a new cart promotion.

```

<?php

namespace App\Form\Type\Action;

use Symfony\Component\Form\AbstractType;

class CheapestProductDiscountConfigurationType extends AbstractType

{

    /\*\*

 \* {@inheritdoc}

 \*/

    public function getBlockPrefix()

    {

        return 'app\_promotion\_action\_cheapest\_product\_discount\_configuration';

    }

}

```

###### Register the action as a service[¶](#register-the-action-as-a-service "Permalink to this headline")

In the `config/services.yaml` configure:

```

# config/services.yaml

app.promotion\_action.cheapest\_product\_discount:

 class: App\Promotion\Action\CheapestProductDiscountPromotionActionCommand

 arguments: ['@sylius.proportional\_integer\_distributor', '@sylius.promotion.units\_promotion\_adjustments\_applicator']

 tags:

 - { name: sylius.promotion\_action, type: cheapest\_product\_discount, form\_type: App\Form\Type\Action\CheapestProductDiscountConfigurationType, label: Cheapest product discount }

```

###### Register the form type as a service[¶](#register-the-form-type-as-a-service "Permalink to this headline")

In the `config/services.yaml` configure:

```

# config/services.yaml

app.form.type.promotion\_action.cheapest\_product\_discount\_configuration:

 class: App\Form\Type\Action\CheapestProductDiscountConfigurationType

 tags:

 - { name: form.type }

```

###### Create a new cart promotion with your action[¶](#create-a-new-cart-promotion-with-your-action "Permalink to this headline")

Go to the admin panel of your system. On the `/admin/promotions/new` url you can create a new promotion.

In its configuration you can choose your new “Cheapest product discount” action.

That’s all. **Done!**

###### Learn more[¶](#learn-more "Permalink to this headline")

* [Customization Guide](index.html#document-customization/index)

* [Cart Promotions Concept Documentation](index.html#document-book/orders/cart-promotions)

##### How to add a custom catalog promotion scope?[¶](#how-to-add-a-custom-catalog-promotion-scope "Permalink to this headline")

Adding a new, custom catalog promotion scope to your shop may become a quite helpful extension to your own Catalog Promotions.

You can imagine for instance, that you have some custom way of aggregating products, or any other method of filtering them.

These products that will fulfill your specific scope will become eligible for actions of Catalog Promotion, and as we know

cheaper Products attract more customers.

Let’s try implementing the new **Catalog Promotion Scope** in this cookbook, that will work with Products that contains a phrase.

Note

If you are familiar with **Cart Promotions** and you know how **Cart Promotion Rules** work,

then the Catalog Promotion Scope should look familiar, as the concept of them is quite similar.

###### Create a new catalog promotion scope[¶](#create-a-new-catalog-promotion-scope "Permalink to this headline")

We should start from creating a provider that will return for us all of eligible product variants. Let’s declare the service:

```

# config/services.yaml

App\Checker\InByPhraseScopeVariantsChecker:

 arguments:

 - '@sylius.repository.product\_variant'

 tags:

 - { name: 'sylius.catalog\_promotion.variant\_checker', type: 'by\_phrase' }

```

Note

Please take a note on tag of Checker, thanks to it those services are working properly.

And the code for the checker itself:

```

<?php

namespace App\Checker;

use Sylius\Component\Core\Repository\ProductVariantRepositoryInterface;

use Webmozart\Assert\Assert;

use Sylius\Component\Promotion\Model\CatalogPromotionScopeInterface;

class InByPhraseScopeVariantsChecker implements VariantInScopeCheckerInterface

{

    public const TYPE = 'by\_phrase';

    private ProductVariantRepositoryInterface $productVariantRepository;

    public function \_\_construct(ProductVariantRepositoryInterface $productVariantRepository)

    {

        $this->productVariantRepository = $productVariantRepository;

    }

    public function inScope(CatalogPromotionScopeInterface $scope, ProductVariantInterface $productVariant): bool

    {

        $configuration = $scope->getConfiguration();

        Assert::keyExists($configuration, 'phrase', 'This scope should have configured phrase');

        return str\_contains($productVariant->getName(), $configuration['phrase']);

    }

}

```

Now the Catalog Promotion should work with your new Scope for programmatically and API created resource.

###### Validation[¶](#validation "Permalink to this headline")

As your new Scope requires only basic syntactical validation, it’s recommended to configure it on the Form type, rather

than in the custom validator.

###### Prepare a configuration form type for your new scope[¶](#prepare-a-configuration-form-type-for-your-new-scope "Permalink to this headline")

To be able to configure a Catalog Promotion with your new Scope you will need a form type for the admin panel.

With current implementation first you need to create a twig template for new Scope:

```

{# templates/bundles/SyliusAdminBundle/CatalogPromotion/Scope/by\_phrase.html.twig #}

{% form\_theme field '@SyliusAdmin/Form/theme.html.twig' %}

{{ form\_row(field.phrase, {}) }}

```

Now let’s create a form type and declare it service:

```

# config/services.yaml

App\Form\Type\CatalogPromotionScope\ByPhraseScopeConfigurationType:

 arguments:

 - '@sylius.repository.product\_variant'

 tags:

 - { name: 'sylius.catalog\_promotion.scope\_configuration\_type', key: 'by\_phrase' }

 - { name: 'form.type' }

```

```

<?php

namespace App\Form\Type\CatalogPromotionScope;

use Symfony\Component\Form\AbstractType;

use Symfony\Component\Form\Extension\Core\Type\TextType;

use Symfony\Component\Form\FormBuilderInterface;

use Symfony\Component\Validator\Constraints\NotBlank;

final class ByPhraseScopeConfigurationType extends AbstractType

{

    public function buildForm(FormBuilderInterface $builder, array $options): void

    {

        $builder->add('phrase', TextType::class, [

            'label' => 'Phrase',

            'constraints' => [

                new NotBlank(['groups' => ['sylius']]),

            ],

        ]);

    }

    public function getBlockPrefix(): string

    {

        return 'sylius\_catalog\_promotion\_scope\_by\_phrase\_configuration';

    }

}

```

And with current implementation, there is also a need to override a `default.html.twig` template with key that is first in alphabetical order.

In our case - we have a template `by\_phrase.html.twig` which is first before out of the box `for\_products`, `for\_variants` and `for\_taxons` templates:

```

{# templates/bundles/SyliusAdminBundle/CatalogPromotion/Scope/default.html.twig #}

{% include 'bundles/SyliusAdminBundle/CatalogPromotion/Scope/by\_phrase.html.twig' %}

```

Note

This overriding will be suspect of change, so there won’t be need for declaring `default.html.twig` template anymore.

Note

There is a need to define translation key in the proper format for every catalog promotion scope as they are used in form types

to properly display different scopes. The required type is: `sylius.form.catalog\_promotion.scope.TYPE` where `TYPE` is the catalog promotion scope type.

###### Prepare a scope template for show page of catalog promotion[¶](#prepare-a-scope-template-for-show-page-of-catalog-promotion "Permalink to this headline")

The last thing is to create a template to display our new scope properly. Remember to name it the same as the scope type.

```

{# templates/bundles/SyliusAdminBundle/CatalogPromotion/Show/Scope/by\_phrase.html.twig #}

<table class="ui very basic celled table">

    <tbody>

    <tr>

        <td class="five wide"><strong class="gray text">Type</strong></td>

        <td>By phrase</td>

    </tr>

    <tr>

        <td class="five wide"><strong class="gray text">Phrase</strong></td>

        <td>{{ scope.configuration.phrase }}</td>

    </tr>

    </tbody>

</table>

```

That’s all. You will now be able to choose the new scope while creating or editing a catalog promotion.

###### Learn more[¶](#learn-more "Permalink to this headline")

* [Customization Guide](index.html#document-customization/index)

* [Catalog Promotion Concept Book](index.html#document-book/products/catalog_promotions)

##### How to add a custom catalog promotion action?[¶](#how-to-add-a-custom-catalog-promotion-action "Permalink to this headline")

Adding a new, custom catalog promotion action to your shop may become a quite helpful extension to your own Catalog Promotions.

You can create your own calculator tailored to your product catalog to attract as many people as possible.

Let’s try to implement the new **Catalog Promotion Action** in this cookbook that will lower the price of the product

or product variant to a specific value.

Note

If you are familiar with **Cart Promotions** and you know how **Cart Promotion Actions** work,

then the Catalog Promotion Action should look familiar, as the concept of them is quite similar.

###### Create a new catalog promotion action[¶](#create-a-new-catalog-promotion-action "Permalink to this headline")

We should start from creating a calculator that will return a proper price for given channel pricing. Let’s declare the service:

```

# config/services.yaml

 App\Calculator\FixedPriceCalculator:

 tags:

 - { name: 'sylius.catalog\_promotion.price\_calculator', type: 'fixed\_price' }

```

Note

Please take a note on a declared tag of calculator, it is necessary for this service to be taken into account.

And the code for the calculator itself:

```

<?php

declare(strict\_types=1);

namespace App\Calculator;

use App\Model\CatalogPromotionActionInterface;

use Sylius\Bundle\CoreBundle\CatalogPromotion\Calculator\ActionBasedPriceCalculatorInterface;

use Sylius\Component\Core\Model\ChannelPricingInterface;

use Sylius\Component\Promotion\Model\CatalogPromotionActionInterface as BaseCatalogPromotionActionInterface;

final class FixedPriceCalculator implements ActionBasedPriceCalculatorInterface

{

    public const TYPE = 'fixed\_price';

    public function supports(BaseCatalogPromotionActionInterface $action): bool

    {

        return $action->getType() === self::TYPE;

    }

    public function calculate(ChannelPricingInterface $channelPricing, BaseCatalogPromotionActionInterface $action): int

    {

        if (!isset($action->getConfiguration()[$channelPricing->getChannelCode()])) {

            return $channelPricing->getPrice();

        }

        $price = $action->getConfiguration()[$channelPricing->getChannelCode()]['price'];

        $minimumPrice = $this->provideMinimumPrice($channelPricing);

        if ($price < $minimumPrice) {

            return $minimumPrice;

        }

        return $price;

    }

    private function provideMinimumPrice(ChannelPricingInterface $channelPricing): int

    {

        if ($channelPricing->getMinimumPrice() <= 0) {

            return 0;

        }

        return $channelPricing->getMinimumPrice();

    }

}

```

Now the catalog promotion should work with your new action for resources created both programmatically and via API.

Let’s now prepare a custom validator for the newly created action.

###### Prepare a custom validator for the new action[¶](#prepare-a-custom-validator-for-the-new-action "Permalink to this headline")

We can start with configuration, declare our basic validator for this particular action:

```

# config/services.yaml

App\Validator\CatalogPromotionAction\FixedPriceActionValidator:

 arguments:

 - '@sylius.repository.channel'

 tags:

 - { name: 'sylius.catalog\_promotion.action\_validator', key: 'fixed\_price' }

```

In this validator, we will check the provided configuration for necessary data and if the configured channels exist.

```

<?php

declare(strict\_types=1);

namespace App\Validator\CatalogPromotionAction;

use Sylius\Bundle\PromotionBundle\Validator\CatalogPromotionAction\ActionValidatorInterface;

use Sylius\Bundle\PromotionBundle\Validator\Constraints\CatalogPromotionAction;

use Sylius\Component\Channel\Repository\ChannelRepositoryInterface;

use Symfony\Component\Validator\Constraint;

use Symfony\Component\Validator\Context\ExecutionContextInterface;

use Webmozart\Assert\Assert;

final class FixedPriceActionValidator implements ActionValidatorInterface

{

    private ChannelRepositoryInterface $channelRepository;

    public function \_\_construct(ChannelRepositoryInterface $channelRepository)

    {

        $this->channelRepository = $channelRepository;

    }

    public function validate(array $configuration, Constraint $constraint, ExecutionContextInterface $context): void

    {

        /\*\* @var CatalogPromotionAction $constraint \*/

        Assert::isInstanceOf($constraint, CatalogPromotionAction::class);

        if (empty($configuration)) {

            $context->buildViolation('There is no configuration provided.')->atPath('configuration')->addViolation();

            return;

        }

        foreach ($configuration as $channelCode => $channelConfiguration) {

            if (null === $this->channelRepository->findOneBy(['code' => $channelCode])) {

                $context->buildViolation('The provided channel is not valid.')->atPath('configuration')->addViolation();

                return;

            }

        }

    }

}

```

Alright, we have a working basic validation, and our new type of action exists, can be created, and edited

programmatically or by API. Let’s now prepare the UI part of this new feature.

###### Prepare a configuration form type for the new action[¶](#prepare-a-configuration-form-type-for-the-new-action "Permalink to this headline")

To be able to configure a catalog promotion with your new action you will need a form type for the admin panel.

And with the current implementation, as our action is channel-based, you need to create 2 form types as below:

```

# config/services.yaml

App\Form\Type\CatalogPromotionAction\ChannelBasedFixedPriceActionConfigurationType:

 tags:

 - { name: 'sylius.catalog\_promotion.action\_configuration\_type', key: 'fixed\_price' }

 - { name: 'form.type' }

```

```

<?php

declare(strict\_types=1);

namespace App\Form\Type\CatalogPromotionAction;

use Sylius\Bundle\MoneyBundle\Form\Type\MoneyType;

use Symfony\Component\Form\AbstractType;

use Symfony\Component\Form\FormBuilderInterface;

use Symfony\Component\OptionsResolver\OptionsResolver;

use Symfony\Comp§onent\Validator\Constraints\GreaterThan;

use Symfony\Component\Validator\Constraints\NotBlank;

final class FixedPriceActionConfigurationType extends AbstractType

{

    public function buildForm(FormBuilderInterface $builder, array $options): void

    {

        $builder

            ->add('price', MoneyType::class, [

                'label' => 'Price',

                'currency' => $options['currency'],

                'constraints' => [

                    new NotBlank([

                        'groups' => 'sylius',

                        'message' => 'Price needs to be set',

                    ]),

                    new GreaterThan([

                        'value' => 0,

                        'groups' => 'sylius',

                        'message' => 'Price cannot be lower than 0',

                    ]),

                ],

            ])

        ;

    }

    public function configureOptions(OptionsResolver $resolver): void

    {

        $resolver

            ->setRequired('currency')

            ->setAllowedTypes('currency', 'string')

        ;

    }

    public function getBlockPrefix(): string

    {

        return 'app\_catalog\_promotion\_action\_fixed\_price\_configuration';

    }

}

```

```

<?php

declare(strict\_types=1);

namespace App\Form\Type\CatalogPromotionAction;

use Sylius\Bundle\CoreBundle\Form\Type\ChannelCollectionType;

use Sylius\Component\Core\Model\ChannelInterface;

use Symfony\Component\Form\AbstractType;

use Symfony\Component\OptionsResolver\OptionsResolver;

final class ChannelBasedFixedPriceActionConfigurationType extends AbstractType

{

    public function configureOptions(OptionsResolver $resolver): void

    {

        $resolver->setDefaults([

            'entry\_type' => FixedPriceActionConfigurationType::class,

            'entry\_options' => function (ChannelInterface $channel) {

                return [

                    'label' => $channel->getName(),

                    'currency' => $channel->getBaseCurrency()->getCode(),

                ];

            },

        ]);

    }

    public function getParent(): string

    {

        return ChannelCollectionType::class;

    }

}

```

And define the translation for our new action type:

```

# translations/messages.en.yaml

sylius:

 form:

 catalog\_promotion:

 action:

 fixed\_price: 'Fixed price'

```

Note

There is a need to define translation key in the proper format for every catalog promotion action as they are used in form types

to properly display different actions. The required type is: `sylius.form.catalog\_promotion.action.TYPE` where `TYPE` is the catalog promotion action type.

###### Prepare an action template for show page of catalog promotion[¶](#prepare-an-action-template-for-show-page-of-catalog-promotion "Permalink to this headline")

The last thing is to create a template to display our new action properly. Remember to name it the same as the action type.

```

{# templates/bundles/SyliusAdminBundle/CatalogPromotion/Show/Action/fixed\_price.html.twig #}

{% import "@SyliusAdmin/Common/Macro/money.html.twig" as money %}

<table class="ui very basic celled table">

    <tbody>

    <tr>

        <td class="five wide"><strong class="gray text">Type</strong></td>

        <td>Fixed price</td>

    </tr>

    {% set currencies = sylius\_channels\_currencies() %}

    {% for channelCode, channelConfiguration in action.configuration %}

        <tr>

            <td class="five wide"><strong class="gray text">{{ channelCode }}</strong></td>

            <td>{{ money.format(channelConfiguration.price, currencies[channelCode]) }}</td>

        </tr>

    {% endfor %}

    </tbody>

</table>

```

That’s all. You will now be able to choose the new action while creating or editing a catalog promotion.

###### Learn more[¶](#learn-more "Permalink to this headline")

* [Customization Guide](index.html#document-customization/index)

* [Catalog Promotion Concept Book](index.html#document-book/products/catalog_promotions)

##### How to customize catalog promotion labels?[¶](#how-to-customize-catalog-promotion-labels "Permalink to this headline")

To customize catalog promotion labels only what need to do is overwrite their template.

Tip

Learn more about template customization [here](index.html#document-customization/template).

###### Change the catalog promotion labels’ styles[¶](#change-the-catalog-promotion-labels-styles "Permalink to this headline")

By default Sylius displays catalog promotion labels in two locations: on the product’s index on product’s card and also on the product show page.

Both places use the same template (`@SyliusShop/Product/Show/\_catalogPromotionLabels.html.twig`) which you need to override with your own:

`templates/bundles/SyliusShopBundle/Product/Show/\_catalogPromotionLabels.html.twig` that will have such content:

```

<div id="appliedPromotions" data-applied-promotions-locale="{{ sylius.localeCode }}">

 {% for appliedPromotion in appliedPromotions %}

 <div class="ui red big label promotion\_label" style="margin: 0.5rem 0;">

 <div class="row ui small sylius\_catalog\_promotion">

 {{ appliedPromotion.label }}{% if appliedPromotion.description and withDescription %} - {{ appliedPromotion.description }}{% endif %}

 </div>

 </div>

 {% endfor %}

</div>

```

How will it look after changes?

Product index:

![_images/catalog_promotion_label_product_index.png](_images/catalog_promotion_label_product_index.png)

Product show:

![_images/catalog_promotion_label_product_show.png](_images/catalog_promotion_label_product_show.png)

Of course, you can modify the styles how you need, also in your stylesheet.

###### Display catalog promotion labels on the product image[¶](#display-catalog-promotion-labels-on-the-product-image "Permalink to this headline")

In order to change the location of catalog promotion label on the product’s card, we need to overwrite two templates.

We will remove the label from underneath the photo in product card template and then add it on the product’s image.

Override the `@SyliusShopBundle/Product/Box/\_content.html.twig` template with `templates/bundles/SyliusShopBundle/Product/Box/\_content.html.twig`:

```

{% import "@SyliusShop/Common/Macro/money.html.twig" as money %}

<div class="ui fluid card" {{ sylius\_test\_html\_attribute('product') }}>

 <a href="{{ path('sylius\_shop\_product\_show', {'slug': product.slug, '\_locale': product.translation.locale}) }}" class="blurring dimmable image">

 <div class="ui dimmer">

 <div class="content">

 <div class="center">

 <div class="ui inverted button">{{ 'sylius.ui.view\_more'|trans }}</div>

 </div>

 </div>

 </div>

 {% include '@SyliusShop/Product/\_mainImage.html.twig' with {'product': product} %}

 </a>

 <div class="content" {{ sylius\_test\_html\_attribute('product-content') }}>

 <a href="{{ path('sylius\_shop\_product\_show', {'slug': product.slug, '\_locale': product.translation.locale}) }}" class="header sylius-product-name" {{ sylius\_test\_html\_attribute('product-name', product.name) }}>{{ product.name }}</a>

 {% if not product.enabledVariants.empty() %}

 {% set price = money.calculatePrice(product|sylius\_resolve\_variant) %}

 {% set originalPrice = money.calculateOriginalPrice(product|sylius\_resolve\_variant) %}

 {% if price != originalPrice %}

 <div class="sylius-product-original-price" {{ sylius\_test\_html\_attribute('product-original-price') }}><del>{{ originalPrice }}</del></div>

 {% endif %}

 <div class="sylius-product-price" {{ sylius\_test\_html\_attribute('product-price') }}>{{ price }}</div>

 {% endif %}

 </div>

</div>

```

And the `@SyliusShopBundle/Product/\_mainImage.html.twig` with `templates/bundles/SyliusShopBundle/Product/\_mainImage.html.twig`:

```

{% if product.imagesByType('thumbnail') is not empty %}

 {% set path = product.imagesByType('thumbnail').first.path|imagine\_filter(filter|default('sylius\_shop\_product\_thumbnail')) %}

{% elseif product.images.first %}

 {% set path = product.images.first.path|imagine\_filter(filter|default('sylius\_shop\_product\_thumbnail')) %}

{% else %}

 {% set path = '//placehold.it/200x200' %}

{% endif %}

{% set variant = product|sylius\_resolve\_variant %}

{% set channelPricing = variant.getChannelPricingForChannel(sylius.channel) %}

<div style="position: relative;">

 <img src="{{ path }}" {{ sylius\_test\_html\_attribute('main-image') }} alt="{{ product.name }}" class="ui bordered image" />

 <div id="appliedPromotions" style="position: absolute; right: 5px; top: 5px" data-applied-promotions-locale="{{ sylius.localeCode }}">

 {% for appliedPromotion in channelPricing.appliedPromotions %}

 <div class="ui blue label promotion\_label" style="margin: 1rem 0;">

 <div class="row ui small sylius\_catalog\_promotion">

 {{ appliedPromotion.name }}

 </div>

 </div>

 {% endfor %}

 </div>

</div>

```

After changes:

![_images/catalog_promotion_repositioned_label.png](_images/catalog_promotion_repositioned_label.png)

Well done! Now you can do anything you want with the catalog promotion labels.

##### Catalog promotion performance[¶](#catalog-promotion-performance "Permalink to this headline")

In case of looking for improving performance of processing your catalog promotions

###### Change default batch size:[¶](#change-default-batch-size "Permalink to this headline")

By default `batch\_size` in Sylius is fixed at processing `100` variants per batch.

In order if you want to increase or decrease it’s value, you have to overwrite it.

You can achieve that by adding to your `\_sylius.yaml` configuration file this config with changed `batch\_size` value:

```

sylius\_core:

 catalog\_promotions:

 batch\_size: 100

```

Then your batch size should be fixed to passed value.

* [How to add a custom cart promotion rule?](index.html#document-cookbook/promotions/custom-cart-promotion-rule)

* [How to add a custom cart promotion action?](index.html#document-cookbook/promotions/custom-cart-promotion-action)

* [How to add a custom catalog promotion scope?](index.html#document-cookbook/promotions/custom-catalog-promotion-scope)

* [How to add a custom catalog promotion action?](index.html#document-cookbook/promotions/custom-catalog-promotion-action)

* [How to customize catalog promotion labels?](index.html#document-cookbook/promotions/catalog-promotion-labels)

* [Catalog promotion performance](index.html#document-cookbook/promotions/catalog-promotion-performance)

#### Inventory[¶](#inventory "Permalink to this headline")

##### How to create a custom inventory sources filter?[¶](#how-to-create-a-custom-inventory-sources-filter "Permalink to this headline")

In this guide, we will create a new inventory source filter with the lowest priority, that provides

inventory source with the lowest stock.

###### 1. Implement LeastItemsInventorySourcesFilter[¶](#implement-leastitemsinventorysourcesfilter "Permalink to this headline")

First of all, the inventory sources filter has to implement the

`Sylius\Plus\Inventory\Application\Filter\InventorySourcesFilterInterface`.

Then implement the behaviour inside of the `filter` method of your service:

```

<?php

declare(strict\_types=1);

namespace App\Inventory\Filter;

use Sylius\Plus\Entity\ProductVariantInterface;

use Sylius\Plus\Inventory\Application\Filter\InventorySourcesFilterInterface;

use Sylius\Plus\Inventory\Domain\Model\InventorySourceInterface;

use Sylius\Plus\Inventory\Domain\Model\VariantsQuantityMapInterface;

final class LeastItemsInventorySourcesFilter implements InventorySourcesFilterInterface

{

    public function filter(array $inventorySources, VariantsQuantityMapInterface $variantsQuantityMap): array

    {

        $inventorySourcesItems = [];

        /\*\* @var InventorySourceInterface $inventorySource \*/

        foreach ($inventorySources as $inventorySource) {

            $inventorySourcesItems[$inventorySource->getCode()] = 0;

            foreach ($variantsQuantityMap->iterate() as $variantData) {

                /\*\* @var ProductVariantInterface $variant \*/

                $variant = $variantData['variant'];

                if (!$variant->isTracked()) {

                    continue;

                }

                $stock = $variant->getInventorySourceStockForInventorySource($inventorySource);

                $inventorySourcesItems[$inventorySource->getCode()] += $stock->getAvailable();

            }

        }

        return [$this->getInventorySourceByCode(

            $inventorySources,

            array\_search(min($inventorySourcesItems), $inventorySourcesItems)

        )];

    }

    private function getInventorySourceByCode(array $inventorySources, string $code): ?InventorySourceInterface

    {

        /\*\* @var InventorySourceInterface $inventorySource \*/

        foreach ($inventorySources as $inventorySource) {

            if ($inventorySource->getCode() === $code) {

                return $inventorySource;

            }

        }

        return null;

    }

}

```

###### 2. Register the custom filter with defined priority[¶](#register-the-custom-filter-with-defined-priority "Permalink to this headline")

Your filtering service has to be registered in the `config/services.yaml` file

with `sylius\_plus.inventory.inventory\_sources\_filter` tag and `priority` attribute set, as we can see below:

```

services:

 App\Inventory\Filter\LeastItemsInventorySourcesFilter:

 tags:

 - { name: 'sylius\_plus.inventory.inventory\_sources\_filter', priority: -10 }

```

After registering the filter, with such a priority, it will be invoked as the last one, after the default filters

provided in Sylius Plus.

That’s all you have to do to customize the inventory sources resolving strategy in your application.

###### Learn more[¶](#learn-more "Permalink to this headline")

* [Multi-Source Inventory concept documentation](index.html#document-book/products/multi_source_inventory)

* [Single Source Inventory concept documentation](index.html#document-book/products/inventory)

* [Order concept documentation](index.html#document-book/orders/orders)

[![_images/banner8.png](_images/banner8.png)](https://sylius.com/plus/?utm_source=docs)

* [How to create a custom inventory sources filter?](index.html#document-cookbook/inventory/custom-inventory-sources-filter)

#### Shipping methods[¶](#shipping-methods "Permalink to this headline")

##### How to add a custom shipping method rule?[¶](#how-to-add-a-custom-shipping-method-rule "Permalink to this headline")

Shipping method rules are used to show shipping methods if certain criteria are fulfilled.

Say you have a requirement for one of your shipping methods that the volume of the shipment may not exceed 1 cubic meter (i.e. 1 m x 1 m 1 m).

To model this requirement you can use a shipping method rule.

###### Create a new shipping method rule[¶](#create-a-new-shipping-method-rule "Permalink to this headline")

The new rule needs a RuleChecker class:

```

<?php

namespace App\Shipping\Checker\Rule;

use Sylius\Component\Shipping\Checker\Rule\RuleCheckerInterface;

use Sylius\Component\Shipping\Model\ShippingSubjectInterface;

final class TotalVolumeLessThanOrEqualRuleChecker implements RuleCheckerInterface

{

    public const TYPE = 'total\_volume\_less\_than\_or\_equal';

    public function isEligible(ShippingSubjectInterface $shippingSubject, array $configuration): bool

    {

        return $shippingSubject->getShippingVolume() <= $configuration['volume'];

    }

}

```

###### Prepare a configuration form type for your new rule[¶](#prepare-a-configuration-form-type-for-your-new-rule "Permalink to this headline")

To be able to configure a shipping method with your new rule you will need a form type for the admin panel.

Create the configuration form type class:

```

<?php

namespace App\Form\Type\Rule;

use Symfony\Component\Form\AbstractType;

use Symfony\Component\Form\Extension\Core\Type\NumberType;

use Symfony\Component\Form\FormBuilderInterface;

use Symfony\Component\Validator\Constraints\GreaterThan;

use Symfony\Component\Validator\Constraints\NotBlank;

use Symfony\Component\Validator\Constraints\Type;

class TotalVolumeLessThanOrEqualConfigurationType extends AbstractType

{

    public function buildForm(FormBuilderInterface $builder, array $options): void

    {

        $builder->add('volume', NumberType::class, [

            'label' => 'app.form.total\_volume\_less\_than\_or\_equal\_configuration.volume',

            'constraints' => [

                new NotBlank(['groups' => ['sylius']]),

                new Type(['type' => 'numeric', 'groups' => ['sylius']]),

                new GreaterThan(['value' => 0, 'groups' => ['sylius']])

            ],

        ]);

    }

    public function getBlockPrefix()

    {

        return 'app\_shipping\_method\_rule\_total\_volume\_less\_than\_or\_equal\_configuration';

    }

}

```

Register the new rule checker as a service in the `config/services.yaml`:

```

# config/services.yml

app.shipping\_method\_rule\_checker.total\_volume\_less\_than\_or\_equal:

 class: App\Shipping\Checker\Rule\TotalVolumeLessThanOrEqualRuleChecker

 tags:

 - { name: sylius.shipping\_method\_rule\_checker, type: total\_volume\_less\_than\_or\_equal, form\_type: App\Form\Type\Rule\TotalVolumeLessThanOrEqualConfigurationType, label: app.form.shipping\_method\_rule.total\_volume\_less\_than\_or\_equal }

```

That’s all. You will now be able to choose the new rule while creating a new shipping method.

* [How to add a custom shipping method rule?](index.html#document-cookbook/shipping-methods/custom-shipping-method-rule)

#### Images[¶](#images "Permalink to this headline")

##### How to resize images?[¶](#how-to-resize-images "Permalink to this headline")

In Sylius we are using the [LiipImagineBundle](https://symfony.com/doc/current/bundles/LiipImagineBundle/index.html)

for handling images.

Tip

You will find a reference to the types of filters in the LiipImagineBundle [in their documentation](https://symfony.com/doc/current/bundles/LiipImagineBundle/filters.html).

There are three places in the Sylius platform where the configuration for images can be found:

* [AdminBundle](https://github.com/Sylius/Sylius/blob/master/src/Sylius/Bundle/AdminBundle/Resources/config/app/config.yml)

* [ShopBundle](https://github.com/Sylius/Sylius/blob/master/src/Sylius/Bundle/ShopBundle/Resources/config/app/config.yml)

* [CoreBundle](https://github.com/Sylius/Sylius/blob/master/src/Sylius/Bundle/CoreBundle/Resources/config/app/config.yml)

These configs provide you with a set of filters for resizing images to **thumbnails**.

| `sylius\_admin\_product\_original` | size: original |

| `sylius\_admin\_admin\_user\_avatar\_thumbnail` | size: [50, 50] |

| `sylius\_admin\_product\_large\_thumbnail` | size: [550, 412] |

| `sylius\_admin\_product\_small\_thumbnail` | size: [150, 112] |

| `sylius\_admin\_product\_tiny\_thumbnail` | size: [64, 64] |

| `sylius\_admin\_product\_thumbnail` | size: [50, 50] |

| `sylius\_shop\_product\_original` | size: original |

| `sylius\_shop\_product\_tiny\_thumbnail` | size: [64, 64] |

| `sylius\_shop\_product\_small\_thumbnail` | size: [150, 112] |

| `sylius\_shop\_product\_thumbnail` | size: [260, 260] |

| `sylius\_shop\_product\_large\_thumbnail` | size: [550, 412] |

| `sylius\_small` | size: [120, 90] |

| `sylius\_medium` | size: [240, 180] |

| `sylius\_large` | size: [640, 480] |

###### How to resize images with filters?[¶](#how-to-resize-images-with-filters "Permalink to this headline")

Knowing that you have filters out of the box you need to also know how to use them with images in **Twig** templates.

The `imagine\_filter('name')` is a twig filter. This is how you would get an image path for on object `item` with a thumbnail applied:

```

<img src="{{ object.path|imagine\_filter('sylius\_small') }}" />

```

Note

Sylius stores images on entities by saving a `path` to the file in the database.

The imagine\_filter root path is `/public/media/image`.

###### How to add custom image resizing filters?[¶](#how-to-add-custom-image-resizing-filters "Permalink to this headline")

If the filters we have in Sylius by default are not suitable for your needs, you can easily add your own.

All you need to do is to configure new filter in the `config/packages/liip\_imagine.yaml` file.

For example you can create a filter for advertisement banners:

```

# config/packages/liip\_imagine.yaml

liip\_imagine:

 filter\_sets:

 advert\_banner:

 filters:

 thumbnail: { size: [800, 200], mode: inset }

```

**How to use your new filter in Twig?**

```

<img src="{{ banner.path|imagine\_filter('advert\_banner') }}" />

```

###### Learn more[¶](#learn-more "Permalink to this headline")

* [The LiipImagineBundle documentation](https://symfony.com/doc/current/bundles/LiipImagineBundle/index.html)

##### How to add an image to an entity? (One-To-One association)[¶](#how-to-add-an-image-to-an-entity-one-to-one-association "Permalink to this headline")

As an example this cookbook will **add an image to the payment method**.

The example uses a mix of attributes, annotations and yaml for configuration, but you choose your preferred flavor.

###### 1. Create the image entity class[¶](#create-the-image-entity-class "Permalink to this headline")

The `getPaymentMethod` and `setPaymentMethod` are optional and are wrappers for the untyped `getOwner` and `setOwner` methods.

The image `type` is set in the constructor to an unspecific value for convenience because it’s not relevant in a one-to-one relationship.

```

<?php

declare(strict\_types=1);

namespace App\Entity\Payment;

use Doctrine\ORM\Mapping as ORM;

use Sylius\Component\Core\Model\Image;

/\*\*

 \* @ORM\Entity

 \* @ORM\Table(name="sylius\_payment\_method\_image")

 \*

 \* @method PaymentMethod|null getOwner()

 \*/

#[ORM\Entity]

#[ORM\Table(name: 'sylius\_payment\_method\_image')]

class PaymentMethodImage extends Image

{

    /\*\*

 \* @ORM\OneToOne(inversedBy="image", targetEntity="App\Entity\Payment\PaymentMethod")

 \* @ORM\JoinColumn(nullable=false, onDelete="CASCADE")

 \*

 \* @var PaymentMethod|null

 \*/

    #[ORM\OneToOne(inversedBy: 'image', targetEntity: PaymentMethod::class)]

    #[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]

    protected $owner;

    public function \_\_construct()

    {

        $this->type = 'default';

    }

    public function getPaymentMethod(): ?PaymentMethod

    {

        return $this->getOwner();

    }

    public function setPaymentMethod(?PaymentMethod $paymentMethod): void

    {

        $this->setOwner($paymentMethod);

    }

}

```

###### 2. Add the image to the owner entity class[¶](#add-the-image-to-the-owner-entity-class "Permalink to this headline")

```

<?php

declare(strict\_types=1);

namespace App\Entity\Payment;

use Doctrine\ORM\Mapping as ORM;

use Sylius\Component\Core\Model\ImageAwareInterface;

use Sylius\Component\Core\Model\ImageInterface;

use Sylius\Component\Core\Model\PaymentMethod as BasePaymentMethod;

/\*\*

 \* @ORM\Entity

 \* @ORM\Table(name="sylius\_payment\_method")

 \*/

#[ORM\Entity]

#[ORM\Table(name: 'sylius\_payment\_method')]

class PaymentMethod extends BasePaymentMethod implements ImageAwareInterface

{

    /\*\*

 \* @Assert\Valid

 \* @ORM\OneToOne(mappedBy="owner", targetEntity="App\Entity\Payment\PaymentMethodImage", cascade={"all"}, orphanRemoval=true)

 \*/

    #[ORM\OneToOne(mappedBy: 'owner', targetEntity: PaymentMethodImage::class, cascade: ['all'], orphanRemoval: true)]

    protected ?PaymentMethodImage $image = null;

    /\*\* @return PaymentMethodImage|null \*/

    public function getImage(): ?ImageInterface

    {

        return $this->image;

    }

    /\*\* @var PaymentMethodImage|null $image \*/

    public function setImage(?ImageInterface $image): void

    {

        $image?->setOwner($this);

        $this->image = $image;

    }

}

```

###### 3. Create the image form type[¶](#create-the-image-form-type "Permalink to this headline")

The constructor arguments are inlined to facilitate the autowiring, but you may inject them from the service configuration if necessary, an example of that is provided in the next step.

```

<?php

declare(strict\_types=1);

namespace App\Form\Type;

use App\Entity\Payment\PaymentMethodImage;

use Sylius\Bundle\CoreBundle\Form\Type\ImageType;

use Symfony\Component\Form\FormBuilderInterface;

final class PaymentMethodImageType extends ImageType

{

    public function \_\_construct()

    {

        parent::\_\_construct(PaymentMethodImage::class, ['sylius']);

    }

    public function buildForm(FormBuilderInterface $builder, array $options): void

    {

        parent::buildForm($builder, $options);

        $builder->remove('type');

    }

    public function getBlockPrefix(): string

    {

        return 'payment\_method\_image';

    }

}

```

Register the image form type service if necessary, for example in case the autowiring or autoconfiguration are disabled or you wish to inject the constructor arguments from configuration:

```

# services.yml or another configuration file of your choice

services:

 App\Form\Type\PaymentMethodImageType:

# Removing the \_\_constructor from the PaymentMethodImageType and configure the arguments if necessary

# arguments:

# - '%app.model.payment\_method\_image.class%'

# - ['sylius']

 tags:

 - { name: form.type }

```

###### 4. Configure the image resource[¶](#configure-the-image-resource "Permalink to this headline")

```

# config/packages/\_sylius.yaml

sylius\_resource:

 resources:

 app.payment\_method\_image:

 classes:

 model: App\Entity\Payment\PaymentMethodImage

 form: App\Form\Type\PaymentMethodImageType

```

###### 5. Add the image field to the owner form type[¶](#add-the-image-field-to-the-owner-form-type "Permalink to this headline")

A form type extension is used in this example because the image is added to a Sylius entity which already has a form type.

You should add the field directly to the owner entity’s form type if it’s part of your project source code.

```

<?php

declare(strict\_types=1);

namespace App\Form\Extension;

use App\Form\Type\PaymentMethodImageType;

use Sylius\Bundle\PaymentBundle\Form\Type\PaymentMethodType;

use Symfony\Component\Form\AbstractTypeExtension;

use Symfony\Component\Form\FormBuilderInterface;

final class PaymentMethodTypeExtension extends AbstractTypeExtension

{

    public function buildForm(FormBuilderInterface $builder, array $options): void

    {

        $builder

            ->add('image', PaymentMethodImageType::class, [

                'label' => 'sylius.ui.image',

                'required' => false,

            ])

        ;

    }

    public static function getExtendedTypes(): iterable

    {

        return [PaymentMethodType::class];

    }

}

```

Register the owner entity’s form type or form type extension service if necessary, for example in case the autowiring or autoconfiguration are disabled:

```

# services.yml or another configuration file of your choice

services:

 App\Form\Extension\PaymentMethodTypeExtension:

 tags:

 - { name: form.type\_extension }

```

###### 6. Create an event subscriber that will upload the image file[¶](#create-an-event-subscriber-that-will-upload-the-image-file "Permalink to this headline")

Alternatively, you can create an event listener or you may reuse the SyliusBundleCoreBundleEventListenerImageUploadListener service.

```

<?php

declare(strict\_types=1);

namespace App\EventSubscriber;

use Sylius\Component\Core\Model\ImageAwareInterface;

use Sylius\Component\Core\Uploader\ImageUploaderInterface;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;

use Symfony\Component\EventDispatcher\GenericEvent;

use Webmozart\Assert\Assert;

final class ImageUploadSubscriber implements EventSubscriberInterface

{

    public function \_\_construct(private ImageUploaderInterface $uploader)

    {

    }

    public static function getSubscribedEvents(): array

    {

        return [

            'sylius.payment\_method.pre\_create' => 'uploadImage',

            'sylius.payment\_method.pre\_update' => 'uploadImage',

        ];

    }

    public function uploadImage(GenericEvent $event): void

    {

        $subject = $event->getSubject();

        Assert::isInstanceOf($subject, ImageAwareInterface::class);

        $this->uploadSubjectImage($subject);

    }

    private function uploadSubjectImage(ImageAwareInterface $subject): void

    {

        $image = $subject->getImage();

        if (null === $image) {

            return;

        }

        if ($image->hasFile()) {

            $this->uploader->upload($image);

        }

        // Remove image if upload failed

        if (null === $image->getPath()) {

            $subject->setImage(null);

        }

    }

}

```

Configure the service if it’s not done automatically:

```

# services.yml or another configuration file of your choice

services:

 App\EventSubscriber\ImageUploadSubscriber:

 arguments:

 - '@sylius.image\_uploader'

 tags:

 - { name: kernel.event\_subscriber }

```

###### 7. Render the image field in the form view[¶](#render-the-image-field-in-the-form-view "Permalink to this headline")

For this example, we need to customize the form view from `SyliusAdminBundle/views/PaymentMethod/\_form.html.twig`,

so we have to copy it to `templates/bundles/SyliusAdminBundle/PaymentMethod/\_form.html.twig` file and render the

`{{ form\_row(form.image) }}` field.

```

{# templates/bundles/SyliusAdminBundle/PaymentMethod/\_form.html.twig #}

{% form\_theme form '@SyliusAdmin/Form/imagesTheme.html.twig' %}

{# all the contents copied from SyliusAdminBundle/views/PaymentMethod/\_form.html.twig #}

<div class="ui segment">

 {{ form\_row(form.image) }}

</div>

```

To display the current image you have to customize the rendering of the image field.

For that, copy the `SyliusAdmin/Form/imagesTheme.html.twig` file to

`templates/bundles/SyliusAdminBundle/Form/imagesTheme.html.twig` and add the following code to it:

```

{# this is a generic block that can be reused for other images #}

{% block image\_widget %}

 <div class="ui upload box segment" id="{{ form.vars.id }}">

 {% if form.vars.value.path|default(null) is not null %}

 <img class="ui small bordered image" src="{{ form.vars.value.path|imagine\_filter('sylius\_small') }}" alt="{{ form.vars.value.type }}" />

 {% endif %}

 <div class="ui element">

 {{ form\_widget(form.file) }}

 </div>

 <div class="ui element">

 {{- form\_errors(form.file) -}}

 </div>

 </div>

{% endblock %}

{%- block payment\_method\_image\_widget -%}

 {{- block('image\_widget') -}}

{%- endblock -%}

```

###### 8. Add validation constraints as necessary[¶](#add-validation-constraints-as-necessary "Permalink to this headline")

```

<?php

declare(strict\_types=1);

namespace App\Entity\Payment;

use Symfony\Component\Validator\Constraints as Assert;

class PaymentMethodImage extends Image

{

    #[Assert\Image(groups: ['sylius'])] // configure the options according to your needs

    protected $file;

}

```

```

<?php

declare(strict\_types=1);

namespace App\Entity\Payment;

use Symfony\Component\Validator\Constraints as Assert;

class PaymentMethod extends BasePaymentMethod implements ImageAwareInterface

{

    #[Assert\Valid]

    protected ?PaymentMethodImage $image = null;

}

```

###### 9. Generate a Doctrine migration and execute it[¶](#generate-a-doctrine-migration-and-execute-it "Permalink to this headline")

```

bin/console doctrine:migrations:diff

bin/console doctrine:migrations:migrate

```

##### How to add images to an entity?[¶](#how-to-add-images-to-an-entity "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

Extending entities with an `images` field is quite a popular use case.

In this cookbook we will present how to **add image to the Shipping Method entity**.

###### Instructions:[¶](#instructions "Permalink to this headline")

####### 1. Extend the ShippingMethod class with the ImagesAwareInterface[¶](#extend-the-shippingmethod-class-with-the-imagesawareinterface "Permalink to this headline")

In order to override the `ShippingMethod` that lives inside of the SyliusCoreBundle,

you have to create your own ShippingMethod class that will extend it:

```

<?php

declare(strict\_types=1);

namespace App\Entity;

use Doctrine\Common\Collections\ArrayCollection;

use Doctrine\Common\Collections\Collection;

use Sylius\Component\Core\Model\ImagesAwareInterface;

use Sylius\Component\Core\Model\ImageInterface;

use Sylius\Component\Core\Model\ShippingMethod as BaseShippingMethod;

class ShippingMethod extends BaseShippingMethod implements ImagesAwareInterface

{

    /\*\*

 \* @var Collection|ImageInterface[]

 \*/

    protected $images;

    public function \_\_construct()

    {

        parent::\_\_construct();

        $this->images = new ArrayCollection();

    }

    /\*\*

 \* {@inheritdoc}

 \*/

    public function getImages(): Collection

    {

        return $this->images;

    }

    /\*\*

 \* {@inheritdoc}

 \*/

    public function getImagesByType(string $type): Collection

    {

        return $this->images->filter(function (ImageInterface $image) use ($type) {

            return $type === $image->getType();

        });

    }

    /\*\*

 \* {@inheritdoc}

 \*/

    public function hasImages(): bool

    {

        return !$this->images->isEmpty();

    }

    /\*\*

 \* {@inheritdoc}

 \*/

    public function hasImage(ImageInterface $image): bool

    {

        return $this->images->contains($image);

    }

    /\*\*

 \* {@inheritdoc}

 \*/

    public function addImage(ImageInterface $image): void

    {

        $image->setOwner($this);

        $this->images->add($image);

    }

    /\*\*

 \* {@inheritdoc}

 \*/

    public function removeImage(ImageInterface $image): void

    {

        if ($this->hasImage($image)) {

            $image->setOwner(null);

            $this->images->removeElement($image);

        }

    }

}

```

Tip

Read more about customizing models in the docs [here](index.html#document-customization/model).

####### 2. Register your extended ShippingMethod as a resource’s model class[¶](#register-your-extended-shippingmethod-as-a-resource-s-model-class "Permalink to this headline")

With such a configuration you will register your `ShippingMethod` class in order to override the default one:

```

# config/packages/sylius\_shipping.yaml

sylius\_shipping:

 resources:

 shipping\_method:

 classes:

 model: App\Entity\ShippingMethod

```

####### 3. Create the ShippingMethodImage class[¶](#create-the-shippingmethodimage-class "Permalink to this headline")

In the `App\Entity` namespace place the `ShippingMethodImage` class which should look like this:

```

<?php

declare(strict\_types=1);

namespace App\Entity;

use Sylius\Component\Core\Model\Image;

class ShippingMethodImage extends Image

{

}

```

####### 4. Add the mapping file for the ShippingMethodImage[¶](#add-the-mapping-file-for-the-shippingmethodimage "Permalink to this headline")

Your new entity will be saved in the database, therefore it needs a mapping file, where you will set the `ShippingMethod` as the `owner`

of the `ShippingMethodImage`.

```

# App/Resources/config/doctrine/ShippingMethodImage.orm.yml

App\Entity\ShippingMethodImage:

 type: entity

 table: app\_shipping\_method\_image

 manyToOne:

 owner:

 targetEntity: App\Entity\ShippingMethod

 inversedBy: images

 joinColumn:

 name: owner\_id

 referencedColumnName: id

 nullable: false

 onDelete: CASCADE

```

####### 5. Modify the ShippingMethod’s mapping file[¶](#modify-the-shippingmethod-s-mapping-file "Permalink to this headline")

The newly added `images` field has to be added to the mapping, with a relation to the `ShippingMethodImage`:

```

# App/Resources/config/doctrine/ShippingMethod.orm.yml

App\Entity\ShippingMethod:

 type: entity

 table: sylius\_shipping\_method

 oneToMany:

 images:

 targetEntity: App\Entity\ShippingMethodImage

 mappedBy: owner

 orphanRemoval: true

 cascade:

 - all

```

####### 6. Register the ShippingMethodImage as a resource[¶](#register-the-shippingmethodimage-as-a-resource "Permalink to this headline")

The `ShippingMethodImage` class needs to be registered as a Sylius resource:

```

# app/config/config.yml

sylius\_resource:

 resources:

 app.shipping\_method\_image:

 classes:

 model: App\Entity\ShippingMethodImage

```

####### 7. Create the ShippingMethodImageType class[¶](#create-the-shippingmethodimagetype-class "Permalink to this headline")

This is how the class for `ShippingMethodImageType` should look like. Place it in the `App\Form\Type\` directory.

```

<?php

declare(strict\_types=1);

namespace App\Form\Type;

use Sylius\Bundle\CoreBundle\Form\Type\ImageType;

final class ShippingMethodImageType extends ImageType

{

    /\*\*

 \* {@inheritdoc}

 \*/

    public function getBlockPrefix(): string

    {

        return 'app\_shipping\_method\_image';

    }

}

```

####### 8. Register the ShippingMethodImageType as a service[¶](#register-the-shippingmethodimagetype-as-a-service "Permalink to this headline")

After creating the form type class, you need to register it as a `form.type` service like below:

```

# services.yml

services:

 app.form.type.shipping\_method\_image:

 class: App\Form\Type\ShippingMethodImageType

 tags:

 - { name: form.type }

 arguments: ['%app.model.shipping\_method\_image.class%']

```

####### 9. Add the ShippingMethodImageType to the resource form configuration[¶](#add-the-shippingmethodimagetype-to-the-resource-form-configuration "Permalink to this headline")

What is more the new form type needs to be configured as the resource form of the `ShippingMethodImage`:

```

# app/config/config.yml

sylius\_resource:

 resources:

 app.shipping\_method\_image:

 classes:

 form: App\Form\Type\ShippingMethodImageType

```

####### 10. Extend the ShippingMethodType with the images field[¶](#extend-the-shippingmethodtype-with-the-images-field "Permalink to this headline")

Tip

Read more about [customizing forms via extensions in the dedicated guide](index.html#document-customization/form).

**Create the form extension class** for the `Sylius\Bundle\ShippingBundle\Form\Type\ShippingMethodType`:

It needs to have the images field as a CollectionType.

```

<?php

declare(strict\_types=1);

namespace App\Form\Extension;

use App\Form\Type\ShippingMethodImageType;

use Sylius\Bundle\ShippingBundle\Form\Type\ShippingMethodType;

use Symfony\Component\Form\AbstractTypeExtension;

use Symfony\Component\Form\Extension\Core\Type\CollectionType;

use Symfony\Component\Form\FormBuilderInterface;

final class ShippingMethodTypeExtension extends AbstractTypeExtension

{

    /\*\*

 \* {@inheritdoc}

 \*/

    public function buildForm(FormBuilderInterface $builder, array $options): void

    {

        $builder->add('images', CollectionType::class, [

            'entry\_type' => ShippingMethodImageType::class,

            'allow\_add' => true,

            'allow\_delete' => true,

            'by\_reference' => false,

            'label' => 'sylius.form.shipping\_method.images',

        ]);

    }

    /\*\*

 \* {@inheritdoc}

 \*/

    public function getExtendedType(): string

    {

        return ShippingMethodType::class;

    }

}

```

Tip

In case you need only a single image upload, this can be done in 2 very easy steps.

First, in the code for the form provided above set `allow\_add` and `allow\_delete` to `false`

Second, in the `\_\_construct` method of the `ShippingMethod` entity you defined earlier add the following:

```

public function \_\_construct()

{

    parent::\_\_construct();

    $this->images = new ArrayCollection();

    $this->addImage(new ShippingMethodImage());

}

```

Register the form extension as a service:

```

# services.yml

services:

 app.form.extension.type.shipping\_method:

 class: App\Form\Extension\ShippingMethodTypeExtension

 tags:

 - { name: form.type\_extension, extended\_type: Sylius\Bundle\ShippingBundle\Form\Type\ShippingMethodType }

```

####### 11. Declare the ImagesUploadListener service[¶](#declare-the-imagesuploadlistener-service "Permalink to this headline")

In order to handle the image upload you need to attach the `ImagesUploadListener` to the `ShippingMethod` entity events:

```

# services.yml

services:

 app.listener.images\_upload:

 class: Sylius\Bundle\CoreBundle\EventListener\ImagesUploadListener

 parent: sylius.listener.images\_upload

 autowire: true

 autoconfigure: false

 public: false

 tags:

 - { name: kernel.event\_listener, event: sylius.shipping\_method.pre\_create, method: uploadImages }

 - { name: kernel.event\_listener, event: sylius.shipping\_method.pre\_update, method: uploadImages }

```

####### 12. Render the images field in the form view[¶](#render-the-images-field-in-the-form-view "Permalink to this headline")

In order to achieve that you will need to customize the form view from the `SyliusAdminBundle/views/ShippingMethod/\_form.html.twig` file.

Copy and paste its contents into your own `app/Resources/SyliusAdminBundle/views/ShippingMethod/\_form.html.twig` file,

and render the `{{ form\_row(form.images) }}` field.

```

{# app/Resources/SyliusAdminBundle/views/ShippingMethod/\_form.html.twig #}

{% from '@SyliusAdmin/Macro/translationForm.html.twig' import translationForm %}

<div class="ui two column stackable grid">

 <div class="column">

 <div class="ui segment">

 {{ form\_errors(form) }}

 <div class="three fields">

 {{ form\_row(form.code) }}

 {{ form\_row(form.zone) }}

 {{ form\_row(form.position) }}

 </div>

 {{ form\_row(form.enabled) }}

 <h4 class="ui dividing header">{{ 'sylius.ui.availability'|trans }}</h4>

 {{ form\_row(form.channels) }}

 <h4 class="ui dividing header">{{ 'sylius.ui.category\_requirements'|trans }}</h4>

 {{ form\_row(form.category) }}

 {% for categoryRequirementChoiceForm in form.categoryRequirement %}

 {{ form\_row(categoryRequirementChoiceForm) }}

 {% endfor %}

 <h4 class="ui dividing header">{{ 'sylius.ui.taxes'|trans }}</h4>

 {{ form\_row(form.taxCategory) }}

 <h4 class="ui dividing header">{{ 'sylius.ui.shipping\_charges'|trans }}</h4>

 {{ form\_row(form.calculator) }}

 {% for name, calculatorConfigurationPrototype in form.vars.prototypes %}

 <div id="{{ form.calculator.vars.id }}\_{{ name }}" data-container=".configuration"

 data-prototype="{{ form\_widget(calculatorConfigurationPrototype)|e }}">

 </div>

 {% endfor %}

 {# Here you go! #}

 {{ form\_row(form.images) }}

 <div class="ui segment configuration">

 {% if form.configuration is defined %}

 {% for field in form.configuration %}

 {{ form\_row(field) }}

 {% endfor %}

 {% endif %}

 </div>

 </div>

 </div>

 <div class="column">

 {{ translationForm(form.translations) }}

 </div>

</div>

```

Tip

Learn more about customizing templates [here](index.html#document-customization/template).

####### 13. Validation[¶](#validation "Permalink to this headline")

Your form so far is working fine, but don’t forget about validation.

The easiest way is using validation config files under the `App/Resources/config/validation` folder.

This could look like this e.g.:

```

# src\Resources\config\validation\ShippingMethodImage.yml

App\Entity\ShippingMethodImage:

 properties:

 file:

 - Image:

 groups: [sylius]

 maxHeight: 1000

 maxSize: 10240000

 maxWidth: 1000

 mimeTypes:

 - "image/png"

 - "image/jpg"

 - "image/jpeg"

 - "image/gif"

 mimeTypesMessage: 'This file format is not allowed. Please use PNG, JPG or GIF files.'

 minHeight: 200

 minWidth: 200

```

This defines the validation constraints for each image entity.

Now connecting the validation of the `ShippingMethod` to the validation of each single `Image Entity` is left:

```

# src\Resources\config\validation\ShippingMethod.yml

App\Entity\ShippingMethod:

 properties:

 ...

 images:

 - Valid: ~

```

###### Learn more[¶](#learn-more "Permalink to this headline")

* [GridBundle documentation](https://github.com/Sylius/SyliusGridBundle/blob/master/docs/index.md)

* [ResourceBundle documentation](https://github.com/Sylius/SyliusResourceBundle/blob/master/docs/index.md)

* [Customization Guide](index.html#document-customization/index)

##### How to store images on AWS-S3 automatically?[¶](#how-to-store-images-on-aws-s3-automatically "Permalink to this headline")

###### Instructions:[¶](#instructions "Permalink to this headline")

First you need to ensure that the official `AWS-S3 SDK` for PHP is installed:

```

composer require aws/aws-sdk-php league/flysystem-aws-s3-v3

```

####### 1. Configure Flysystem AWS S3 Adapter[¶](#configure-flysystem-aws-s3-adapter "Permalink to this headline")

Place this file under `config/packages/flysystem.yaml`:

```

# config/packages/flysystem.yaml

flysystem:

 storages:

 sylius.storage:

 adapter: 'aws'

 options:

 client: Aws\S3\S3Client

 bucket: "%aws.s3.bucket%"

 prefix: "media/image"

 streamReads: true

```

For more details see:

* <https://flysystem.thephpleague.com/docs/adapter/aws-s3-v3/>

* <https://github.com/thephpleague/flysystem-bundle/blob/3.x/docs/2-cloud-storage-providers.md#aws-sdk-s3>

####### 2. Configure Liip-Imagine:[¶](#configure-liip-imagine "Permalink to this headline")

Add this file under `config/packages/liip\_imagine.yaml` in order to make Liip-Imagine aware of AWS S3 storage:

```

# config/packages/liip\_imagine.yaml

liip\_imagine:

 loaders:

 aws\_s3:

 flysystem:

 filesystem\_service: sylius.storage

 resolvers:

 aws\_s3:

 aws\_s3:

 client\_config:

 credentials:

 key: "%aws.s3.key%"

 secret: "%aws.s3.secret%"

 region: "%aws.s3.region%"

 version: "%aws.s3.version%"

 bucket: "%aws.s3.bucket%"

 get\_options:

 Scheme: https

 put\_options:

 CacheControl: "max-age=86400"

 cache\_prefix: media/cache

 data\_loader: aws\_s3

 cache: aws\_s3

```

####### 3. Define S3-related parameters and configure final services:[¶](#define-s3-related-parameters-and-configure-final-services "Permalink to this headline")

In `config/services.yaml`:

```

# config/services.yaml

parameters:

 aws.s3.key: "%env(AWS\_S3\_KEY)%"

 aws.s3.secret: "%env(AWS\_S3\_SECRET)%"

 aws.s3.bucket: "%env(AWS\_S3\_BUCKET)%"

 aws.s3.region: "%env(AWS\_S3\_REGION)%"

 aws.s3.version: "%env(AWS\_S3\_VERSION)%"

services:

 # composer require aws/aws-sdk-php

 Aws\S3\S3Client:

 factory: [Aws\S3\S3Client, 'factory']

 arguments:

 -

 version: "%aws.s3.version%"

 region: "%aws.s3.region%"

 credentials:

 key: "%aws.s3.key%"

 secret: "%aws.s3.secret%"

```

* [How to resize images?](index.html#document-cookbook/images/images)

* [How to add an image to an entity? (One-To-One association)](index.html#document-cookbook/images/image-on-entity)

* [How to add images to an entity?](index.html#document-cookbook/images/images-on-entity)

* [How to store images on AWS-S3 automatically?](index.html#document-cookbook/images/images-on-aws-s3)

#### Deployment[¶](#deployment "Permalink to this headline")

##### How to deploy Sylius to SymfonyCloud?[¶](#how-to-deploy-sylius-to-symfonycloud "Permalink to this headline")

Tip

Start with reading [SymfonyCloud documentation](https://symfony.com/cloud/doc).

The process of deploying Sylius to SymfonyCloud is eased by the tools provided by SymfonyCloud for Symfony projects.

In this guide you will find sufficient instructions to have your application up and running on SymfonyCloud.

###### 1. Create a new Sylius application[¶](#create-a-new-sylius-application "Permalink to this headline")

This whole section can be skipped if you already have a working project.

To begin creating your new project, run this command:

```

$ composer create-project sylius/sylius-standard acme

```

This will create a new Symfony project in the `acme` directory. Next, move to the project directory, initialize the

git repository and create your first commit:

```

$ cd acme

$ git init

$ git add .

$ git commit -m "Initial commit"

```

###### 2. Install the SymfonyCloud client[¶](#install-the-symfonycloud-client "Permalink to this headline")

* Download the CLI tool from [symfony.com](https://symfony.com/download/)

* Login using `symfony login`

###### 3. Make the store ready to deploy[¶](#make-the-store-ready-to-deploy "Permalink to this headline")

* Initialize a default configuration for SymfonyCloud:

```

$ symfony project:init

```

* Customize the `.symfony/services.yaml` file:

```

db:

 type: mysql:10.2

 disk: 1024

```

* Customize the `.symfony.cloud.yaml` file:

```

name: app

type: php:7.3

build:

 flavor: none

runtime:

 extensions: []

relationships:

 database: "db:mysql"

web:

 locations:

 "/":

 root: "public"

 expires: -1

 passthru: "/index.php"

 "/assets/shop":

 expires: 2w

 passthru: false

 allow: false

 rules:

 # Only allow static files from the assets directories.

 '\.(css|js|jpe?g|png|gif|svgz?|ico|bmp|tiff?|wbmp|ico|jng|bmp|html|pdf|otf|woff2|woff|eot|ttf|jar|swf|ogx|avi|wmv|asf|asx|mng|flv|webm|mov|ogv|mpe|mpe?g|mp4|3gpp|weba|ra|m4a|mp3|mp2|mpe?ga|midi?)$':

 allow: true

 "/media/image":

 expires: 2w

 passthru: false

 allow: false

 rules:

 # Only allow static files from the assets directories.

 '\.(jpe?g|png|gif|svgz?)$':

 allow: true

 "/media/cache":

 expires: 2w

 passthru: false

 allow: false

 rules:

 # Only allow static files from the assets directories.

 '\.(jpe?g|png|gif|svgz?)$':

 allow: true

 "/media/cache/resolve":

 passthru: "/index.php"

 scripts: false

 expires: -1

 allow: true

disk: 1024

mounts:

 "/var": { source: local, source\_path: var }

 "/public/uploads": { source: local, source\_path: uploads }

 "/public/media": { source: local, source\_path: media }

hooks:

 build: |

 set -x -e

 curl -s https://get.symfony.com/cloud/configurator | (>&2 bash)

 (>&2 symfony-build)

 (>&2 symfony console sylius:install:check-requirements)

 (>&2

 # Setup everything to use the Node installation

 unset NPM\_CONFIG\_PREFIX

 export NVM\_DIR=${SYMFONY\_APP\_DIR}/.nvm

 set +x && . "${NVM\_DIR}/nvm.sh" use --lts && set -x

 # Starting from here, everything is setup to use the same Node

 yarn build:prod

 )

 deploy: |

 set -x -e

 mkdir -p public/media/image var/log

 (>&2 symfony-deploy)

```

###### 4. Commit the configuration[¶](#commit-the-configuration "Permalink to this headline")

```

$ git add php.ini .symfony .symfony.cloud.yaml && git commit -m "SymfonyCloud configuration"

```

###### 5. Deploy the store to SymfonyCloud[¶](#deploy-the-store-to-symfonycloud "Permalink to this headline")

The first deploy will take care of creating a new SymfonyCloud project for you.

```

$ symfony deploy

```

The output of this command shows you on which URL your online store can be accessed. Alternatively, you can also use

`symfony open:remote` to open your store in your browser.

Hint

**SymfonyCloud** offers a 7 days trial, which you can use for testing your store deployment.

###### 6. Finish Sylius installation[¶](#finish-sylius-installation "Permalink to this headline")

Finish the Sylius installation by running:

```

$ symfony ssh bin/console sylius:install

```

Tip

You can load the predefined set of Sylius fixtures to try your new store:

```

```

$ symfony ssh bin/console sylius:fixtures:load

###### 7. Dive deeper[¶](#dive-deeper "Permalink to this headline")

####### Add default Sylius cronjobs:[¶](#add-default-sylius-cronjobs "Permalink to this headline")

Add the example below to your `.symfony.cloud.yaml` file. This runs these cronjobs every 6 hours.

```

crons:

 cleanup\_cart:

 spec: '0 \*/6 \* \* \*'

 cmd: croncape /usr/bin/flock -n /tmp/lock.app.cleanup\_cart symfony console sylius:remove-expired-carts --verbose

 cleanup\_order:

 spec: '0 \*/6 \* \* \*'

 cmd: croncape /usr/bin/flock -n /tmp/lock.app.cleanup\_order symfony console sylius:cancel-unpaid-orders --verbose

```

####### Additional tips:[¶](#additional-tips "Permalink to this headline")

* SymfonyCloud can serve gzipped versions of your static assets. Make sure to save your assets in the same folder, but with a .gz suffix.

###### Learn more[¶](#learn-more "Permalink to this headline")

* SymfonyCloud documentation: [Getting started](https://symfony.com/doc/master/cloud/getting-started.html)

* SymfonyCloud documentation: [Moving to production](https://symfony.com/doc/master/cloud/cookbooks/go_live.html)

* [Installation Guide](index.html#document-book/installation/installation)

##### How to deploy Sylius to Platform.sh?[¶](#how-to-deploy-sylius-to-platform-sh "Permalink to this headline")

Tip

Start with reading [Platform.sh documentation](https://docs.platform.sh/frameworks/symfony.html).

Also Symfony provides [a guide on deploying projects to Platform.sh](https://symfony.com/doc/current/deployment/platformsh.html).

The process of deploying Sylius to Platform.sh is based on the guidelines prepared for Symfony projects in general.

In this guide you will find sufficient instructions to have your application up and running on Platform.sh.

###### 1. Prepare a Platform.sh project[¶](#prepare-a-platform-sh-project "Permalink to this headline")

* Create an account on [Platform.sh](https://platform.sh/).

* Create a new project, name it (**MyFirstShop** for example) and select the **Blank project** template.

Hint

**Platform.sh** offers a trial month, which you can use for testing your store deployment. If you would be asked to

provide your credit card data nevertheless, use [this link](https://accounts.platform.sh/platform/trial/general/setup)

to create your new project.

[![_images/platform-sh-project.png](_images/platform-sh-project.png)](_images/platform-sh-project.png)

  

* Install the Symfony-Platform.sh bridge in your application with `composer require platformsh/symfonyflex-bridge`.

###### 2. Make the application ready to deploy[¶](#make-the-application-ready-to-deploy "Permalink to this headline")

* Create the `.platform/routes.yaml` file, which describes how an incoming URL is going to be processed by the server.

```

"https://{default}/":

 type: upstream

 upstream: "app:http"

"https://www.{default}/":

 type: redirect

 to: "https://{default}/"

```

* Create the `.platform/services.yaml` file.

```

db:

 type: mysql:10.2

 disk: 2048

```

* Create the `.platform.app.yaml` file, which is the main server application configuration file (and the longest one 😉).

```

name: app

type: php:7.3

build:

 flavor: composer

variables:

 env:

 # Tell Symfony to always install in production-mode.

 APP\_ENV: 'prod'

 APP\_DEBUG: 0

# The hooks that will be performed when the package is deployed.

hooks:

 build: |

 set -e

 yarn install

 yarn build:prod

 deploy: |

 set -e

 rm -rf var/cache/\*

 mkdir -p public/media/image

 bin/console sylius:install -n

 bin/console sylius:fixtures:load -n

 bin/console assets:install --symlink --relative public

 bin/console cache:clear

# The relationships of the application with services or other applications.

# The left-hand side is the name of the relationship as it will be exposed

# to the application in the PLATFORM\_RELATIONSHIPS variable. The right-hand

# side is in the form `<service name>:<endpoint name>`.

relationships:

 # NOTE: this will install mariadb because platform.sh uses it instead of mysql.

 database: "db:mysql"

dependencies:

 nodejs:

 yarn: "\*"

 php:

 composer/composer: '^2'

# The size of the persistent disk of the application (in MB).

disk: 2048

# The mounts that will be performed when the package is deployed.

mounts:

 "/var/cache": "shared:files/cache"

 "/var/log": "shared:files/log"

 "/var/sessions": "shared:files/sessions"

 "/public/uploads": "shared:files/uploads"

 "/public/media": "shared:files/media"

# The configuration of app when it is exposed to the web.

web:

 locations:

 "/":

 # The public directory of the app, relative to its root.

 root: "public"

 # The front-controller script to send non-static requests to.

 passthru: "/index.php"

 allow: true

 expires: -1

 scripts: true

 '/assets/shop':

 expires: 2w

 passthru: true

 allow: false

 rules:

 # Only allow static files from the assets directories.

 '\.(css|js|jpe?g|png|gif|svgz?|ico|bmp|tiff?|wbmp|ico|jng|bmp|html|pdf|otf|woff2|woff|eot|ttf|jar|swf|ogx|avi|wmv|asf|asx|mng|flv|webm|mov|ogv|mpe|mpe?g|mp4|3gpp|weba|ra|m4a|mp3|mp2|mpe?ga|midi?)$':

 allow: true

 '/media/image':

 expires: 2w

 passthru: true

 allow: false

 rules:

 # Only allow static files from the assets directories.

 '\.(jpe?g|png|gif|svgz?)$':

 allow: true

 '/media/cache/resolve':

 passthru: "/index.php"

 expires: -1

 allow: true

 scripts: true

 '/media/cache':

 expires: 2w

 passthru: true

 allow: false

 rules:

 # Only allow static files from the assets directories.

 '\.(jpe?g|png|gif|svgz?)$':

 allow: true

runtime:

 extensions:

 - sodium

```

Warning

It is important to place the newly created file after importing regular parameters.yml file. Otherwise your database

connection will not work. Also this will be the file where you should set your required parameters. Its value will

be fetched from environmental variables.

The application secret is used in several places in Sylius and Symfony. Platform.sh allows you to deploy an environment

for each branch you have, and therefore it makes sense to have a secret automatically generated by the Platform.sh system.

The last 3 lines in the sample above will use the Platform.sh-provided random value as the application secret.

###### 3. Add Platform.sh as a remote to your repository[¶](#add-platform-sh-as-a-remote-to-your-repository "Permalink to this headline")

Use the below command to add your Platform.sh project as the `platform` remote:

```

git remote add platform [PROJECT-ID]@git.[CLUSTER].platform.sh:[PROJECT-ID].git

```

The `PROJECT-ID` is the unique identifier of your project,

and `CLUSTER` can be `eu` or `us` - depending on where are you deploying your project.

###### 4. Commit the configuration[¶](#commit-the-configuration "Permalink to this headline")

```

git add . && git commit -m "Platform.sh configuration"

```

###### 5. Push your project to the Platform.sh remote repository[¶](#push-your-project-to-the-platform-sh-remote-repository "Permalink to this headline")

```

git push platform master

```

The output of this command shows you on which URL your online store can be accessed.

###### 6. Connect to the project via SSH and install Sylius[¶](#connect-to-the-project-via-ssh-and-install-sylius "Permalink to this headline")

The SSH command can be found in your project data on Platform.sh. Alternatively use the

[Platform CLI tool](https://docs.platform.sh/gettingstarted/cli.html).

When you get connected please run:

```

php bin/console sylius:install --env prod

```

Warning

By default platform.sh creates only one instance of the database with the `main` name.

Platform.sh works with the concept of an environment per branch if activated. The idea is to mimic production settings per each branch.

###### How to deploy Sylius Plus to Platform.sh?[¶](#how-to-deploy-sylius-plus-to-platform-sh "Permalink to this headline")

[Sylius Plus](https://sylius.com/plus/) is installed to Sylius like a plugin, but it needs some changes to the Platform.sh configuration presented above to deploy it properly.

First of all, make sure you have your project configured following the [Sylius Plus installation guide](/book/installation/sylius_plus_installation).

After that, you should modify your `.platform.app.yaml`. Configuration from step 2 should be extended by the following lines.

```

# ...

hooks:

 build: |

 set -e

 yarn install --ignore-engines # without this flag you will get error related with node version conflict

 yarn build:prod

 wkhtmltopdf -V # Sylius Plus is installed with InvoicingPlugin, so we need wkhtmltopdf to generate PDF

 deploy: |

 set -e

 rm -rf var/cache/\*

 mkdir -p public/media/image

 bin/console sylius:install -n

 bin/console sylius:fixtures:load plus -n # Updating fixtures with new Sylius Plus features

 bin/console assets:install --symlink --relative public

 bin/console cache:clear

dependencies:

 nodejs:

 yarn: "\*"

 ruby:

 "wkhtmltopdf-binary": "0.12.5.1" # adding wkhtmltopdf as a one of dependencies

# ...

```

In order to use the wkhtmltopdf (needed for Invoicing and Refunds) on server properly, you also need to add it to the `config\packages'knp\_snappy.yaml`:

```

knp\_snappy:

pdf:

 enabled: true

 binary: wkhtmltopdf # for local purpose was '%env(WKHTMLTOPDF\_PATH)%'

 options: []

image:

 enabled: true

 binary: wkhtmltoimage # for local purpose was '%env(WKHTMLTOIMAGE\_PATH)%'

 options: []

```

Sylius Plus is on Private Packagist, so when you want to download it on server, you need add authentication token before deployment.

You can do it by UI on your project page on platform.sh or if you have platform.sh CLI you can add authentication\_token:

```

platform variable:create --level project --name env:COMPOSER_AUTH \

--json true --visible-runtime false --sensitive true --visible-build true

--value '{"http-basic": {"sylius.repo.packagist.com": {"username": "token", "password": "YOUR\_AUTHENTICATION\_TOKEN"}}}'

```

All the other steps from the Sylius deployment on Platform.sh remain unchanged.

###### 7. Dive deeper[¶](#dive-deeper "Permalink to this headline")

####### Add default Sylius cronjobs:[¶](#add-default-sylius-cronjobs "Permalink to this headline")

Add the example below to your `.platform.app.yaml` file. This runs these cronjobs every 6 hours.

```

crons:

 cleanup\_cart:

 spec: '0 \*/6 \* \* \*'

 cmd: '/usr/bin/flock -n /tmp/lock.app.cleanup\_cart bin/console sylius:remove-expired-carts --env=prod --verbose'

 cleanup\_order:

 spec: '0 \*/6 \* \* \*'

 cmd: '/usr/bin/flock -n /tmp/lock.app.cleanup\_order bin/console sylius:cancel-unpaid-orders --env=prod --verbose'

```

####### Additional tips:[¶](#additional-tips "Permalink to this headline")

* Platform.sh can serve gzipped versions of your static assets. Make sure to save your assets in the same folder, but with a .gz suffix.

* Platform.sh comes with a [New Relic integration](https://docs.platform.sh/administration/integrations/new-relic.html).

* Platform.sh comes with a [Blackfire.io integration](https://docs.platform.sh/administration/integrations/blackfire.html)

###### Learn more[¶](#learn-more "Permalink to this headline")

* Platform.sh documentation: [Configuring Symfony projects for Platform.sh](https://docs.platform.sh/frameworks/symfony.html)

* Symfony documentation: [Deploying Symfony to Platform.sh](https://symfony.com/doc/current/deployment/platformsh.html)

* [Installation Guide](index.html#document-book/installation/installation)

##### How to deploy Sylius to Cloudways PHP Hosting?[¶](#how-to-deploy-sylius-to-cloudways-php-hosting "Permalink to this headline")

Cloudways is a managed hosting platform for custom PHP apps and PHP frameworks such as Symfony, Laravel, Codeigniter, Yii,

CakePHP and many more. You can launch the servers on any of the five providers including DigitalOcean, Vultr, AWS, GCE and KYUP containers.

The deployment process of Sylius on Cloudways is pretty much straightforward and easy.

Now to install Sylius you need to go through series of few steps:

###### 1. Launch Server with Custom PHP App[¶](#launch-server-with-custom-php-app "Permalink to this headline")

You should [signup at Cloudways](https://platform.cloudways.com/signup) to buy the PHP servers from the above mentioned providers. Simply go to the pricing page and choose your required plan.

You then need to go through the verification process. Once it done login to platform and launch your first Custom PHP application. You can follow the Gif too.

![_images/cloudways-php-server.gif](_images/cloudways-php-server.gif)

Now let’s start the process of installing Sylius on Cloudways.

###### 2. Install the latest version of Sylius via SSH[¶](#install-the-latest-version-of-sylius-via-ssh "Permalink to this headline")

Open the SSH terminal from the **Server Management tab**. You can also use PuTTY for this purpose. Find the SSH credentials under

the **Master Credentials** heading and login to the SSH terminal:

![_images/cloudways-ssh.png](_images/cloudways-ssh.png)

After the login, move to the application folder using the `cd` command and run the following command to start installing Sylius:

```

composer create-project sylius/sylius-standard myshop

```

The command will start installing the long list of dependencies for Sylius. Once the installation finishes, Sylius will ask for the database credentials.

You can find the database username and password in the Application Access Details.

![_images/cloudways-database.png](_images/cloudways-database.png)

Enter the database details in the SSH terminal:

![_images/cloudways-sylius-database.png](_images/cloudways-sylius-database.png)

Keep the rest of the values to default so that the config file will have the defaults Sylius settings.

If the need arises, you can obviously change these settings later.

###### 3. Install Node Dependencies[¶](#install-node-dependencies "Permalink to this headline")

Sylius requires several Node packages, which also needs to be installed and updated before setting up the shop. In addition, I also need to start and setup Webpack.

Now move to the myshop folder by using `cd myshop` and run the following command `yarn install`. Once the command finishes, run the next command, `yarn build`.

###### 4. Install Sylius for the production environment[¶](#install-sylius-for-the-production-environment "Permalink to this headline")

Now run the following command:

```

bin/console sylius:install -e prod

```

###### 5. Update The Webroot of the Application[¶](#update-the-webroot-of-the-application "Permalink to this headline")

Finally, the last step is to update the webroot of the application in the Platform. Move to the **Application Settings** tab and update it.

![_images/cloudways-sylius-webroot.png](_images/cloudways-sylius-webroot.png)

Now open the application URL as shown in the Access Details tab.

###### Learn more[¶](#learn-more "Permalink to this headline")

* Cloudways PHP Hosting documentation: [How to host PHP applications on DigitalOcean via Cloudways](https://cloudways.com/blog/host-php-on-digitalocean)

* PHP FAQs And Features: [Know more about PHP Hosting](https://cloudways.com/en/php-cloud-hosting.php)

* [What You As A User Can Do With Cloudways PHP Stack](https://cloudways.com/blog/php-stack-user-guide)

##### How to prepare simple CRON jobs?[¶](#how-to-prepare-simple-cron-jobs "Permalink to this headline")

###### What are CRON jobs?[¶](#what-are-cron-jobs "Permalink to this headline")

This is what we call scheduling repetitive task on the server. In web applications this will be mainly

repetitively running specific commands.

###### CRON jobs in Sylius[¶](#cron-jobs-in-sylius "Permalink to this headline")

Sylius has two vital, predefined commands designed to be run as cron jobs on your server.

* `sylius:remove-expired-carts` - to remove carts that have expired after desired time

* `sylius:cancel-unpaid-orders` - to cancel orders that are still unpaid after desired time

The recommended configuration

```

0 */6 * * * sh php bin/console sylius:remove-expired-carts

0 */6 * * * sh php bin/console sylius:cancel-unpaid-orders

```

###### How to configure a CRON job ?[¶](#how-to-configure-a-cron-job "Permalink to this headline")

Tip

Learn more here: [Cron and Crontab usage and examples](https://www.pantz.org/software/cron/croninfo.html).

##### How to deploy Sylius to Artifakt.com[¶](#how-to-deploy-sylius-to-artifakt-com "Permalink to this headline")

###### 1. What is Artifakt?[¶](#what-is-artifakt "Permalink to this headline")

[Artifakt](https://www.artifakt.com/) is an all-in-one platform helping enterprises and developers fast-track deployments,

manage and run complex web applications on enterprise-grade cloud infrastructures on a global scale.

###### 2. How does Artifakt work?[¶](#how-does-artifakt-work "Permalink to this headline")

Artifakt provides a service based on Cloud technologies to help build, deploy, monitor, secure, and manage the scalability of your applications.

###### 3. Create an Artifakt project[¶](#create-an-artifakt-project "Permalink to this headline")

To get off to a good start with Artifakt, you first need to create a project.

Click Create Project in the left menu and follow the steps to create your first project.

The project code must be unique within the workspace, you can refer to projects for more information.

[![_images/create_project.png](_images/create_project.png)](_images/create_project.png)

In the second step, select the Sylius runtime and version that corresponds to the application you want to deploy.

Also, choose the [region](https://docs.artifakt.com/platform/available-regions) where your application and data will be deployed.

When you create a project, you can use the default Artifakt code base for the chosen Sylius runtime, or you can link your own Git repository.

If you wish to use the Artifakt default code base, simply click “Skip, and Create Project”. Otherwise, you can choose the location of your repository and choose your preferred repository and default branch.

If you did not link your Git repository during the project creation, you can do so afterwards by going to Project → Settings → Environments and clicking Link to a different repository so that you can then create environments using your application’s source code.

[![_images/github_settings.png](_images/github_settings.png)](_images/github_settings.png)

Hint

If you don’t have an account on **Artifakt.com**, you should request a demo or contact support first.

###### 4. Create an environment[¶](#create-an-environment "Permalink to this headline")

Once the project is created, you can create your first environment. To do so, go to your project and click on **New Environment**.

When creating an environment, you must define a name, the Git branch to deploy, the [type of platform](https://docs.artifakt.com/platform/platform-management) to use, and the [criticality](https://docs.artifakt.com/platform/environment-lifecycle#environnement-et-criticite) (environments can be either critical or noncritical).

[![_images/create_environment.png](_images/create_environment.png)](_images/create_environment.png)

If you set up your Git repository at the project level, the branches associated with your repository are automatically retrieved and available in the drop-down list.

###### 5. Building the environment[¶](#building-the-environment "Permalink to this headline")

Once your environment has been created, you can now launch its construction by clicking on **Build** in the drop-down menu.

You will then be redirected to the Overview page of the environment, from where you can follow the creation and start up of each service of the platform, in real time.

[![_images/build.png](_images/build.png)](_images/build.png)

Once the environment is deployed and available online, a domain name generated by Artifakt will be displayed in the list of project environments and on the environment home page.

[![_images/domain_name.png](_images/domain_name.png)](_images/domain_name.png)

An Artifakt page will indicate that your environment’s infrastructure has been created. You will then have to deploy your application source code when you are ready.

[![_images/deploy.png](_images/deploy.png)](_images/deploy.png)

Bravo! 🥳 💯 Your application is online thanks to Artifakt!

##### How to deploy Sylius with Docker?[¶](#how-to-deploy-sylius-with-docker "Permalink to this headline")

The simplest way to deploy your Sylius store with Docker is to use the template provided in the Sylius-Standard `docker-compose.prod.yml` configuration file.

Tip

When using a Virtual Private Server (VPS) we recommend having at least 2GB of RAM memory.

###### 1. Install Docker on your VPS[¶](#install-docker-on-your-vps "Permalink to this headline")

```

curl -fsSL https://get.docker.com -o get-docker.sh

sudo sh get-docker.sh

```

###### 2. Execute Docker Compose Configuration[¶](#execute-docker-compose-configuration "Permalink to this headline")

```

export MYSQL\_PASSWORD=SLyPJLaye7

docker compose -f docker-compose.prod.yml up -d

```

Tip

Deploying the database on the same machine as the application is not the best practice. **Use Managed Database solution instead.**

###### Learn more[¶](#learn-more "Permalink to this headline")

* [Check out Docker learning recommendations!](https://docs.docker.com/get-started/resources/#self-paced-online-learning)

* [How to deploy Sylius to Platform.sh?](index.html#document-cookbook/deployment/platform-sh)

* [How to deploy Sylius to Cloudways PHP Hosting?](index.html#document-cookbook/deployment/cloudways)

* [How to prepare simple CRON jobs?](index.html#document-cookbook/deployment/cron-jobs)

* [How to deploy Sylius to SymfonyCloud?](index.html#document-cookbook/deployment/symfonycloud)

* [How to deploy Sylius to Artifakt.com](index.html#document-cookbook/deployment/artifakt)

* [How to deploy Sylius with Docker?](index.html#document-cookbook/deployment/docker)

#### Configuration[¶](#configuration "Permalink to this headline")

##### How to disable default shop, admin or API of Sylius?[¶](#how-to-disable-default-shop-admin-or-api-of-sylius "Permalink to this headline")

When you are using Sylius as a whole you may be needing to remove some of its parts. It is possible to remove

for example Sylius shop to have only administration panel and API. Or the other way, remove API if you do not need it.

Therefore you have this guide that will help you when wanting to disable shop, admin or API of Sylius.

###### How to disable Sylius shop?[¶](#how-to-disable-sylius-shop "Permalink to this headline")

**1.** Remove SyliusShopBundle from `config/bundles.php`.

```

// # config/bundles.php

return [

    //...

    // Sylius\Bundle\ShopBundle\SyliusShopBundle::class => ['all' => true], // - remove or leave this line commented

    //...

];

```

**2.** Remove SyliusShopBundle’s configs from `config/packages/\_sylius.yaml`.

Here you’ve got the lines that should disappear from this file:

```

imports:

# - { resource: "@SyliusShopBundle/Resources/config/app/config.yml" } # remove or leave this line commented

#...

#sylius\_shop:

# product\_grid:

# include\_all\_descendants: true

```

**3.** Remove SyliusShopBundle routing configuration file `config/routes/sylius\_shop.yaml`.

**4.** Remove security configuration from `config/packages/security.yaml`.

The part that has to be removed from this file is shown below:

```

parameters:

 # sylius.security.shop\_regex: "^/(?!admin|api/.\*|api$)[^/]++"

security:

 firewalls:

# Delete or leave this part commented

# shop:

# switch\_user: { role: ROLE\_ALLOWED\_TO\_SWITCH }

# context: shop

# pattern: "%sylius.security.shop\_regex%"

# form\_login:

# success\_handler: sylius.authentication.success\_handler

# failure\_handler: sylius.authentication.failure\_handler

# provider: sylius\_shop\_user\_provider

# login\_path: sylius\_shop\_login

# check\_path: sylius\_shop\_login\_check

# failure\_path: sylius\_shop\_login

# default\_target\_path: sylius\_shop\_homepage

# use\_forward: false

# use\_referer: true

# csrf\_token\_generator: security.csrf.token\_manager

# csrf\_parameter: \_csrf\_shop\_security\_token

# csrf\_token\_id: shop\_authenticate

# remember\_me:

# secret: "%secret%"

# name: APP\_SHOP\_REMEMBER\_ME

# lifetime: 31536000

# remember\_me\_parameter: \_remember\_me

# logout:

# path: sylius\_shop\_logout

# target: sylius\_shop\_login

# invalidate\_session: false

# success\_handler: sylius.handler.shop\_user\_logout

# anonymous: true

access\_control:

# - { path: "%sylius.security.shop\_regex%/\_partial", role: IS\_AUTHENTICATED\_ANONYMOUSLY, ips: [127.0.0.1, ::1] }

# - { path: "%sylius.security.shop\_regex%/\_partial", role: ROLE\_NO\_ACCESS }

# - { path: "%sylius.security.shop\_regex%/login", role: IS\_AUTHENTICATED\_ANONYMOUSLY }

# - { path: "%sylius.security.shop\_regex%/register", role: IS\_AUTHENTICATED\_ANONYMOUSLY }

# - { path: "%sylius.security.shop\_regex%/verify", role: IS\_AUTHENTICATED\_ANONYMOUSLY }

# - { path: "%sylius.security.shop\_regex%/account", role: ROLE\_USER }

# - { path: "%sylius.security.shop\_regex%/seller/register", role: ROLE\_USER }

```

**Done!** There is no shop in Sylius now, just admin and API.

###### How to disable Sylius Admin?[¶](#how-to-disable-sylius-admin "Permalink to this headline")

**1.** Remove SyliusAdminBundle from `config/bundles.php`.

```

// # config/bundles.php

return [

    //...

    // Sylius\Bundle\AdminBundle\SyliusAdminBundle::class => ['all' => true], // - remove or leave this line commented

    //...

];

```

**2.** Remove SyliusAdminBundle’s config import from `config/packages/\_sylius.yaml`.

Here you’ve got the line that should disappear from imports:

```

imports:

# - { resource: "@SyliusAdminBundle/Resources/config/app/config.yml" } # remove or leave this line commented

```

**3.** Remove SyliusAdminBundle routing configuration from `config/routes/sylius\_admin.yaml`.

**4.** Remove security configuration from `config/packages/security.yaml`.

The part that has to be removed from this file is shown below:

```

parameters:

# Delete or leave this part commented

# sylius.security.admin\_regex: "^/admin"

 sylius.security.shop\_regex: "^/(?!api/.\*|api$)[^/]++" # Remove `admin|` from the pattern

security:

 firewalls:

# Delete or leave this part commented

# admin:

# switch\_user: true

# context: admin

# pattern: "%sylius.security.admin\_regex%"

# form\_login:

# provider: sylius\_admin\_user\_provider

# login\_path: sylius\_admin\_login

# check\_path: sylius\_admin\_login\_check

# failure\_path: sylius\_admin\_login

# default\_target\_path: sylius\_admin\_dashboard

# use\_forward: false

# use\_referer: true

# csrf\_token\_generator: security.csrf.token\_manager

# csrf\_parameter: \_csrf\_admin\_security\_token

# csrf\_token\_id: admin\_authenticate

# remember\_me:

# secret: "%secret%"

# path: /admin

# name: APP\_ADMIN\_REMEMBER\_ME

# lifetime: 31536000

# remember\_me\_parameter: \_remember\_me

# logout:

# path: sylius\_admin\_logout

# target: sylius\_admin\_login

# anonymous: true

access\_control:

# Delete or leave this part commented

# - { path: "%sylius.security.admin\_regex%/\_partial", role: IS\_AUTHENTICATED\_ANONYMOUSLY, ips: [127.0.0.1, ::1] }

# - { path: "%sylius.security.admin\_regex%/\_partial", role: ROLE\_NO\_ACCESS }

# - { path: "%sylius.security.admin\_regex%/login", role: IS\_AUTHENTICATED\_ANONYMOUSLY }

# - { path: "%sylius.security.admin\_regex%", role: ROLE\_ADMINISTRATION\_ACCESS }

```

**Done!** There is no admin in Sylius now, just api and shop.

###### How to disable Sylius API?[¶](#how-to-disable-sylius-api "Permalink to this headline")

**1.** Remove SyliusAdminApiBundle & FOSOAuthServerBundle from `config/bundles.php`.

```

// # config/bundles.php

return [

    //...

    // FOS\OAuthServerBundle\FOSOAuthServerBundle::class => ['all' => true],

    // Sylius\Bundle\AdminApiBundle\SyliusAdminApiBundle::class => ['all' => true], // - remove or leave this line commented

    //...

];

```

**2.** Remove SyliusAdminApiBundle’s config import from `config/packages/\_sylius.yaml`.

Here you’ve got the line that should disappear from imports:

```

imports:

# - { resource: "@SyliusAdminApiBundle/Resources/config/app/config.yml" } # remove or leave this line commented

```

**3.** Remove SyliusAdminApiBundle routing configuration from `config/routes/sylius\_admin\_api.yaml`.

**4.** Remove security configuration from `config/packages/security.yaml`.

The part that has to be removed from this file is shown below:

```

parameters:

# Delete or leave this part commented

# sylius.security.api\_regex: "^/api"

 sylius.security.shop\_regex: "^/(?!admin$)[^/]++" # Remove `|api/.\*|api` from the pattern

security:

 firewalls:

# Delete or leave this part commented

# oauth\_token:

# pattern: "%sylius.security.api\_regex%/oauth/v2/token"

# security: false

# api:

# pattern: "%sylius.security.api\_regex%/.\*"

# fos\_oauth: true

# stateless: true

# anonymous: true

access\_control:

# Delete or leave this part commented

# - { path: "%sylius.security.api\_regex%/login", role: IS\_AUTHENTICATED\_ANONYMOUSLY }

# - { path: "%sylius.security.api\_regex%/.\*", role: ROLE\_API\_ACCESS }

```

**5.** Remove fos\_rest config from `config/packages/fos\_rest.yaml`.

```

fos\_rest:

 format\_listener:

 rules:

 # - { path: '^/api', priorities: ['json', 'xml'], fallback\_format: json, prefer\_extension: true } # remove or leave this line commented

```

**Done!** There is no API in Sylius now, just admin and shop.

###### Learn more[¶](#learn-more "Permalink to this headline")

* [Architecture: Division into Core, Shop, Admin and API](index.html#division-into-core-shop-admin-api)

##### How to use installer commands?[¶](#how-to-use-installer-commands "Permalink to this headline")

Sylius platform ships with the `sylius:install` command, which takes care of creating the database, schema, dumping the assets and basic store configuration.

This command actually uses several other commands behind the scenes and each of those is available for you:

###### Checking system requirements[¶](#checking-system-requirements "Permalink to this headline")

You can quickly check all your system requirements and possible recommendations by calling the following command:

```

php bin/console sylius:install:check-requirements

```

###### Database configuration[¶](#database-configuration "Permalink to this headline")

Sylius can create or even reset the database/schema for you, simply call:

```

php bin/console sylius:install:database

```

The command will check if your database schema exists. If yes, you may decide to recreate it from scratch, otherwise Sylius will take care of this automatically.

It also allows you to load sample data.

###### Loading sample data[¶](#loading-sample-data "Permalink to this headline")

You can load sample data by calling the following command:

```

php bin/console sylius:install:sample-data

```

###### Basic store configuration[¶](#basic-store-configuration "Permalink to this headline")

To configure your store, use this command and answer all questions:

```

php bin/console sylius:install:setup

```

###### Installing assets[¶](#installing-assets "Permalink to this headline")

You can reinstall all web assets by simply calling:

```

php bin/console sylius:install:assets

```

##### How to load custom fixtures suite?[¶](#how-to-load-custom-fixtures-suite "Permalink to this headline")

If you have your custom fixtures suite, you can load it during install by providing at fixture-suite parameter:

```

php bin/console sylius:install --fixture-suite=your_custom_fixtures_suite

```

Same option also available at sylius:install:database, sylius:install:sample-data commands.

##### How to disable admin version notifications?[¶](#how-to-disable-admin-version-notifications "Permalink to this headline")

By default Sylius sends checks from the admin whether you are running the latest version. In case you are not

running the latest version, a notification will be shown in the admin panel (top right).

This guide will instruct you how to disable this check & notification.

###### How to disable notifications?[¶](#how-to-disable-notifications "Permalink to this headline")

Add the following configuration to `config/packages/sylius\_admin.yaml`.

```

sylius\_admin:

 notifications:

 enabled: false

```

##### How to customize Admin routes prefix?[¶](#how-to-customize-admin-routes-prefix "Permalink to this headline")

By default, Sylius administration routes are prefixed with `/admin`.

You can use the parameter `sylius\_admin.path\_name` to retrieve the admin routes prefix.

In order to change to administration panel route prefix you need to modify the `SYLIUS\_ADMIN\_ROUTING\_PATH\_NAME` environment variable.

Warning

If you used the `/admin` prefix in some admin URLs in your custom code you need to replace the `/admin`

by `/%sylius\_admin.path\_prefix%`.

* [How to use installer commands?](index.html#document-cookbook/configuration/installation-commands)

* [How to disable default shop, admin or API of Sylius?](index.html#document-cookbook/configuration/disabling-shop-admin-api)

* [How to disable admin version notifications?](index.html#document-cookbook/configuration/disabling-admin-notifications)

* [How to customize Admin routes prefix?](index.html#document-cookbook/configuration/admin-prefix)

#### Frontend[¶](#frontend "Permalink to this headline")

##### How to add Google Analytics script to shop?[¶](#how-to-add-google-analytics-script-to-shop "Permalink to this headline")

All shops tend to observe traffic on their websites, the most popular tool for that is Google Analytics.

In Sylius we have two ways to enable it:

*If you have the Sylius layout overridden completely then:*

* paste the script directly into [head section of the layout](#google-analytics-to-head-section)

or *if you are just customizing the Sylius layout, and you will be updating to future versions then:*

* add the script [via Sonata events](#google-analytics-via-sonata-events)

###### Adding Google Analytics by pasting the script directly into the layout template.[¶](#adding-google-analytics-by-pasting-the-script-directly-into-the-layout-template "Permalink to this headline")

If you want to add Google Analytics by this way, you need to override the `layout.html.twig` in `templates/bundles/SyliusShopBundle/layout.html.twig`.

```

{# templates/bundles/SyliusShopBundle/layout.html.twig #}

{# rest of layout.html.twig code #}

{% block metatags %}

{% endblock %}

{% block google\_script %}

 <!-- Google Analytics -->

 <script>

 (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){

 (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1\*new Date();a=s.createElement(o),

 m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)

 })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

 ga('create', 'UA-XXXXX-Y', 'auto');

 ga('send', 'pageview');

 </script>

 <!-- End Google Analytics -->

{% endblock %}

{% block stylesheets %}

{% endblock %}

{# rest of layout.html.twig code #}

```

###### Adding Google Analytics script with Sonata events.[¶](#adding-google-analytics-script-with-sonata-events "Permalink to this headline")

If you want to add Google Analytics by sonata event you need to add a new file, create the file `googleScript.html.twig` in `/templates/layout.html.twig`.

```

{# templates/googleScript.html.twig#}

<!-- Google Analytics -->

<script>

 (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){

 (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1\*new Date();a=s.createElement(o),

 m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)

 })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

 ga('create', 'UA-XXXXX-Y', 'auto');

 ga('send', 'pageview');

</script>

<!-- End Google Analytics -->

```

Now, we need to configure a new service.

```

# config/packages/\_sylius.yaml

app.block\_event\_listener.layout.after\_stylesheets:

 class: Sylius\Bundle\UiBundle\Block\BlockEventListener

 arguments:

 - 'googleScript.html.twig'

 tags:

 - { name: kernel.event\_listener, event: sonata.block.event.sylius.shop.layout.stylesheets, method: onBlockEvent }

```

###### Learn more[¶](#learn-more "Permalink to this headline")

* [Google Analytics Documentation](https://developers.google.com/analytics/devguides/collection/analyticsjs)

##### How to migrate from Gulp to Webpack (Sylius 1.11 or earlier)[¶](#how-to-migrate-from-gulp-to-webpack-sylius-1-11-or-earlier "Permalink to this headline")

Now we will walk through the process of migrating your project from Gulp to Webpack.

Note

This guide assumes your project is using Gulp as a build tool and you are using Sylius 1.11 or earlier.

If you are using Sylius 1.12 or later check our [How to migrate from Gulp to Webpack (Sylius 1.12 or later)](index.html#document-cookbook/frontend/migrating-to-webpack-1-12-or-later) guide.

Warning

Keep in mind you might have to adjust some steps to your project needs. Every project is different, and we are not able

to provide a universal solution for every case.

**1.** Install Webpack Encore composer package

```

$ composer require symfony/webpack-encore-bundle --no-scripts

```

**2.** Update your dependencies version in `package.json` file to the latest version. You can copy the `package.json` content from

[Sylius/Sylius repository](https://github.com/Sylius/Sylius/blob/1.12/package.json).

**3.** Remove the following files from you project:

* `.babelrc`

* `gulpfile.babel.js`

* `yarn.lock`

* `public/assets`

* `node\_modules`

**4.** Create a `webpack.config.js` file (or if you already have existing one, replace its content) using [webpack.config.js in the Sylius/Sylius-Standard repository](https://github.com/Sylius/Sylius-Standard/blob/1.12/webpack.config.js) as a reference.

**5.** Create assets directory with the following structure:

```

<project_root>/

├── assets/

│   ├── admin/

│   │   ├── entry.js <- this file can be empty for now

│   ├── shop/

│   │   ├── entry.js <- this file can be empty for now

```

**6a.** Create or replace `config/packages/assets.yaml` with the following configuration:

```

framework:

 assets:

 packages:

 admin:

 json\_manifest\_path: '%kernel.project\_dir%/public/build/admin/manifest.json'

 shop:

 json\_manifest\_path: '%kernel.project\_dir%/public/build/shop/manifest.json'

 app.admin:

 json\_manifest\_path: '%kernel.project\_dir%/public/build/app/admin/manifest.json'

 app.shop:

 json\_manifest\_path: '%kernel.project\_dir%/public/build/app/shop/manifest.json'

```

**6b.** Create or replace `config/packages/webpack\_encore.yaml` with the following configuration:

```

webpack\_encore:

 output\_path: '%kernel.project\_dir%/public/build/default'

 builds:

 admin: '%kernel.project\_dir%/public/build/admin'

 shop: '%kernel.project\_dir%/public/build/shop'

 app.admin: '%kernel.project\_dir%/public/build/app/admin'

 app.shop: '%kernel.project\_dir%/public/build/app/shop'

```

**7a.** Create or override `templates/bundles/SyliusAdminBundle/\_scripts.html.twig` template with the following content:

```

{{ encore\_entry\_script\_tags('admin-entry', null, 'admin') }}

{{ encore\_entry\_script\_tags('app-admin-entry', null, 'app.admin') }}

```

**7b.** Create or override `templates/bundles/SyliusAdminBundle/\_styles.html.twig` template with the following content:

```

{{ encore\_entry\_link\_tags('admin-entry', null, 'admin') }}

{{ encore\_entry\_link\_tags('app-admin-entry', null, 'app.admin') }}

```

**7c.** Create or override `templates/bundles/SyliusShopBundle/\_scripts.html.twig` template with the following content:

```

{{ encore\_entry\_script\_tags('shop-entry', null, 'shop') }}

{{ encore\_entry\_script\_tags('app-shop-entry', null, 'app.shop') }}

```

**7d.** Create or override `templates/bundles/SyliusShopBundle/\_styles.html.twig` template with the following content:

```

{{ encore\_entry\_link\_tags('shop-entry', null, 'shop') }}

{{ encore\_entry\_link\_tags('app-shop-entry', null, 'app.shop') }}

```

**7e.** Create or override `templates/bundles/SyliusAdminBundle/Layout/\_logo.html.twig` template with the following content:

```

<a class="item" href="{{ path('sylius\_admin\_dashboard') }}" style="padding: 13px 0;">

 <div style="max-width: 90px; margin: 0 auto;">

 <img src="{{ asset('build/admin/images/admin-logo.svg', 'admin') }}" class="ui fluid image">

 </div>

</a>

```

**7f.** Create or override `templates/bundles/SyliusAdminBundle/Security/\_content.html.twig` template with the following content:

```

{% include '@SyliusUi/Security/\_login.html.twig'

    with {

        'action': path('sylius\_admin\_login\_check'),

        'paths': {'logo': asset('build/admin/images/logo.png', 'admin')}

    }

%}

```

Warning

Files mentioned above are the most common ones that need to be overridden. Keep in mind, across your project you might

have other files using the old paths. You will have to find and adjust them manually.

**8.** Run the following commands:

```

bin/console cache:clear

yarn install

yarn build

```

**9.** If you are using GitHub Actions or any other CI tool, make sure your workflow is using `yarn build` or `yarn build:prod` command.

##### How to migrate from Gulp to Webpack (Sylius 1.12 or later)[¶](#how-to-migrate-from-gulp-to-webpack-sylius-1-12-or-later "Permalink to this headline")

Now we will walk through the process of migrating your project from Gulp to Webpack.

Note

This guide assumes your project is using Gulp as a build tool and you are using Sylius 1.12 or later.

If you are using Sylius 1.11 or earlier check our [How to migrate from Gulp to Webpack (Sylius 1.11 or earlier)](index.html#document-cookbook/frontend/migrating-to-webpack-1-11-or-earlier) guide.

Warning

Keep in mind you might have to adjust some steps to your project needs. Every project is different, and we are not able

to provide a universal solution for every case.

**1.** Update your dependencies version in `package.json` file to the latest version. You can copy the `package.json` content from

[Sylius/Sylius repository](https://github.com/Sylius/Sylius/blob/1.12/package.json).

**2.** Remove the following files from you project:

* `.babelrc`

* `gulpfile.babel.js`

* `yarn.lock`

* `public/assets`

* `node\_modules`

**3.** Create a `webpack.config.js` file (or if you already have existing one, replace its content) using [webpack.config.js in the Sylius/Sylius-Standard repository](https://github.com/Sylius/Sylius-Standard/blob/1.12/webpack.config.js) as a reference.

**4.** Create assets directory with the following structure:

```

<project_root>/

├── assets/

│   ├── admin/

│   │   ├── entry.js <- this file can be empty for now

│   ├── shop/

│   │   ├── entry.js <- this file can be empty for now

```

**5a.** Create or replace `config/packages/assets.yaml` with the following configuration:

```

framework:

 assets:

 packages:

 admin:

 json\_manifest\_path: '%kernel.project\_dir%/public/build/admin/manifest.json'

 shop:

 json\_manifest\_path: '%kernel.project\_dir%/public/build/shop/manifest.json'

 app.admin:

 json\_manifest\_path: '%kernel.project\_dir%/public/build/app/admin/manifest.json'

 app.shop:

 json\_manifest\_path: '%kernel.project\_dir%/public/build/app/shop/manifest.json'

```

**5b.** Create or replace `config/packages/webpack\_encore.yaml` with the following configuration:

```

webpack\_encore:

 output\_path: '%kernel.project\_dir%/public/build/default'

 builds:

 admin: '%kernel.project\_dir%/public/build/admin'

 shop: '%kernel.project\_dir%/public/build/shop'

 app.admin: '%kernel.project\_dir%/public/build/app/admin'

 app.shop: '%kernel.project\_dir%/public/build/app/shop'

```

**6a.** Create or override `templates/bundles/SyliusAdminBundle/\_scripts.html.twig` template with the following content:

```

{{ encore\_entry\_script\_tags('admin-entry', null, 'admin') }}

{{ encore\_entry\_script\_tags('app-admin-entry', null, 'app.admin') }}

```

**6b.** Create or override `templates/bundles/SyliusAdminBundle/\_styles.html.twig` template with the following content:

```

{{ encore\_entry\_link\_tags('admin-entry', null, 'admin') }}

{{ encore\_entry\_link\_tags('app-admin-entry', null, 'app.admin') }}

```

**6c.** Create or override `templates/bundles/SyliusShopBundle/\_scripts.html.twig` template with the following content:

```

{{ encore\_entry\_script\_tags('shop-entry', null, 'shop') }}

{{ encore\_entry\_script\_tags('app-shop-entry', null, 'app.shop') }}

```

**6d.** Create or override `templates/bundles/SyliusShopBundle/\_styles.html.twig` template with the following content:

```

{{ encore\_entry\_link\_tags('shop-entry', null, 'shop') }}

{{ encore\_entry\_link\_tags('app-shop-entry', null, 'app.shop') }}

```

Warning

Files mentioned above are the most common ones that need to be overridden. Keep in mind, across your project you might

have other files using the old paths. You will have to find and adjust them manually.

**7.** Run the following commands:

```

yarn install

yarn build

```

**8.** If you have the following entry in your `config/packages/\_sylius.yaml` file (available from `Sylius 1.12`) remove it:

```

sylius\_ui:

 use\_webpack: false

```

Remove it or change its value to `true`.

**9.** If you are using GitHub Actions or any other CI tool, make sure your workflow is using `yarn build` or `yarn build:prod` command.

##### How to stay with Gulp after Sylius 1.12 update[¶](#how-to-stay-with-gulp-after-sylius-1-12-update "Permalink to this headline")

If you want to stay with Gulp (what we do not recommend), that is the guide made just for you.

Warning

This guide should be used only for **existing projects**. We consider using gulp as deprecated and we do not recommend using it for new projects.

**1.** Update your dependencies version in `package.json` file to the latest version. You can copy the `package.json` content from

[Sylius/Sylius repository](https://github.com/Sylius/Sylius/blob/1.12/package.json).

Note

This step is required even if you do not want to use Webpack. In `Sylius 1.12` we have bumped all of our JS dependencies, what forced use to adjust our Gulp configs to the new versions of libraries.

**2.** Revert changes in `package.json` file scripts section:

```

- "watch": "encore dev --watch",

- "build": "encore dev",

- "build:prod": "encore production",

+"watch": "gulp watch",

+"build": "gulp build",

```

**3.** Update `.babelrc` file:

```

{

  "presets": [

- ["env", {

+ ["@babel/preset-env", {

      "targets": {

        "node": "6"

- },

+ }

- "useBuiltIns": true

    }]

  ],

  "plugins": [

- ["transform-object-rest-spread", {

+ ["@babel/plugin-proposal-object-rest-spread", {

      "useBuiltIns": true

    }]

  ]

}

```

**4.** Disable Webpack in `config/packages/\_sylius.yaml` file:

```

+sylius\_ui:

+ use\_webpack: false

```

* [How to add Google Analytics script to shop?](index.html#document-cookbook/frontend/google-analytics)

* [How to migrate from Gulp to Webpack (Sylius 1.11 or earlier)](index.html#document-cookbook/frontend/migrating-to-webpack-1-11-or-earlier)

* [How to migrate from Gulp to Webpack (Sylius 1.12 or later)](index.html#document-cookbook/frontend/migrating-to-webpack-1-12-or-later)

* [How to stay with Gulp after Sylius 1.12 update](index.html#document-cookbook/frontend/staying-with-gulp)

#### Taxation[¶](#taxation "Permalink to this headline")

##### How to configure tax rates to be based on shipping address?[¶](#how-to-configure-tax-rates-to-be-based-on-shipping-address "Permalink to this headline")

The default configuration of Sylius tax calculation is based on billing address but there are situations where we would

like to use a shipping address to be used in this process. This may be useful to anyone who uses Sylius in European Union

as from 1st July 2021 the new taxation rules will be applied.

Note

You can learn more about new EU taxation rules [here](https://ec.europa.eu/taxation_customs/business/vat/modernising-vat-cross-border-ecommerce_en).

To change the way how the taxes are calculated: by billing or by shipping address, you need to add a parameter to your config:

```

# config/packages/\_sylius.yaml

sylius\_core:

 # resources definitions

 shipping\_address\_based\_taxation: true

```

And with this change, the way how taxes are calculated is now based on shipping address.

* [How to configure tax rates to be based on shipping address?](index.html#document-cookbook/taxation/customize-tax-by-address)

#### API[¶](#api "Permalink to this headline")

##### How to add product variants by options to the cart in Sylius API?[¶](#how-to-add-product-variants-by-options-to-the-cart-in-sylius-api "Permalink to this headline")

In order to add a product variant to the cart in the Sylius API, you need to fill the “add to cart” request’s body with `productCode` and `productVariantCode`

You can find this data in the body of the response which is received after the request below is sent.

```

curl -X GET "https://master.demo.sylius.com/api/v2/shop/products?page=1&itemsPerPage=30" -H "accept: application/ld+json"

```

But the most common case is choosing a product variant by its product options.

So below you can find all requests needed to get the product variant of a product with chosen product options values.

In this example, we will be adding an “S” (in size) and “petite” (in height) “Beige strappy summer dress” to the cart.

Firstly you need to get details of the “Beige strappy summer dress” product by its code:

```

curl -X GET "https://master.demo.sylius.com/api/v2/shop/products/Beige\_strappy\_summer\_dress" -H "accept: application/ld+json"

```

In the response you will see that this product has 2 product options:

```

{

 "options": [

 "/api/v2/shop/product-options/dress\_size",

 "/api/v2/shop/product-options/dress\_height"

 ]

}

```

With this data you can check all the available product option values for each product option:

```

curl -X GET "https://master.demo.sylius.com/api/v2/shop/product-options/dress\_size" -H "accept: application/ld+json"

```

The response with all the available option values would contain:

```

{

 "values": [

 "/api/v2/shop/product-option-values/dress\_s",

 "/api/v2/shop/product-option-values/dress\_m",

 "/api/v2/shop/product-option-values/dress\_l",

 "/api/v2/shop/product-option-values/dress\_xl",

 "/api/v2/shop/product-option-values/dress\_xxl"

 ]

}

```

In the same way, you can check values for the other option - `dress\_height`.

Now, with all necessary data, you can find the “Beige strappy summer dress” product’s “small” and “petite” variant

You need to call a GET on the product variants collection with parameters: `productName` and with the chosen option values:

```

curl -X GET "https://master.demo.sylius.com/api/v2/shop/product-variants?product=/api/v2/shop/products/Beige\_strappy\_summer\_dress&optionValues[]=/api/v2/shop/product-option-values/dress\_height\_petite&optionValues[]=/api/v2/shop/product-option-values/dress\_s" -H "accept: application/ld+json"

```

In the response you should get a collection with only one item:

```

{

 "hydra:member": [

 {

 "id": 579960,

 "code": "Beige\_strappy\_summer\_dress-variant-0",

 "product": "/api/v2/shop/products/Beige\_strappy\_summer\_dress",

 "optionValues": [

 "/api/v2/shop/product-option-values/dress\_s",

 "/api/v2/shop/product-option-values/dress\_height\_petite"

 ],

 "translations": {

 "en\_US": {

 "@id": "/api/v2/shop/product-variant-translations/579960",

 "@type": "ProductVariantTranslation",

 "id": 579960,

 "name": "S Petite",

 "locale": "en\_US"

 }

 },

 "price": 7693

 }

 ]

}

```

Warning

When you search by only some of the product’s option values in the response you may get a collection with more than one object.

And with this information, you can add the chosen `product variant` to the cart:

```

curl -X PATCH "https://master.demo.sylius.com/api/v2/shop/orders/ORDER\_TOKEN/items" -H "accept: application/ld+json" -H "Content-Type: application/merge-patch+json"

```

with body:

```

{

 "productCode": "Beige\_strappy\_summer\_dress",

 "productVariantCode": "Beige\_strappy\_summer\_dress-variant-0",

 "quantity": 1

}

```

##### How to force already registered user to login during checkout in Sylius API?[¶](#how-to-force-already-registered-user-to-login-during-checkout-in-sylius-api "Permalink to this headline")

You can force the user to log in during checkout if he is already registered in your app.

###### Create a new constraint validator[¶](#create-a-new-constraint-validator "Permalink to this headline")

Firstly you need to add a new constraint class:

```

// src/Validator/Constraints

<?php

declare(strict\_types=1);

namespace App\Validator\Constraints;

use Symfony\Component\Validator\Constraint;

final class UserAlreadyRegistered extends Constraint

{

    public string $message = 'This email is already registered. Please log in.';

    public function validatedBy(): string

    {

        return 'sylius\_api\_registered\_user\_validator';

    }

    public function getTargets(): string

    {

        return self::CLASS\_CONSTRAINT;

    }

}

```

Then you need to add a validator class:

```

// src/Validator/Constraints

<?php

declare(strict\_types=1);

namespace App\Validator\Constraints;

use Sylius\Component\Core\Model\CustomerInterface;

use Sylius\Component\Core\Model\ShopUserInterface;

use Sylius\Component\Core\Repository\CustomerRepositoryInterface;

use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;

use Symfony\Component\Validator\Constraint;

use Symfony\Component\Validator\ConstraintValidator;

use Webmozart\Assert\Assert;

final class UserAlreadyRegisteredValidator extends ConstraintValidator

{

    public function \_\_construct(

        private CustomerRepositoryInterface $customerRepository,

        private TokenStorageInterface $tokenStorage

    ) {

    }

    public function validate($value, Constraint $constraint): void

    {

        /\*\* @var UserAlreadyRegistered $constraint \*/

        Assert::isInstanceOf($constraint, UserAlreadyRegistered::class);

        $token = $this->tokenStorage->getToken();

        /\*\* @var CustomerInterface|null $existingCustomer \*/

        $existingCustomer = $this->customerRepository->findOneBy(['email' => $value->getEmail()]);

        if (null !== $existingCustomer && !$token->getUser() instanceof ShopUserInterface) {

            $this->context->addViolation($constraint->message);

        }

    }

}

```

###### Enabling your validator[¶](#enabling-your-validator "Permalink to this headline")

As the last thing you need to do is enable this constraint validator in your app and register it as a service in `services.yaml`. You can do it this way:

```

// # config/validator/validation.xml

<?xml version="1.0" encoding="UTF-8"?>

<constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping http://symfony.com/schema/dic/services/constraint-mapping-1.0.xsd">

 <class name="Sylius\Bundle\ApiBundle\Command\Checkout\UpdateCart">

 <constraint name="App\Validator\Constraints\UserAlreadyRegistered">

 <option name="groups">

 <value>sylius</value>

 </option>

 </constraint>

 </class>

</constraint-mapping>

```

```

// # config/services.yaml

services:

 # other definitions

 App\Validator\Constraints\UserAlreadyRegisteredValidator:

 class: App\Validator\Constraints\UserAlreadyRegisteredValidator

 tags: [ { name: validator.constraint\_validator, alias: sylius\_api\_registered\_user\_validator } ]

```

* [How to add product variants by options to the cart in Sylius API?](index.html#document-cookbook/api/add_to_cart_product_chosen_by_product_options)

* [How to force already registered user to login during checkout in Sylius API?](index.html#document-cookbook/api/how_force_login_already_registered_user_during_checkout)

The BDD Guide[¶](#the-bdd-guide "Permalink to this headline")

-------------------------------------------------------------

In [the BDD Guide](index.html#document-bdd/index) you will learn how to write clean and reusable features, contexts and pages using Behat.

### The BDD Guide[¶](#the-bdd-guide "Permalink to this headline")

Behaviour driven development is an approach to software development process that provides software development and management teams

with shared tools and a shared process to collaborate on software development. The awesome part of BDD is its ubiquitous language,

which is used to describe the software in English-like sentences of domain specific language.

The application’s behaviour is described by scenarios, and those scenarios are turned into automated test suites with tools such as Behat.

Sylius behaviours are fully covered with Behat scenarios. There are more than 1200 scenarios in the Sylius suite, and if you want

to understand some aspects of Sylius better, or are wondering how to configure something, we strongly recommend

reading them. They can be found in the `features/` directory of the Sylius/Sylius repository.

We use [FriendsOfBehat/SymfonyExtension](https://github.com/FriendsOfBehat/SymfonyExtension) to integrate Behat with Symfony.

#### Basic Usage[¶](#basic-usage "Permalink to this headline")

The best way of understanding how things work in detail is showing and analyzing examples, that is why this section gathers all the knowledge from the previous chapters.

Let’s assume that we are going to implement the functionality of managing countries in our system.

Now let us show you the flow.

##### Describing features[¶](#describing-features "Permalink to this headline")

Let’s start with writing our feature file, which will contain answers to the most important questions:

Why (benefit, business value), who (actor using the feature) and what (the feature itself).

It should also include scenarios, which serve as examples of how things supposed to work.

Let’s have a look at the `features/addressing/managing\_countries/adding\_country.feature` file.

```

# features/addressing/managing\_countries/adding\_country.feature

@managing\_countries

Feature: Adding a new country

 In order to sell my goods to different countries

 As an Administrator

 I want to add a new country to the store

 Background:

 Given I am logged in as an administrator

 @ui

 Scenario: Adding country

 When I want to add a new country

 And I choose "United States"

 And I add it

 Then I should be notified that it has been successfully created

 And the country "United States" should appear in the store

```

Pay attention to the form of these sentences. From the developer point of view they are hiding the details of the feature’s implementation.

Instead of describing “When I click on the select box And I choose United States from the dropdown Then I should see the United States country in the table”

- we are using sentences that are less connected with the implementation, but more focused on the effects of our actions.

A side effect of such approach is that it results in steps being really generic, therefore if we want to add another way of testing this feature for instance in the domain or api context,

it will be extremely easy to apply. We just need to add a different tag (in this case “@domain”) and of course implement the proper steps in the domain context of our system.

To be more descriptive let’s imagine that we want to check if a country is added properly in two ways.

First we are checking if the adding works via frontend, so we are implementing steps that are clicking, opening pages,

filling fields on forms and similar, but also we want to check this action regardlessly of the frontend, for that we need the domain, which allows us to perform actions only on objects.

##### Choosing a correct suite[¶](#choosing-a-correct-suite "Permalink to this headline")

After we are done with a feature file, we have to create a new suite for it.

At the beginning we have decided that it will be a frontend/user interface feature:

```

# src/Sylius/Behat/Resources/config/suites/ui/addressing/managing\_countries.yml

default:

 suites:

 ui\_managing\_countries:

 contexts:

 # This service is responsible for clearing database before each scenario,

 # so that only data from the current and its background is available.

 - sylius.behat.context.hook.doctrine\_orm

 # The transformer contexts services are responsible for all the transformations of data in steps:

 # For instance "And the country "France" should appear in the store" transforms "(the country "France")" to a proper Country object, which is from now on available in the scope of the step.

 - sylius.behat.context.transform.country

 - sylius.behat.context.transform.shared\_storage

 # The setup contexts here are preparing the background, adding available countries and users or administrators.

 # These contexts have steps like "I am logged in as an administrator" already implemented.

 - sylius.behat.context.setup.geographical

 - sylius.behat.context.setup.security

 # Lights, Camera, Action!

 # Those contexts are essential here we are placing all action steps like "When I choose "France" and I add it Then I should ne notified that...".

 - sylius.behat.context.ui.admin.managing\_countries

 - sylius.behat.context.ui.admin.notification

 filters:

 tags: "@managing\_countries && @ui"

```

A very important thing that is done here is the configuration of tags, from now on Behat will be searching for all your features tagged with `@managing\_countries` and your scenarios tagged with `@ui`.

Second thing is `contexts` in this section we will be placing all our services with step implementation.

We have mentioned with the generic steps we can easily switch our testing context to `@domain`. Have a look how it looks:

```

# src/Sylius/Behat/Resources/config/suites/domain/addressing/managing\_countries.yml

default:

 suites:

 domain\_managing\_countries:

 contexts\_services:

 - sylius.behat.context.hook.doctrine\_orm

 - sylius.behat.context.transform.country

 - sylius.behat.context.transform.shared\_storage

 - sylius.behat.context.setup.geographical

 - sylius.behat.context.setup.security

 # Domain step implementation.

 - sylius.behat.context.domain.admin.managing\_countries

 filters:

 tags: "@managing\_countries && @domain"

```

We are almost finished with the suite configuration.

##### Registering Pages[¶](#registering-pages "Permalink to this headline")

The page object approach allows us to hide all the detailed interaction with ui (html, javascript, css) inside.

We have three kinds of pages:

* Page - First layer of our pages it knows how to interact with DOM objects. It has a method `getUrl(array $urlParameters)` where you can define a raw url to open it.

* SymfonyPage - This page extends the Page. It has a router injected so that the `getUrl()` method generates a url from the route name which it gets from the `getRouteName()` method.

* Base Crud Pages (IndexPage, CreatePage, UpdatePage) - These pages extend SymfonyPage and they are specific to the Sylius resources. They have a resource name injected and therefore they know about the route name.

There are two ways to manipulate UI - by using `->getDocument()` or `->getElement('your\_element')`.

First method will return a `DocumentElement` which represents an html structure of the currently opened page,

second one is a bit more tricky because it uses the `->getDefinedElements()` method and it will return a `NodeElement` which represents only the restricted html structure.

Usage example of `getElement('your\_element')` and `getDefinedElements()` methods.

```

final class CreatePage extends SymfonyPage implements CreatePageInterface

{

    // This method returns a simple associative array, where the key is the name of your element and the value is its locator.

    protected function getDefinedElements(): array

    {

        return array\_merge(parent::getDefinedElements(), [

            'provinces' => '#sylius\_country\_provinces',

        ]);

    }

    // By default it will assume that your locator is css.

    protected function getDefinedElements(): array

    {

        return array\_merge(parent::getDefinedElements(), [

            'provinces\_css' => '.provinces',

            'provinces\_xpath' => ['xpath' => '//\*[contains(@class, "provinces")]'], // Now your value is an array where key is your locator type.

        ]);

    }

    // Like that you can easily manipulate your page elements.

    public function addProvince(ProvinceInterface $province): void

    {

        $provinceSelectBox = $this->getElement('provinces');

        $provinceSelectBox->selectOption($province->getName());

    }

}

```

Let’s get back to our main example and analyze our scenario. We have steps like:

```

When I choose "France"

And I add it

Then I should be notified that it has been successfully created

And the country "France" should appear in the store

```

```

namespace Sylius\Behat\Page\Admin\Country;

use Sylius\Behat\Page\Admin\Crud\CreatePage as BaseCreatePage;

final class CreatePage extends BaseCreatePage implements CreatePageInterface

{

    public function chooseName(string $name): void

    {

        $this->getDocument()->selectFieldOption('Name', $name);

    }

    public function create(): void

    {

        $this->getDocument()->pressButton('Create');

    }

}

```

```

namespace Sylius\Behat\Page\Admin\Country;

use Sylius\Behat\Page\Admin\Crud\IndexPage as BaseIndexPage;

final class IndexPage extends BaseIndexPage implements IndexPageInterface

{

    public function isSingleResourceOnPage(array $parameters): bool

    {

        try {

            // Table accessor is a helper service which is responsible for all html table operations.

            $rows = $this->tableAccessor->getRowsWithFields($this->getElement('table'), $parameters);

            return 1 === count($rows);

        } catch (ElementNotFoundException $exception) {

            // Table accessor throws this exception when cannot find table element on page.

            return false;

        }

    }

}

```

There is one small gap in this concept - PageObjects is not a concrete instance of the currently opened page, they only mimic its behaviour (dummy pages).

This gap will be more understandable on the below code example.

```

// Of course this is only to illustrate this gap.

class HomePage

{

    // In this context on home page sidebar you have for example weather information in selected countries.

    public function readWeather()

    {

        return $this->getElement('sidebar')->getText();

    }

    protected function getDefinedElements(): array

    {

        return ['sidebar' => ['css' => '.sidebar']];

    }

    protected function getUrl(): string

    {

        return 'https://your\_domain.com';

    }

}

class LeagueIndexPage

{

    // In this context you have for example football match results.

    public function readMatchResults(): void

    {

        return $this->getElement('sidebar')->getText();

    }

    protected function getDefinedElements(): array

    {

        return ['sidebar' => ['css' => '.sidebar']];

    }

    protected function getUrl(): string

    {

        return 'https://your\_domain.com/leagues/';

    }

}

final class GapContext implements Context

{

    private $homePage;

    private $leagueIndexPage;

    /\*\*

 \* @Given I want to be on Homepage

 \*/

    public function iWantToBeOnHomePage(): void// After this method call we will be on "https://your\_domain.com".

    {

        $this->homePage->open(); //When we add @javascript tag we can actually see this thanks to selenium.

    }

    /\*\*

 \* @Then I want to see the sidebar and get information about the weather in France

 \*/

    public function iWantToReadSideBarOnHomePage($someInformation): void // Still "https://your\_domain.com".

    {

        $someInformation === $this->leagueIndexPage->readMatchResults(); // This returns true, but wait a second we are on home page (dummy pages).

        $someInformation === $this->homePage->readWeather(); // This also returns true.

    }

}

```

##### Registering contexts[¶](#registering-contexts "Permalink to this headline")

As it was shown in the previous section we have registered a lot of contexts, so we will show you only some of the steps implementation.

```

Given I want to add a new country

And I choose "United States"

And I add it

Then I should be notified that it has been successfully created

And the country "United States" should appear in the store

```

Let’s start with essential one ManagingCountriesContext

###### Ui contexts[¶](#ui-contexts "Permalink to this headline")

```

namespace Sylius\Behat\Context\Ui\Admin;

final class ManagingCountriesContext implements Context

{

    /\*\* @var IndexPageInterface \*/

    private $indexPage;

    /\*\* @var CreatePageInterface \*/

    private $createPage;

    /\*\* @var UpdatePageInterface \*/

    private $updatePage;

    public function \_\_construct(

        IndexPageInterface $indexPage,

        CreatePageInterface $createPage,

        UpdatePageInterface $updatePage

    ) {

        $this->indexPage = $indexPage;

        $this->createPage = $createPage;

        $this->updatePage = $updatePage;

    }

    /\*\*

 \* @Given I want to add a new country

 \*/

    public function iWantToAddNewCountry(): void

    {

        $this->createPage->open(); // This method will send request.

    }

    /\*\*

 \* @When I choose :countryName

 \*/

    public function iChoose(string $countryName): void

    {

        $this->createPage->chooseName($countryName);

        // Great benefit of using page objects is that we hide html manipulation behind a interfaces so we can inject different CreatePage which implements CreatePageInterface

        // And have different html elements which allows for example chooseName($countryName).

    }

    /\*\*

 \* @When I add it

 \*/

    public function iAddIt(): void

    {

        $this->createPage->create();

    }

    /\*\*

 \* @Then /^the (country "([^"]+)") should appear in the store$/

 \*/

    public function countryShouldAppearInTheStore(CountryInterface $country): void // This step use Country transformer to get Country object.

    {

        $this->indexPage->open();

        //Webmozart assert library.

        Assert::true(

            $this->indexPage->isSingleResourceOnPage(['code' => $country->getCode()]),

            sprintf('Country %s should exist but it does not', $country->getCode())

        );

    }

}

```

```

namespace Sylius\Behat\Context\Ui\Admin;

final class NotificationContext implements Context

{

    /\*\*

 \* This is a helper service which give access to proper notification elements.

 \*

 \* @var NotificationCheckerInterface

 \*/

    private $notificationChecker;

    public function \_\_construct(NotificationCheckerInterface $notificationChecker)

    {

        $this->notificationChecker = $notificationChecker;

    }

    /\*\*

 \* @Then I should be notified that it has been successfully created

 \*/

    public function iShouldBeNotifiedItHasBeenSuccessfullyCreated(): void

    {

        $this->notificationChecker->checkNotification('has been successfully created.', NotificationType::success());

    }

}

```

###### Transformer contexts[¶](#transformer-contexts "Permalink to this headline")

```

namespace Sylius\Behat\Context\Transform;

final class CountryContext implements Context

{

    /\*\* @var CountryNameConverterInterface \*/

    private $countryNameConverter;

    /\*\* @var RepositoryInterface \*/

    private $countryRepository;

    public function \_\_construct(

        CountryNameConverterInterface $countryNameConverter,

        RepositoryInterface $countryRepository

    ) {

        $this->countryNameConverter = $countryNameConverter;

        $this->countryRepository = $countryRepository;

    }

    /\*\*

 \* @Transform /^country "([^"]+)"$/

 \* @Transform /^"([^"]+)" country$/

 \*/

    public function getCountryByName(string $countryName): CountryInterface // Thanks to this method we got in our ManagingCountries an Country object.

    {

        $countryCode = $this->countryNameConverter->convertToCode($countryName);

        $country = $this->countryRepository->findOneBy(['code' => $countryCode]);

        Assert::notNull(

            $country,

            'Country with name %s does not exist'

        );

        return $country;

    }

}

```

```

namespace Sylius\Behat\Context\Ui\Admin;

use Sylius\Behat\Page\Admin\Country\UpdatePageInterface;

final class ManagingCountriesContext implements Context

{

    /\*\* @var UpdatePageInterface \*/

    private $updatePage;

    public function \_\_construct(UpdatePageInterface $updatePage)

    {

        $this->updatePage = $updatePage;

    }

    /\*\*

 \* @Given /^I want to create a new province in (country "[^"]+")$/

 \*/

    public function iWantToCreateANewProvinceInCountry(CountryInterface $country): void

    {

        $this->updatePage->open(['id' => $country->getId()]);

        $this->updatePage->clickAddProvinceButton();

    }

}

```

```

namespace Sylius\Behat\Context\Transform;

final class ShippingMethodContext implements Context

{

    /\*\* @var ShippingMethodRepositoryInterface \*/

    private $shippingMethodRepository;

    public function \_\_construct(ShippingMethodRepositoryInterface $shippingMethodRepository)

    {

        $this->shippingMethodRepository = $shippingMethodRepository;

    }

    /\*\*

 \* @Transform :shippingMethod

 \*/

    public function getShippingMethodByName(string $shippingMethodName): void

    {

        $shippingMethod = $this->shippingMethodRepository->findOneByName($shippingMethodName);

        if (null === $shippingMethod) {

            throw new \Exception('Shipping method with name "'.$shippingMethodName.'" does not exist');

        }

        return $shippingMethod;

    }

}

```

```

namespace Sylius\Behat\Context\Ui\Admin;

use Sylius\Behat\Page\Admin\ShippingMethod\UpdatePageInterface;

final class ShippingMethodContext implements Context

{

    /\*\* @var UpdatePageInterface \*/

    private $updatePage;

    public function \_\_construct(UpdatePageInterface $updatePage)

    {

        $this->updatePage = $updatePage;

    }

    /\*\*

 \* @Given I want to modify a shipping method :shippingMethod

 \*/

    public function iWantToModifyAShippingMethod(ShippingMethodInterface $shippingMethod): void

    {

        $this->updatePage->open(['id' => $shippingMethod->getId()]);

    }

}

```

Warning

Contexts should have single responsibility and this segregation (Setup, Transformer, Ui, etc…) is not accidental.

We shouldn’t create objects in transformer contexts.

###### Setup contexts[¶](#setup-contexts "Permalink to this headline")

For setup context we need different scenario with more background steps and all preparing scene steps.

Editing scenario will be great for this example:

```

Given the store has disabled country "France"

And I want to edit this country

When I enable it

And I save my changes

Then I should be notified that it has been successfully edited

And this country should be enabled

```

```

namespace Sylius\Behat\Context\Setup;

final class GeographicalContext implements Context

{

    /\*\* @var SharedStorageInterface \*/

    private $sharedStorage;

    /\*\* @var FactoryInterface \*/

    private $countryFactory;

    /\*\* @var RepositoryInterface \*/

    private $countryRepository;

    /\*\* @var CountryNameConverterInterface \*/

    private $countryNameConverter;

    public function \_\_construct(

        SharedStorageInterface $sharedStorage,

        FactoryInterface $countryFactory,

        RepositoryInterface $countryRepository,

        CountryNameConverterInterface $countryNameConverter

    ) {

        $this->sharedStorage = $sharedStorage;

        $this->countryFactory = $countryFactory;

        $this->countryRepository = $countryRepository;

        $this->countryNameConverter = $countryNameConverter;

    }

    /\*\*

 \* @Given /^the store has disabled country "([^"]\*)"$/

 \*/

    public function theStoreHasDisabledCountry(string $countryName): void // This method save country in data base.

    {

        $country = $this->createCountryNamed(trim($countryName));

        $country->disable();

        $this->sharedStorage->set('country', $country);

        // Shared storage is an helper service for transferring objects between steps.

        // There is also SharedStorageContext which use this helper service to transform sentences like "(this country), (it), (its), (theirs)" into Country Object.

        $this->countryRepository->add($country);

    }

    private function createCountryNamed(string $name): CountryInterface

    {

        /\*\* @var CountryInterface $country \*/

        $country = $this->countryFactory->createNew();

        $country->setCode($this->countryNameConverter->convertToCode($name));

        return $country;

    }

}

```

#### How to add a new context?[¶](#how-to-add-a-new-context "Permalink to this headline")

To add a new context to Behat container it is needed to add a service in to one of a following files `cli.xml`/`domain.xml`/`hook.xml`/`setup.xml`/`transform.xml`/`ui.xml` in `src/Sylius/Behat/Resources/config/services/contexts/` folder:

```

<service id="sylius.behat.context.CONTEXT\_CATEGORY.CONTEXT\_NAME"

 class="%sylius.behat.context.CONTEXT\_CATEGORY.CONTEXT\_NAME.class%"

 public="true" />

```

Then you can use it in your suite configuration:

```

default:

 suites:

 SUITE\_NAME:

 contexts:

 - "sylius.behat.context.CONTEXT\_CATEGORY.CONTEXT\_NAME"

 filters:

 tags: "@SUITE\_TAG"

```

Note

The context categories are usually one of `hook`, `setup`, `ui` and `domain` and, as you can guess, they are corresponded to files name mentioned above.

#### How to add a new page object?[¶](#how-to-add-a-new-page-object "Permalink to this headline")

Sylius uses a solution inspired by `SensioLabs/PageObjectExtension`, which provides an infrastructure to create

pages that encapsulates all the user interface manipulation in page objects.

To create a new page object it is needed to add a service in Behat container in `etc/behat/services/pages.xml` file:

```

<service id="sylius.behat.page.PAGE\_NAME" class="%sylius.behat.page.PAGE\_NAME.class%" parent="sylius.behat.symfony\_page" public="false" />

```

Note

There are some boilerplates for common pages, which you may use. The available parents are `sylius.behat.page` (`FriendsOfBehat\PageObjectExtension\Page\Page`)

and `sylius.behat.symfony\_page` (`FriendsOfBehat\PageObjectExtension\Page\SymfonyPage`). It is not required for a page to extend any class as

pages are POPOs (Plain Old PHP Objects).

Then you will need to add that service as a regular argument in context service.

The simplest Symfony-based page looks like:

```

use FriendsOfBehat\PageObjectExtension\Page\SymfonyPage;

class LoginPage extends SymfonyPage

{

    public function getRouteName(): string

    {

        return 'sylius\_user\_security\_login';

    }

}

```

#### How to define a new suite?[¶](#how-to-define-a-new-suite "Permalink to this headline")

To define a new suite it is needed to create a suite configuration file in a one of `cli`/`domain`/`ui` directory inside `src/Sylius/Behat/Resources/config/suites`.

Then register that file in `src/Sylius/Behat/Resources/config/suites.yml`.

#### How to use transformers?[¶](#how-to-use-transformers "Permalink to this headline")

Behat provides many awesome features, and one of them is definitely **transformers**. They can be used to transform (usually widely used) parts of steps and return some values from them,

to prevent unnecessary duplication in many steps’ definitions.

##### Basic transformer[¶](#basic-transformer "Permalink to this headline")

Example is always the best way to clarify, so let’s look at this:

```

/\*\*

 \* @Transform /^"([^"]+)" shipping method$/

 \* @Transform /^shipping method "([^"]+)"$/

 \* @Transform :shippingMethod

 \*/

public function getShippingMethodByName(string $shippingMethodName): ShippingMethodInterface

{

    $shippingMethod = $this->shippingMethodRepository->findOneByName($shippingMethodName);

    Assert::notNull(

        $shippingMethod,

        sprintf('Shipping method with name "%s" does not exist', $shippingMethodName)

    );

    return $shippingMethod;

}

```

This transformer is used to return `ShippingMethod` object from proper repository using it’s name. It also throws exception if such a method does not exist. It can be used in plenty of steps,

that have shipping method name in it.

Note

In the example above a [Webmozart assertion](https://github.com/webmozart/assert) library was used, to assert a value and throw an exception if needed.

But how to use it? It is as simple as that:

```

/\*\*

 \* @Given /^(shipping method "[^"]+") belongs to ("[^"]+" tax category)$/

 \*/

public function shippingMethodBelongsToTaxCategory(

    ShippingMethodInterface $shippingMethod,

    TaxCategoryInterface $taxCategory

): void {

    // some logic here

}

```

If part of step matches transformer definition, it should be surrounded by parenthesis to be handled as whole expression. That’s it! As it is shown in the example, many transformers can be

used in the same step definition. Is it all? No! The following example will also work like charm:

```

/\*\*

 \* @When I delete shipping method :shippingMethod

 \* @When I try to delete shipping method :shippingMethod

 \*/

public function iDeleteShippingMethod(ShippingMethodInterface $shippingMethod): void

{

    // some logic here

}

```

It is worth to mention, that in such a case, transformer would be matched depending on a name after ‘:’ sign. So many transformers could be used when using this signature also.

This style gives an opportunity to write simple steps with transformers, without any regex, which would boost context readability.

Note

Transformer definition does not have to be implemented in the same context, where it is used. It allows to share them between many different contexts.

##### Transformers implemented in Sylius[¶](#transformers-implemented-in-sylius "Permalink to this headline")

###### Specified[¶](#specified "Permalink to this headline")

There are plenty of transformers already implemented in *Sylius*. Most of them return specific resources from their repository, for example:

* `tax category "Fruits"` -> find tax category in their repository with name “Fruits”

* `"Chinese banana" variant of product "Banana"` -> find variant of specific product

etc. You’re free to use them in your own behat scenarios.

Note

All transformers definitions are currently kept in `Sylius\Behat\Context\Transform` namespace.

Warning

Remember to include contexts with transformers in custom suite to be able to use them!

###### Generic[¶](#generic "Permalink to this headline")

Moreover, there are also some more generic transformers, that could be useful in many different cases. They are now placed in two contexts: `LexicalContext` and `SharedStorageContext`.

Why are they so awesome? Let’s describe them one by one:

**LexicalContext**

* `@Transform /^"(?:€|£|\$)((?:\d+\.)?\d+)"$/` -> tricky transformer used to parse price string with currency into integer (used to represent price in *Sylius*). It is used in steps like `this promotion gives "€30.00" fixed discount to every order`

* `@Transform /^"((?:\d+\.)?\d+)%"$/` -> similar one, transforming percentage string into float (example: `this promotion gives "10%" percentage discount to every order`)

**SharedStorageContext**

Note

`SharedStorage` is kind of container used to keep objects, which can be shared between steps. It can be used, for example, to keep newly created promotion,

to use its name in checking existence step.

* `@Transform /^(it|its|theirs)$/` -> amazingly useful transformer, that returns last resource saved in `SharedStorage`. It allows to simplify many steps used after creation/update (and so on) actions. Example: instead of writing `When I create "Wade Wilson" customer/Then customer "Wade Wilson" should be registered` just write `When I create "Wade Wilson" customer/Then it should be registered`

* `@Transform /^(?:this|that|the) ([^"]+)$/` -> similar to previous one, but returns resource saved with specific key, for example `this promotion` will return resource saved with `promotion` key in `SharedStorage`

* [Basic Usage](index.html#document-bdd/basic-usage)

* [How to add a new context?](index.html#document-bdd/how-to-add-new-context)

* [How to add a new page object?](index.html#document-bdd/how-to-add-new-page)

* [How to define a new suite?](index.html#document-bdd/how-to-define-new-suite)

* [How to use transformers?](index.html#document-bdd/how-to-use-transformers)

* [Basic Usage](index.html#document-bdd/basic-usage)

* [How to add a new context?](index.html#document-bdd/how-to-add-new-context)

* [How to add a new page object?](index.html#document-bdd/how-to-add-new-page)

* [How to define a new suite?](index.html#document-bdd/how-to-define-new-suite)

* [How to use transformers?](index.html#document-bdd/how-to-use-transformers)

Components & Bundles[¶](#components-bundles "Permalink to this headline")

-------------------------------------------------------------------------

[Documentation of all Sylius components and bundles](index.html#document-components_and_bundles/index) useful when using them standalone.

### Components & Bundles[¶](#components-bundles "Permalink to this headline")

#### Sylius Bundles Documentation[¶](#sylius-bundles-documentation "Permalink to this headline")

##### SyliusAddressingBundle[¶](#syliusaddressingbundle "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

This bundle integrates the [Addressing](index.html#document-components_and_bundles/components/Addressing/index) into Symfony and Doctrine.

With minimal configuration you can introduce addresses, countries, provinces and zones management into your project.

It’s fully customizable, but the default setup should be optimal for most use cases.

It also contains zone matching mechanisms, which allow you to match customer addresses to appropriate tax/shipping (or any other) zones.

There are several models inside the bundle, Address, Country, Province, Zone and ZoneMember.

There is also a **ZoneMatcher** service.

You’ll get familiar with it in later parts of this documentation.

###### Installation[¶](#installation "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

We assume you’re familiar with [Composer](https://packagist.org), a dependency manager for PHP.

Use following command to add the bundle to your composer.json and download package.

If you have [Composer installed globally](https://getcomposer.org/doc/00-intro.md#globally).

```

composer require sylius/addressing-bundle

```

Otherwise you have to download .phar file.

```

curl -sS https://getcomposer.org/installer | php

php composer.phar require sylius/addressing-bundle:*

```

####### Adding required bundles to the kernel[¶](#adding-required-bundles-to-the-kernel "Permalink to this headline")

You need to enable the bundle inside the kernel.

If you’re not using any other Sylius bundles, you will also need to add SyliusResourceBundle and its dependencies to kernel.

Don’t worry, everything was automatically installed via Composer.

```

<?php

// app/AppKernel.php

public function registerBundles()

{

    $bundles = array(

        new winzou\Bundle\StateMachineBundle\winzouStateMachineBundle(),

        new FOS\RestBundle\FOSRestBundle(),

        new JMS\SerializerBundle\JMSSerializerBundle(),

        new Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle(),

        new BabDev\PagerfantaBundle\BabDevPagerfantaBundle(),

        new Sylius\Bundle\AddressingBundle\SyliusAddressingBundle(),

        new Sylius\Bundle\ResourceBundle\SyliusResourceBundle(),

        // Other bundles...

        new Doctrine\Bundle\DoctrineBundle\DoctrineBundle(),

    );

}

```

####### Updating database schema[¶](#updating-database-schema "Permalink to this headline")

Run the following command.

```

php bin/console doctrine:schema:update --force

```

Warning

This should be done only in **dev** environment! We recommend using Doctrine migrations, to safely update your schema.

###### ZoneMatcher[¶](#zonematcher "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

This bundle exposes the **ZoneMatcher** as `sylius.zone\_matcher` service.

```

<?php

$zoneMatcher = $this->get('sylius.zone\_matcher');

$zone = $zoneMatcher->match($user->getBillingAddress());

```

###### Forms[¶](#forms "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

The bundle ships with a set of useful form types for all models. You can use the defaults or [override them](index.html#document-customization/form) with your own types.

####### Address form[¶](#address-form "Permalink to this headline")

The address form type is named `sylius\_address` and you can create it whenever you need, using the form factory.

```

<?php

// src/Acme/ShopBundle/Controller/AddressController.php

namespace Acme\ShopBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;

use Symfony\Component\HttpFoundation\Request;

class DemoController extends Controller

{

    public function fooAction(Request $request)

    {

        $form = $this->get('form.factory')->create('sylius\_address');

    }

}

```

You can also embed it into another form.

```

<?php

// src/Acme/ShopBundle/Form/Type/OrderType.php

namespace Acme\ShopBundle\Form\Type;

use Sylius\Bundle\OrderBundle\Form\Type\OrderType as BaseOrderType;

use Symfony\Component\Form\FormBuilderInterface;

class OrderType extends BaseOrderType

{

    public function buildForm(FormBuilderInterface $builder, array $options)

    {

        parent::buildForm($builder, $options);

        $builder

            ->add('billingAddress', 'sylius\_address')

            ->add('shippingAddress', 'sylius\_address')

        ;

    }

}

```

###### Summary[¶](#summary "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### Configuration Reference[¶](#configuration-reference "Permalink to this headline")

```

sylius\_addressing:

 # The driver used for persistence layer.

 driver: ~

 resources:

 address:

 classes:

 model: Sylius\Component\Addressing\Model\Address

 interface: Sylius\Component\Addressing\Model\AddressInterface

 controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController

 repository: ~

 factory: Sylius\Component\Resource\Factory\Factory

 form: Sylius\Bundle\AddressingBundle\Form\Type\AddressType

 country:

 classes:

 model: Sylius\Component\Addressing\Model\Country

 interface: Sylius\Component\Addressing\Model\CountryInterface

 controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController

 repository: ~

 factory: Sylius\Component\Resource\Factory\Factory

 form: Sylius\Bundle\AddressingBundle\Form\Type\CountryType

 province:

 classes:

 model: Sylius\Component\Addressing\Model\Province

 interface: Sylius\Component\Addressing\Model\ProvinceInterface

 controller: Sylius\Bundle\AddressingBundle\Controller\ProvinceController

 repository: ~

 factory: Sylius\Component\Resource\Factory\Factory

 form: Sylius\Bundle\AddressingBundle\Form\Type\ProvinceType

 zone:

 classes:

 model: Sylius\Component\Addressing\Model\Zone

 interface: Sylius\Component\Addressing\Model\ZoneInterface

 controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController

 repository: ~

 factory: Sylius\Component\Resource\Factory\Factory

 form: Sylius\Bundle\AddressingBundle\Form\Type\ZoneType

 zone\_member:

 classes:

 model: Sylius\Component\Addressing\Model\ZoneMember

 interface: Sylius\Component\Addressing\Model\ZoneMemberInterface

 controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController

 repository: ~

 factory: Sylius\Component\Resource\Factory\Factory

 form: Sylius\Bundle\AddressingBundle\Form\Type\ZoneMemberType

```

####### Bug tracking[¶](#bug-tracking "Permalink to this headline")

This bundle uses [GitHub issues](https://github.com/Sylius/Sylius/issues).

If you have found bug, please create an issue.

###### Learn more[¶](#learn-more "Permalink to this headline")

* [Addresses in the Sylius platform](index.html#document-book/customers/addresses/index) - concept documentation

##### SyliusAttributeBundle[¶](#syliusattributebundle "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

This bundle provides easy integration of the [Sylius Attribute component](index.html#document-components_and_bundles/components/Attribute/index)

with any Symfony full-stack application.

Sylius uses this bundle internally for its product catalog to manage the different attributes that are specific to each

product.

###### Installation[¶](#installation "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

We assume you’re familiar with [Composer](https://packagist.org), a dependency manager for PHP.

Use the following command to add the bundle to your composer.json and download the package.

If you have [Composer installed globally](https://getcomposer.org/doc/00-intro.md#globally).

```

composer require sylius/attribute-bundle

```

Otherwise you have to download .phar file.

```

curl -sS https://getcomposer.org/installer | php

php composer.phar require sylius/attribute-bundle

```

####### Adding required bundles to the kernel[¶](#adding-required-bundles-to-the-kernel "Permalink to this headline")

You need to enable the bundle inside the kernel.

If you’re not using any other Sylius bundles, you will also need to add SyliusResourceBundle and its dependencies to kernel.

Don’t worry, everything was automatically installed via Composer.

```

<?php

// app/AppKernel.php

public function registerBundles()

{

    $bundles = array(

        new winzou\Bundle\StateMachineBundle\winzouStateMachineBundle(),

        new FOS\RestBundle\FOSRestBundle(),

        new JMS\SerializerBundle\JMSSerializerBundle(),

        new Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle(),

        new BabDev\PagerfantaBundle\BabDevPagerfantaBundle(),

        new Sylius\Bundle\AttributeBundle\SyliusAttributeBundle(),

        new Sylius\Bundle\ResourceBundle\SyliusResourceBundle(),

        // Other bundles...

        new Doctrine\Bundle\DoctrineBundle\DoctrineBundle(),

    );

}

```

####### Container configuration[¶](#container-configuration "Permalink to this headline")

Put this configuration inside your `app/config/config.yml`.

```

sylius\_attribute:

 driver: doctrine/orm # Configure the doctrine orm driver used in the documentation.

```

And configure doctrine extensions which are used by the bundle.

```

stof\_doctrine\_extensions:

 orm:

 default:

 timestampable: true

```

####### Updating database schema[¶](#updating-database-schema "Permalink to this headline")

Run the following command.

```

php bin/console doctrine:schema:update --force

```

Warning

This should be done only in **dev** environment! We recommend using Doctrine migrations, to safely update your schema.

Congratulations! The bundle is now installed and ready to use.

###### Configuration reference[¶](#configuration-reference "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

```

sylius\_attribute:

 driver: ~ # The driver used for persistence layer. Currently only `doctrine/orm` is supported.

 resources:

 # `subject\_name` can be any name, for example `product`, `ad`, or `blog\_post`

 subject\_name:

 subject: ~ # Required: The subject class implementing `AttributeSubjectInterface`.

 attribute:

 classes:

 model: Sylius\Component\Attribute\Model\Attribute

 interface: Sylius\Component\Attribute\Model\AttributeInterface

 repository: Sylius\Bundle\TranslationBundle\Doctrine\ORM\TranslatableResourceRepository

 controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController

 factory: Sylius\Component\Resource\Factory\Factory

 form: Sylius\Bundle\AttributeBundle\Form\Type\AttributeType

 translation:

 classes:

 model: Sylius\Component\Attribute\Model\AttributeTranslation

 interface: Sylius\Component\Attribute\Model\AttributeTranslationInterface

 controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController

 repository: ~ # Required: The repository class for the attribute translation.

 factory: Sylius\Component\Resource\Factory\Factory

 form: Sylius\Bundle\AttributeBundle\Form\Type\AttributeTranslationType

 attribute\_value:

 classes:

 model: ~ # Required: The model of the attribute value

 interface: ~ # Required: The interface of the attribute value

 controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController

 repository: ~ # Required: The repository class for the attribute value.

 factory: Sylius\Component\Resource\Factory\Factory

 form: Sylius\Bundle\AttributeBundle\Form\Type\AttributeValueType

```

###### Learn more[¶](#learn-more "Permalink to this headline")

* [Attributes in the Sylius platform](index.html#document-book/products/attributes) - concept documentation

##### SyliusCustomerBundle[¶](#syliuscustomerbundle "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

A solution for customer management system inside of a Symfony application.

###### Installation[¶](#installation "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

We assume you’re familiar with [Composer](https://packagist.org), a dependency manager for PHP.

Use the following command to add the bundle to your composer.json and download the package.

If you have [Composer installed globally](https://getcomposer.org/doc/00-intro.md#globally).

```

composer require sylius/customer-bundle

```

Otherwise you have to download .phar file.

```

curl -sS https://getcomposer.org/installer | php

php composer.phar require sylius/customer-bundle

```

####### Adding required bundles to the kernel[¶](#adding-required-bundles-to-the-kernel "Permalink to this headline")

You need to enable the bundle inside the kernel.

If you’re not using any other Sylius bundles, you will also need to add SyliusResourceBundle and its dependencies to kernel.

Don’t worry, everything was automatically installed via Composer.

```

<?php

// app/AppKernel.php

public function registerBundles()

{

    $bundles = array(

        new FOS\RestBundle\FOSRestBundle(),

        new JMS\SerializerBundle\JMSSerializerBundle(),

        new Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle(),

        new BabDev\PagerfantaBundle\BabDevPagerfantaBundle(),

        new Bazinga\Bundle\HateoasBundle\BazingaHateoasBundle(),

        new winzou\Bundle\StateMachineBundle\winzouStateMachineBundle(),

        new Sylius\Bundle\ResourceBundle\SyliusResourceBundle(),

        new Sylius\Bundle\CustomerBundle\SyliusCustomerBundle(),

        // Other bundles...

        new Doctrine\Bundle\DoctrineBundle\DoctrineBundle(),

    );

}

```

####### Configure Doctrine extensions[¶](#configure-doctrine-extensions "Permalink to this headline")

Configure doctrine extensions which are used by the bundle.

```

# app/config/config.yml

stof\_doctrine\_extensions:

 orm:

 default:

 timestampable: true

```

####### Updating database schema[¶](#updating-database-schema "Permalink to this headline")

Run the following command.

```

php bin/console doctrine:schema:update --force

```

Warning

This should be done only in **dev** environment! We recommend using Doctrine migrations, to safely update your schema.

Congratulations! The bundle is now installed and ready to use.

###### Summary[¶](#summary "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### Configuration reference[¶](#configuration-reference "Permalink to this headline")

```

sylius\_customer:

 driver: doctrine/orm

 resources:

 customer:

 classes:

 model: Sylius\Component\Core\Model\Customer

 repository: Sylius\Bundle\CoreBundle\Doctrine\ORM\CustomerRepository

 form:

 default: Sylius\Bundle\CoreBundle\Form\Type\Customer\CustomerType

 profile: Sylius\Bundle\CustomerBundle\Form\Type\CustomerProfileType

 choice: Sylius\Bundle\ResourceBundle\Form\Type\ResourceChoiceType

 interface: Sylius\Component\Customer\Model\CustomerInterface

 controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController

 factory: Sylius\Component\Resource\Factory\Factory

 customer\_group:

 classes:

 model: Sylius\Component\Customer\Model\CustomerGroup

 repository: Sylius\Bundle\CustomerBundle\Doctrine\ORM\CustomerGroupRepository

 interface: Sylius\Component\Customer\Model\CustomerGroupInterface

 controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController

 factory: Sylius\Component\Resource\Factory\Factory

 form: Sylius\Bundle\CustomerBundle\Form\Type\CustomerGroupType

```

####### Bug tracking[¶](#bug-tracking "Permalink to this headline")

This bundle uses [GitHub issues](https://github.com/Sylius/Sylius/issues).

If you have found bug, please create an issue.

###### Learn more[¶](#learn-more "Permalink to this headline")

* [Customers in the Sylius platform](index.html#document-book/customers/index) - concept documentation

##### SyliusInventoryBundle[¶](#syliusinventorybundle "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

Flexible inventory management for Symfony applications.

With minimal configuration you can implement inventory tracking in your project.

It’s fully customizable, but the default setup should be optimal for most use cases.

There is StockableInterface and InventoryUnit model inside the bundle.

There are services **AvailabilityChecker**, **InventoryOperator** and **InventoryChangeListener**.

You’ll get familiar with them in later parts of this documentation.

###### Installation[¶](#installation "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

We assume you’re familiar with [Composer](https://packagist.org), a dependency manager for PHP.

Use the following command to add the bundle to your composer.json and download package.

If you have [Composer installed globally](https://getcomposer.org/doc/00-intro.md#globally).

```

composer require sylius/inventory-bundle

```

Otherwise you have to download .phar file.

```

curl -sS https://getcomposer.org/installer | php

php composer.phar require sylius/inventory-bundle

```

####### Adding required bundles to the kernel[¶](#adding-required-bundles-to-the-kernel "Permalink to this headline")

First, you need to enable the bundle inside the kernel.

If you’re not using any other Sylius bundles, you will also need to add SyliusResourceBundle and its dependencies to the kernel.

Don’t worry, everything was automatically installed via Composer.

```

<?php

// app/AppKernel.php

public function registerBundles()

{

    $bundles = array(

        // ...

        new FOS\RestBundle\FOSRestBundle(),

        new Sylius\Bundle\ResourceBundle\SyliusResourceBundle(),

        new Sylius\Bundle\InventoryBundle\SyliusInventoryBundle(),

    );

}

```

####### Creating your entities[¶](#creating-your-entities "Permalink to this headline")

Let’s assume we want to implement a book store application and track the books inventory.

You have to create a Book and an InventoryUnit entity, living inside your application code.

We think that **keeping the app-specific bundle structure simple** is a good practice, so

let’s assume you have your `App` registered under `App\Bundle\AppBundle` namespace.

We will create Book entity.

```

<?php

// src/Entity/Book.php

namespace App\Entity;

use Sylius\Component\Inventory\Model\StockableInterface;

use Doctrine\ORM\Mapping as ORM;

/\*\*

 \* @ORM\Entity

 \* @ORM\Table(name="app\_book")

 \*/

class Book implements StockableInterface

{

    /\*\*

 \* @ORM\Id

 \* @ORM\Column(type="integer")

 \* @ORM\GeneratedValue(strategy="AUTO")

 \*/

    protected $id;

    /\*\*

 \* @ORM\Column(type="string")

 \*/

    protected $isbn;

    /\*\*

 \* @ORM\Column(type="string")

 \*/

    protected $title;

    /\*\*

 \* @ORM\Column(type="integer")

 \*/

    protected $onHand;

    public function \_\_construct()

    {

        $this->onHand = 1;

    }

    public function getId()

    {

        return $this->id;

    }

    public function getIsbn()

    {

        return $this->isbn;

    }

    public function setIsbn($isbn)

    {

        $this->isbn = $isbn;

    }

    public function getSku()

    {

        return $this->getIsbn();

    }

    public function getTitle()

    {

        return $this->title;

    }

    public function setTitle($title)

    {

        $this->title = $title;

    }

    public function getInventoryName()

    {

        return $this->getTitle();

    }

    public function isInStock()

    {

        return 0 < $this->onHand;

    }

    public function getOnHand()

    {

        return $this->onHand;

    }

    public function setOnHand($onHand)

    {

        $this->onHand = $onHand;

    }

}

```

Note

This example shows the full power of StockableInterface.

In order to track the books inventory our Book entity must implement StockableInterface.

Note that we added `->getSku()` method which is alias to `->getIsbn()`, this is the power of the interface,

we now have full control over the entity mapping.

In the same way `->getInventoryName()` exposes the book title as the displayed name for our stockable entity.

The next step requires the creating of the InventoryUnit entity, let’s do this now.

```

<?php

// src/Entity/InventoryUnit.php

namespace App\Entity;

use Sylius\Component\Inventory\Model\InventoryUnit as BaseInventoryUnit;

use Doctrine\ORM\Mapping as ORM;

/\*\*

 \* @ORM\Entity

 \* @ORM\Table(name="app\_inventory\_unit")

 \*/

class InventoryUnit extends BaseInventoryUnit

{

    /\*\*

 \* @ORM\Id

 \* @ORM\Column(type="integer")

 \* @ORM\GeneratedValue(strategy="AUTO")

 \*/

    protected $id;

}

```

Note that we are using base model from Sylius component, which means inheriting some functionality inventory component provides.

InventoryUnit holds the reference to stockable object, which is Book in our case.

So, if we use the InventoryOperator to create inventory units, they will reference the given book entity.

####### Container configuration[¶](#container-configuration "Permalink to this headline")

Put this configuration inside your `app/config/config.yml`.

```

sylius\_inventory:

 driver: doctrine/orm

 resources:

 inventory\_unit:

 classes:

 model: App\Entity\InventoryUnit

```

####### Updating database schema[¶](#updating-database-schema "Permalink to this headline")

Remember to update your database schema.

For “**doctrine/orm**” driver run the following command.

```

php bin/console doctrine:schema:update --force

```

Warning

This should be done only in **dev** environment! We recommend using Doctrine migrations, to safely update your schema.

###### Models[¶](#models "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

Here is a quick reference for the default models.

####### InventoryUnit[¶](#inventoryunit "Permalink to this headline")

Each unit holds a reference to a stockable object and its state, which can be **sold** or **returned**.

It also provides some handy shortcut methods like isSold.

####### Stockable[¶](#stockable "Permalink to this headline")

In order to be able to track stock levels in your application, you must implement StockableInterface or use the Stockable model.

It uses the SKU to identify stockable and need to provide display name.

It can get/set current stock level with getOnHand and setOnHand methods.

###### Using the services[¶](#using-the-services "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

When using the bundle, you have access to several handy services.

####### AvailabilityChecker[¶](#availabilitychecker "Permalink to this headline")

The name speaks for itself, this service checks availability for given stockable object.

AvailabilityChecker relies on the current stock level.

There are two methods for checking availability.

`->isStockAvailable()` just checks whether stockable object is available in stock and doesn’t care about quantity.

`->isStockSufficient()` checks if there is enough units in the stock for given quantity.

####### InventoryOperator[¶](#inventoryoperator "Permalink to this headline")

Inventory operator is the heart of this bundle. It can be used to manage stock levels and inventory units.

Creating/destroying inventory units with a given state is also the operators job.

###### Twig Extension[¶](#twig-extension "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

There are two handy twig functions bundled in: sylius\_inventory\_is\_available and sylius\_inventory\_is\_sufficient.

They are simple proxies to the availability checker, and can be used to show if the stockable object is available/sufficient.

Here is a simple example, note that product variable has to be an instance of StockableInterface.

```

{% if not sylius\_inventory\_is\_available(product) %}

 <span class="label label-important">out of stock</span>

{% endif %}

```

###### Summary[¶](#summary "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### Configuration reference[¶](#configuration-reference "Permalink to this headline")

```

sylius\_inventory:

 # The driver used for persistence layer.

 driver: ~

 # Enable or disbale tracking inventory

 track\_inventory: true

 # The availability checker service id.

 checker: sylius.availability\_checker.default

 # The inventory operator service id.

 operator: sylius.inventory\_operator.default

 # Array of events for InventoryChangeListener

 events: ~

 resources:

 inventory\_unit:

 classes:

 model: Sylius\Component\Inventory\Model\InventoryUnit

 interface: Sylius\Component\Inventory\Model\InventoryUnitInterface

 controller: Sylius\Bundle\InventoryBundle\Controller\InventoryUnitController

 repository: ~ # You can override the repository class here.

 factory: Sylius\Component\Resource\Factory\Factory

 stockable:

 classes:

 model: ~ # The stockable model class.

 controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController

```

####### Bug tracking[¶](#bug-tracking "Permalink to this headline")

This bundle uses [GitHub issues](https://github.com/Sylius/Sylius/issues).

If you have found bug, please create an issue.

###### Learn more[¶](#learn-more "Permalink to this headline")

* [Inventory in the Sylius platform](index.html#document-book/products/inventory) - concept documentation

##### SyliusOrderBundle[¶](#syliusorderbundle "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

This bundle is a foundation for sales order handling for Symfony projects.

It allows you to use any model as the merchandise.

It also includes a super flexible adjustments feature, which serves as a basis for any taxation, shipping charges or discounts system.

###### Installation[¶](#installation "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

We assume you’re familiar with [Composer](https://packagist.org), a dependency manager for PHP.

Use the following command to add the bundle to your composer.json and download package.

If you have [Composer installed globally](https://getcomposer.org/doc/00-intro.md#globally).

```

composer require sylius/order-bundle

```

Otherwise you have to download .phar file.

```

curl -sS https://getcomposer.org/installer | php

php composer.phar require sylius/order-bundle

```

####### Adding required bundles to the kernel[¶](#adding-required-bundles-to-the-kernel "Permalink to this headline")

First, you need to enable the bundle inside the kernel.

If you’re not using any other Sylius bundles, you will also need to add SyliusResourceBundle and its dependencies to the kernel.

Don’t worry, everything was automatically installed via Composer.

```

<?php

// app/AppKernel.php

public function registerBundles()

{

    $bundles = array(

        new winzou\Bundle\StateMachineBundle\winzouStateMachineBundle(),

        new FOS\RestBundle\FOSRestBundle(),

        new JMS\SerializerBundle\JMSSerializerBundle(),

        new Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle(),

        new BabDev\PagerfantaBundle\BabDevPagerfantaBundle(),

        new Sylius\Bundle\ResourceBundle\SyliusResourceBundle(),

        new Sylius\Bundle\MoneyBundle\SyliusMoneyBundle(),

        new Sylius\Bundle\OrderBundle\SyliusOrderBundle(),

        // Other bundles...

        new Doctrine\Bundle\DoctrineBundle\DoctrineBundle(),

    );

}

```

####### Updating database schema[¶](#updating-database-schema "Permalink to this headline")

Remember to update your database schema.

For “**doctrine/orm**” driver run the following command.

```

php bin/console doctrine:schema:update --force

```

Warning

This should be done only in **dev** environment! We recommend using Doctrine migrations, to safely update your schema.

###### The Order, OrderItem and OrderItemUnit[¶](#the-order-orderitem-and-orderitemunit "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

Here is a quick reference of what the default models can do for you.

####### Order basics[¶](#order-basics "Permalink to this headline")

Each order has 2 main identifiers, an *ID* and a human-friendly *number*.

You can access those by calling `->getId()` and `->getNumber()` respectively.

The number is mutable, so you can change it by calling `->setNumber('E001')` on the order instance.

```

<?php

$order->getId();

$order->getNumber();

$order->setNumber('E001');

```

####### Order totals[¶](#order-totals "Permalink to this headline")

Note

All money amounts in Sylius are represented as “cents” - integers.

An order has 3 basic totals, which are all persisted together with the order.

The first total is the *items total*, it is calculated as the sum of all item totals (including theirs adjustments).

The second total is the *adjustments total*, you can read more about this in next chapter.

```

<?php

echo $order->getItemsTotal(); // 1900.

echo $order->getAdjustmentsTotal(); // -250.

$order->calculateTotal();

echo $order->getTotal(); // 1650.

```

The main order total is a sum of the previously mentioned values.

You can access the order total value using the `->getTotal()` method.

Note

It’s not needed to call `calculateTotal()` method, as both `itemsTotal` and `adjustmentsTotal` are automatically updated after each operation that can influence their values.

####### Items management[¶](#items-management "Permalink to this headline")

The collection of items (Implementing the `Doctrine\Common\Collections\Collection` interface) can be obtained using the `->getItems()`.

To add or remove items, you can simply use the `addItem` and `removeItem` methods.

```

<?php

// $item1 and $item2 are instances of OrderItemInterface.

$order

    ->addItem($item)

    ->removeItem($item2)

;

```

####### OrderItem basics[¶](#orderitem-basics "Permalink to this headline")

An order item model has only the id as identifier, also it has the order to which it belongs, accessible via `->getOrder()` method.

The sellable object can be retrieved and set, using the following setter and getter - `->getProduct()` & `->setVariant(ProductVariantInterface $variant)`.

```

<?php

$item->setVariant($book);

```

Note

In most cases you’ll use the **OrderBuilder** service to create your orders.

Just like for the order, the total is available via the same method, but the unit price is accessible using the `->getUnitPrice()`

Each item also can calculate its total, using the quantity (`->getQuantity()`) and the unit price.

Warning

Concept of `OrderItemUnit` allows better management of `OrderItem`’s quantity. Because of that, it’s needed to use [OrderItemQuantityModifier](index.html#bundle-order-order-item-quantity-modifier) to handle

quantity modification properly.

```

<?php

$item = $itemRepository->createNew();

$item->setVariant($book);

$item->setUnitPrice(2000);

$orderItemQuantityModifier->modify($item, 4); //modifies item's quantity to 4

echo $item->getTotal(); // 8000.

```

An OrderItem can also hold adjustments.

####### Units management[¶](#units-management "Permalink to this headline")

Each element from `units` collection in `OrderItem` represents single, separate unit from order. It’s total is sum of its `item` unit price and totals’ of each adjustments. Unit’s can be added

and removed using `addUnit` and `removeUnit` methods from `OrderItem`, but it’s highly recommended to use [OrderItemQuantityModifier](index.html#bundle-order-order-item-quantity-modifier).

###### The Adjustments[¶](#the-adjustments "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

Adjustments are based on simple but powerful idea inspired by [Spree adjustments](https://dev-docs.spreecommerce.org/internals/adjustments).

They serve as foundation for any tax, shipping and discounts systems.

####### Adjustment model[¶](#adjustment-model "Permalink to this headline")

Note

To be written. Learn more in [the Book](index.html#document-book/orders/adjustments).

###### Using the services[¶](#using-the-services "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

When using the bundle, you have access to several handy services.

You can use them to retrieve and persist orders.

####### Managers and Repositories[¶](#managers-and-repositories "Permalink to this headline")

Note

Sylius uses `Doctrine\Common\Persistence` interfaces.

You have access to following services which are used to manage and retrieve resources.

This set of default services is shared across almost all Sylius bundles, but this is just a convention.

You’re interacting with them like you usually do with own entities in your project.

```

<?php

// ObjectManager which is capable of managing the resources.

// For \*doctrine/orm\* driver it will be EntityManager.

$this->get('sylius.manager.order');

$this->get('sylius.manager.order\_item');

$this->get('sylius.manager.order\_item\_unit');

$this->get('sylius.manager.adjustment');

// ObjectRepository for the Order resource, it extends the base EntityRepository.

// You can use it like usual entity repository in project.

$this->get('sylius.repository.order');

$this->get('sylius.repository.order\_item');

$this->get('sylius.repository.order\_item\_unit');

$this->get('sylius.repository.adjustment');

// Those repositories have some handy default methods, for example...

$item = $itemRepository->createNew();

$orderRepository->find(4);

$paginator = $orderRepository->createPaginator(array('confirmed' => false)); // Get Pagerfanta instance for all unconfirmed orders.

```

####### OrderItemQuantityModifier[¶](#orderitemquantitymodifier "Permalink to this headline")

`OrderItemQuantityModifier` should be used to modify `OrderItem` quantity, because of whole background units’ logic,

that needs to be done. This service handles this task, adding and removing proper amounts of units to `OrderItem`.

```

<?php

$orderItemFactory = $this->get('sylius.factory.order\_item');

$orderItemQuantityModifier = $this->get('sylius.order\_item\_quantity\_modifier');

$orderItem = $orderItemFactory->createNew();

$orderItem->getQuantity(); // default quantity of order item is 0

$orderItem->setUnitPrice(1000);

$orderItemQuantityModifier->modify($orderItem, 4);

$orderItem->getQuantity(); // after using modifier, quantity is as expected

$orderItem->getTotal();    // item's total is sum of all units' total (units have been created by modifier)

$orderItemQuantityModifier->modify($orderItem, 2);

// OrderItemQuantityModifier can also reduce item's quantity and remove unnecessary units

$orderItem->getQuantity(); // 2

$orderItem->getTotal();    // 2000

```

###### Summary[¶](#summary "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### Configuration reference[¶](#configuration-reference "Permalink to this headline")

```

sylius\_order:

 driver: doctrine/orm

 resources:

 order:

 classes:

 model: Sylius\Component\Core\Model\Order

 controller: Sylius\Bundle\CoreBundle\Controller\OrderController

 repository: Sylius\Bundle\CoreBundle\Doctrine\ORM\OrderRepository

 form: Sylius\Bundle\CoreBundle\Form\Type\Order\OrderType

 interface: Sylius\Component\Order\Model\OrderInterface

 factory: Sylius\Component\Resource\Factory\Factory

 order\_item:

 classes:

 model: Sylius\Component\Core\Model\OrderItem

 form: Sylius\Bundle\CoreBundle\Form\Type\Order\OrderItemType

 interface: Sylius\Component\Order\Model\OrderItemInterface

 controller: Sylius\Bundle\OrderBundle\Controller\OrderItemController

 factory: Sylius\Component\Resource\Factory\Factory

 order\_item\_unit:

 classes:

 model: Sylius\Component\Core\Model\OrderItemUnit

 interface: Sylius\Component\Order\Model\OrderItemUnitInterface

 controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController

 factory: Sylius\Component\Order\Factory\OrderItemUnitFactory

 adjustment:

 classes:

 model: Sylius\Component\Order\Model\Adjustment

 interface: Sylius\Component\Order\Model\AdjustmentInterface

 controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController

 factory: Sylius\Component\Resource\Factory\Factory

 order\_sequence:

 classes:

 model: Sylius\Component\Order\Model\OrderSequence

 interface: Sylius\Component\Order\Model\OrderSequenceInterface

 factory: Sylius\Component\Resource\Factory\Factory

 expiration:

 cart: '2 days'

 order: '5 days'

```

####### Bug tracking[¶](#bug-tracking "Permalink to this headline")

This bundle uses [GitHub issues](https://github.com/Sylius/Sylius/issues).

If you have found bug, please create an issue.

###### Processors[¶](#processors "Permalink to this headline")

Order processors are responsible of manipulating the orders to apply different predefined adjustments or other modifications based on order state.

####### Registering custom processors[¶](#registering-custom-processors "Permalink to this headline")

Once you have your own [OrderProcessorInterface](index.html#component-order-processors-order-processor-interface) implementation, if services autowiring and auto-configuration are not enabled, you need to register it as a service.

```

<?xml version="1.0" encoding="UTF-8"?>

<container xmlns="http://symfony.com/schema/dic/services"

 xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"

 xsi:schemaLocation="http://symfony.com/schema/dic/services

 https://symfony.com/schema/dic/services/services-1.0.xsd">

 <services>

 <service id="app.order\_processor.custom" class="App\OrderProcessor\CustomOrderProcessor">

 <tag name="sylius.order\_processor" priority="0" />

 </service>

 </services>

</container>

```

Note

You can add your own processor to the [CompositeOrderProcessor](index.html#component-order-processors-composite-order-processor) using sylius.order\_processor

Note

If services autoconfiguration is enabled, you should register your own processor by adding the `Sylius\Bundle\OrderBundle\Attribute\AsOrderProcessor` attribute

on the top of the processor class.

```

<?php

namespace App\OrderProcessor;

use Sylius\Bundle\OrderBundle\Attribute\AsOrderProcessor;

use Sylius\Component\Order\Model\OrderInterface;

use Sylius\Component\Order\Processor\OrderProcessorInterface;

#[AsOrderProcessor(priority: 10)] //priority is optional

//#[AsOrderProcessor] can be used as well

final class CustomOrderProcessor implements OrderProcessorInterface

{

    public function process(OrderInterface $order): void

    {

         // ...

    }

}

```

Then you should enable autoconfiguring with attributes in your `config/packages/\_sylius.yaml` file:

```

sylius\_order:

 autoconfigure\_with\_attributes: true

```

####### Using CompositeOrderProcessor[¶](#using-compositeorderprocessor "Permalink to this headline")

All processor services containing sylius.order\_processor tag can be launched as follows:

In a controller:

```

<?php

// Fetch order from DB

$order = $this->container->get('sylius.repository.order')->find('$id');

// Get the processor from the container or inject the service

$orderProcessor = $this->container->get('sylius.order\_processing.order\_processor');

$orderProcessor->process($order);

```

Note

The CompositeOrderProcessor is named as ` sylius.order\_processing.order\_processor` in the container and contains all services tagged as sylius.order\_processor

###### Learn more[¶](#learn-more "Permalink to this headline")

* [Carts & Orders in the Sylius platform](index.html#document-book/orders/index) - concept documentation

##### SyliusProductBundle[¶](#syliusproductbundle "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

###### Installation[¶](#installation "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

We assume you’re familiar with [Composer](https://packagist.org), a dependency manager for PHP.

Use the following command to add the bundle to your composer.json and download the package.

If you have [Composer installed globally](https://getcomposer.org/doc/00-intro.md#globally).

```

composer require sylius/product-bundle

```

Otherwise you have to download .phar file.

```

curl -sS https://getcomposer.org/installer | php

php composer.phar require sylius/product-bundle

```

####### Adding required bundles to the kernel[¶](#adding-required-bundles-to-the-kernel "Permalink to this headline")

You need to enable the bundle inside the kernel.

If you’re not using any other Sylius bundles, you will also need to add SyliusResourceBundle and its dependencies to kernel.

Don’t worry, everything was automatically installed via Composer.

```

<?php

// app/AppKernel.php

public function registerBundles()

{

    $bundles = array(

        new FOS\RestBundle\FOSRestBundle(),

        new JMS\SerializerBundle\JMSSerializerBundle(),

        new Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle(),

        new BabDev\PagerfantaBundle\BabDevPagerfantaBundle(),

        new winzou\Bundle\StateMachineBundle\winzouStateMachineBundle(),

        new Sylius\Bundle\ProductBundle\SyliusProductBundle(),

        new Sylius\Bundle\AttributeBundle\SyliusAttributeBundle(),

        new Sylius\Bundle\ResourceBundle\SyliusResourceBundle(),

        new Sylius\Bundle\LocaleBundle\SyliusLocaleBundle(),

        // Other bundles...

        new Doctrine\Bundle\DoctrineBundle\DoctrineBundle(),

    );

}

```

####### Container configuration[¶](#container-configuration "Permalink to this headline")

Put this configuration inside your `app/config/config.yml`.

```

stof\_doctrine\_extensions:

 orm:

 default:

 sluggable: true

 timestampable: true

```

####### Updating database schema[¶](#updating-database-schema "Permalink to this headline")

Run the following command.

```

php bin/console doctrine:schema:update --force

```

Warning

This should be done only in **dev** environment! We recommend using Doctrine migrations, to safely update your schema.

Congratulations! The bundle is now installed and ready to use.

###### The Product[¶](#the-product "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### Retrieving products[¶](#retrieving-products "Permalink to this headline")

Retrieving a product from the database should always happen via repository, which always implements `Sylius\Bundle\ResourceBundle\Model\RepositoryInterface`.

If you are using Doctrine, you’re already familiar with this concept, as it extends the native Doctrine `ObjectRepository` interface.

Your product repository is always accessible via the `sylius.repository.product` service.

```

<?php

public function myAction(Request $request)

{

    $repository = $this->container->get('sylius.repository.product');

}

```

Retrieving products is simple as calling proper methods on the repository.

```

<?php

public function myAction(Request $request)

{

    $repository = $this->container->get('sylius.repository.product');

    $product = $repository->find(4); // Get product with id 4, returns null if not found.

    $product = $repository->findOneBy(array('slug' => 'my-super-product')); // Get one product by defined criteria.

    $products = $repository->findAll(); // Load all the products!

    $products = $repository->findBy(array('special' => true)); // Find products matching some custom criteria.

}

```

Product repository also supports paginating products. To create a [Pagerfanta instance](https://github.com/whiteoctober/Pagerfanta) use the `createPaginator` method.

```

<?php

public function myAction(Request $request)

{

    $repository = $this->container->get('sylius.repository.product');

    $products = $repository->createPaginator();

    $products->setMaxPerPage(3);

    $products->setCurrentPage($request->query->get('page', 1));

   // Now you can return products to the template and iterate over it to get products from the current page.

}

```

The paginator also can be created for specific criteria and with desired sorting.

```

<?php

public function myAction(Request $request)

{

    $repository = $this->container->get('sylius.repository.product');

    $products = $repository->createPaginator(array('foo' => true), array('createdAt' => 'desc'));

    $products->setMaxPerPage(3);

    $products->setCurrentPage($request->query->get('page', 1));

}

```

####### Creating new product object[¶](#creating-new-product-object "Permalink to this headline")

To create new product instance, you can simply call `createNew()` method on the factory.

```

<?php

public function myAction(Request $request)

{

    $factory = $this->container->get('sylius.factory.product');

    $product = $factory->createNew();

}

```

Note

Creating a product via this factory method makes the code more testable, and allows you to change the product class easily.

####### Saving & removing product[¶](#saving-removing-product "Permalink to this headline")

To save or remove a product, you can use any `ObjectManager` which manages Product. You can always access it via alias `sylius.manager.product`.

But it’s also perfectly fine if you use `doctrine.orm.entity\_manager` or other appropriate manager service.

```

<?php

public function myAction(Request $request)

{

    $factory = $this->container->get('sylius.factory.product');

    $manager = $this->container->get('sylius.manager.product'); // Alias for appropriate doctrine manager service.

    $product = $factory->createNew();

    $product

        ->setName('Foo')

        ->setDescription('Nice product')

    ;

    $manager->persist($product);

    $manager->flush(); // Save changes in database.

}

```

To remove a product, you also use the manager.

```

<?php

public function myAction(Request $request)

{

    $repository = $this->container->get('sylius.repository.product');

    $manager = $this->container->get('sylius.manager.product');

    $product = $repository->find(1);

    $manager->remove($product);

    $manager->flush(); // Save changes in database.

}

```

####### Properties[¶](#properties "Permalink to this headline")

A product can also have a set of defined Properties ([Attributes](index.html#document-book/products/attributes)), you’ll learn about them in next chapter of this documentation.

###### Forms[¶](#forms "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

The bundle ships with a set of useful form types for all models. You can use the defaults or [override them](index.html#document-customization/form) with your own forms.

####### Product form[¶](#product-form "Permalink to this headline")

The product form type is named `sylius\_product` and you can create it whenever you need, using the form factory.

```

<?php

// src/Acme/ShopBundle/Controller/ProductController.php

namespace Acme\ShopBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;

use Symfony\Component\HttpFoundation\Request;

class DemoController extends Controller

{

    public function fooAction(Request $request)

    {

        $form = $this->get('form.factory')->create('sylius\_product');

    }

}

```

The default product form consists of following fields.

| Field | Type |

| --- | --- |

| name | text |

| description | textarea |

| metaDescription | text |

| metaKeywords | text |

You can render each of these using the usual Symfony way `{{ form\_row(form.description) }}`.

###### Summary[¶](#summary "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### Configuration reference[¶](#configuration-reference "Permalink to this headline")

```

sylius\_product:

 driver: doctrine/orm

 resources:

 product:

 classes:

 model: Sylius\Component\Core\Model\Product

 repository: Sylius\Bundle\CoreBundle\Doctrine\ORM\ProductRepository

 form: Sylius\Bundle\CoreBundle\Form\Type\Product\ProductType

 interface: Sylius\Component\Product\Model\ProductInterface

 controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController

 factory: Sylius\Component\Product\Factory\ProductFactory

 translation:

 classes:

 model: Sylius\Component\Core\Model\ProductTranslation

 form: Sylius\Bundle\CoreBundle\Form\Type\Product\ProductTranslationType

 interface: Sylius\Component\Product\Model\ProductTranslationInterface

 controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController

 factory: Sylius\Component\Resource\Factory\Factory

 product\_variant:

 classes:

 model: Sylius\Component\Core\Model\ProductVariant

 repository: Sylius\Bundle\ProductBundle\Doctrine\ORM\ProductVariantRepository

 form: Sylius\Bundle\CoreBundle\Form\Type\Product\ProductVariantType

 interface: Sylius\Component\Product\Model\ProductVariantInterface

 controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController

 factory: Sylius\Component\Product\Factory\ProductVariantFactory

 product\_option:

 classes:

 repository: Sylius\Bundle\ProductBundle\Doctrine\ORM\ProductOptionRepository

 model: Sylius\Component\Product\Model\ProductOption

 interface: Sylius\Component\Product\Model\ProductOptionInterface

 controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController

 factory: Sylius\Component\Resource\Factory\TranslatableFactory

 form: Sylius\Bundle\ProductBundle\Form\Type\ProductOptionType

 translation:

 classes:

 model: Sylius\Component\Product\Model\ProductOptionTranslation

 interface: Sylius\Component\Product\Model\ProductOptionTranslationInterface

 controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController

 factory: Sylius\Component\Resource\Factory\Factory

 form: Sylius\Bundle\ProductBundle\Form\Type\ProductOptionTranslationType

 product\_option\_value:

 classes:

 model: Sylius\Component\Product\Model\ProductOptionValue

 interface: Sylius\Component\Product\Model\ProductOptionValueInterface

 controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController

 factory: Sylius\Component\Resource\Factory\TranslatableFactory

 form: Sylius\Bundle\ProductBundle\Form\Type\ProductOptionValueType

 translation:

 classes:

 model: Sylius\Component\Product\Model\ProductOptionValueTranslation

 interface: Sylius\Component\Product\Model\ProductOptionValueTranslationInterface

 controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController

 factory: Sylius\Component\Resource\Factory\Factory

 form: Sylius\Bundle\ProductBundle\Form\Type\ProductOptionValueTranslationType

 product\_association:

 classes:

 model: Sylius\Component\Product\Model\ProductAssociation

 interface: Sylius\Component\Product\Model\ProductAssociationInterface

 controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController

 factory: Sylius\Component\Resource\Factory\Factory

 form: Sylius\Bundle\ProductBundle\Form\Type\ProductAssociationType

 product\_association\_type:

 classes:

 model: Sylius\Component\Product\Model\ProductAssociationType

 interface: Sylius\Component\Product\Model\ProductAssociationTypeInterface

 controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController

 factory: Sylius\Component\Resource\Factory\Factory

 form: Sylius\Bundle\ProductBundle\Form\Type\ProductAssociationTypeType

```

####### Bug tracking[¶](#bug-tracking "Permalink to this headline")

This bundle uses [GitHub issues](https://github.com/Sylius/Sylius/issues).

If you have found bug, please create an issue.

###### Learn more[¶](#learn-more "Permalink to this headline")

* [Products in the Sylius platform](index.html#document-book/products/index) - concept documentation

##### SyliusPromotionBundle[¶](#syliuspromotionbundle "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

Promotions system for Symfony applications.

With minimal configuration you can introduce promotions and coupons into your project. The following types of promotions are available and **totally mixable**:

* percentage discounts

* fixed amount discounts

* promotions limited by time

* promotions limited by a maximum number of usages

* promotions based on coupons

This means you can for instance create the following promotions :

* 20$ discount for New Year orders having more than 3 items

* 8% discount for Christmas orders over 100 EUR

* first 3 orders have 100% discount

* 5% discount this week with the coupon code *WEEK5*

* 40€ discount with the code you have received by mail

###### Installation[¶](#installation "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

We assume you’re familiar with [Composer](https://packagist.org), a dependency manager for PHP.

Use the following command to add the bundle to your `composer.json` and download the package.

If you have [Composer installed globally](https://getcomposer.org/doc/00-intro.md#globally).

```

composer require sylius/promotion-bundle

```

Otherwise you have to download .phar file.

```

curl -sS https://getcomposer.org/installer | php

php composer.phar require sylius/promotion-bundle

```

####### Adding required bundles to the kernel[¶](#adding-required-bundles-to-the-kernel "Permalink to this headline")

You need to enable the bundle inside the kernel.

If you’re not using any other Sylius bundles, you will also need to add `SyliusResourceBundle` and its dependencies to kernel.

Don’t worry, everything was automatically installed via Composer.

```

<?php

// app/AppKernel.php

public function registerBundles()

{

    $bundles = array(

        new FOS\RestBundle\FOSRestBundle(),

        new JMS\SerializerBundle\JMSSerializerBundle(),

        new Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle(),

        new BabDev\PagerfantaBundle\BabDevPagerfantaBundle(),

        new winzou\Bundle\StateMachineBundle\winzouStateMachineBundle(),

        new Sylius\Bundle\ResourceBundle\SyliusResourceBundle(),

        new Sylius\Bundle\PromotionBundle\SyliusPromotionBundle(),

        // Other bundles...

        new Doctrine\Bundle\DoctrineBundle\DoctrineBundle(),

    );

}

```

####### Promotion Subject configuration[¶](#promotion-subject-configuration "Permalink to this headline")

Note

You need to have a class that is [registered as a Sylius resource](https://github.com/Sylius/SyliusResourceBundle/blob/master/docs/index.md).

It can be for example a `CarRentalOrderClass`.

* Make your `CarRentalOrder` class implement the `PromotionSubjectInterface`.

Put its configuration inside your `app/config/config.yml`.

```

# app/config/config.yml

sylius\_promotion:

 resources:

 promotion\_subject:

 classes:

 model: App\Entity\CarRentalOrder

```

And configure doctrine extensions which are used by the bundle.

```

# app/config/config.yml

stof\_doctrine\_extensions:

 orm:

 default:

 timestampable: true

```

Congratulations! The bundle is now installed and ready to use.

###### Models[¶](#models "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

All the models of this bundle are defined in `Sylius\Component\Promotion\Model`.

####### PromotionRule[¶](#promotionrule "Permalink to this headline")

A `PromotionRule` is used to check if your order is eligible to the promotion. A promotion can have none, one or several rules. `SyliusPromotionBundle` comes with 2 types of rules :

> 

> * cart quantity rule : quantity of the order is checked

> * item total rule : the amount of the order is checked

> 

> 

> 

A rule is configured via the `configuration` attribute which is an array serialized into database. For cart quantity rules, you have to configure the `count` key, whereas the `amount` key is used for item total rules.

Configuration is always strict, which means, that if you set `count` to **4** for cart quantity rule, orders with equal or more than **4** quantity will be eligible.

####### PromotionAction[¶](#promotionaction "Permalink to this headline")

An `PromotionAction` defines the nature of the discount. Common actions are :

* percentage discount

* fixed amount discount

An action is configured via the `configuration` attribute which is an array serialized into database. For percentage discount actions, you have to configure the `percentage` key, whereas the `amount` key is used for fixed discount rules.

####### PromotionCoupon[¶](#promotioncoupon "Permalink to this headline")

A `PromotionCoupon` is a ticket having a `code` that can be exchanged for a financial discount. A promotion can have none, one or several coupons.

A coupon is considered as valid if the method `isValid()` returns `true`. This method checks the number of times this coupon can be used (attribute `usageLimit`), the number of times this has already been used (attribute `used`) and the coupon expiration date (attribute `expiresAt`). If `usageLimit` is not set, the coupon will be usable an unlimited times.

####### PromotionSubjectInterface[¶](#promotionsubjectinterface "Permalink to this headline")

A `PromotionSubjectInterface` is the object you want to apply the promotion on. For instance, in Sylius Standard, a `Sylius\Component\Core\Model\Order` can be subject to promotions.

By implementing `PromotionSubjectInterface`, your object will have to define the following methods :

- `getPromotionSubjectItemTotal()` should return the amount of your order

- `getPromotionSubjectItemCount()` should return the number of items of your order

- `getPromotionCoupon()` should return the coupon linked to your order. If you do not want to use coupon, simply return `null`.

####### Promotion[¶](#promotion "Permalink to this headline")

The `Promotion` is the main model of this bundle. A promotion has a `name`, a `description` and :

* can have none, one or several rules

* should have at least one action to be effective

* can be based on coupons

* can have a limited number of usages by using the attributes `usageLimit` and `used`. When `used` reaches `usageLimit` the promotion is no longer valid. If `usageLimit` is not set, the promotion will be usable an unlimited times.

* can be limited by time by using the attributes `startsAt` and `endsAt`

###### How are rules checked?[¶](#how-are-rules-checked "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

Everything related to this subject is located in `Sylius\Component\Promotion\Checker`.

####### Rule checkers[¶](#rule-checkers "Permalink to this headline")

New rules can be created by implementing `RuleCheckerInterface`. This interface provides the method `isEligible` which aims to determine if the promotion subject respects the current rule or not.

I told you before that `SyliusPromotionBundle` ships with 2 types of rules : cart quantity rule and item total rule.

Cart quantity rule is defined via the service `sylius.promotion\_rule\_checker.cart\_quantity` which uses the class `CartQuantityRuleChecker`. The method `isEligible` checks here if the promotion subject has the minimum quantity (method `getPromotionSubjectItemCount()` of `PromotionSubjectInterface`) required by the rule.

Item total rule is defined via the service `sylius.promotion\_rule\_checker.item\_total` which uses the class `ItemTotalRuleChecker`. The method `isEligible` checks here if the promotion subject has the minimum amount (method `getPromotionSubjectItemTotal()` of `PromotionSubjectInterface`) required by the rule.

####### The promotion eligibility checker service[¶](#the-promotion-eligibility-checker-service "Permalink to this headline")

To be eligible to a promotion, a subject must :

1. respect all the rules related to the promotion

2. respect promotion dates if promotion is limited by time

3. respect promotions usages count if promotion has a limited number of usages

4. if a coupon is provided with this order, it must be valid and belong to this promotion

The service `sylius.promotion\_eligibility\_checker` checks all these constraints for you with the method `isEligible()` which returns `true` or `false`. This service uses the class `CompositePromotionEligibilityChecker`.

###### How actions are applied?[¶](#how-actions-are-applied "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

Everything related to this subject is located in `Sylius\Component\Promotion\Action`.

####### Actions[¶](#actions "Permalink to this headline")

Actions can be created by implementing `PromotionActionCommandInterface`. This interface provides the method `execute` which aim is to apply a promotion to its subject. It also provides the method `getConfigurationFormType` which has to return the form name related to this action.

Actions have to be defined as services and have to use the tag named `sylius.promotion\_action` with the attributes `type` and `label`.

As `SyliusPromotionBundle` is totally independent, it does not provide actions out of the box.

Note

`Sylius\Component\Core\Promotion\Action\FixedDiscountPromotionActionCommand` from `Sylius/Sylius-Standard` is an example of action for a fixed amount discount. The related service is called `sylius.promotion\_action.fixed\_discount`.

Note

`Sylius\Component\Core\Promotion\Action\PercentageDiscountPromotionActionCommand` from `Sylius/Sylius-Standard` is an example of action for a discount based on percentage. The related service is called `sylius.promotion\_action.percentage\_discount`.

Learn more about actions in the [cart promotions concept documentation](index.html#document-book/orders/cart-promotions) and in the [Cookbook](index.html#document-cookbook/index).

####### Applying actions to promotions[¶](#applying-actions-to-promotions "Permalink to this headline")

We have seen above how actions can be created. Now let’s see how they are applied to their subject.

The `PromotionApplicator` is responsible of this via its method `apply`. This method will `execute` all the registered actions of a promotion on a subject.

###### How promotions are applied?[¶](#how-promotions-are-applied "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

By using the [promotion eligibility checker](index.html#document-components_and_bundles/bundles/SyliusPromotionBundle/rule_checker) and the [promotion applicator checker](index.html#document-components_and_bundles/bundles/SyliusPromotionBundle/action_applicator) services, the promotion processor applies all the possible promotions on a subject.

The promotion processor is defined via the service `sylius.promotion\_processor` which uses the class `Sylius\Component\Promotion\Processor\PromotionProcessor`. Basically, it calls the method `apply` of the promotion applicator for all the active promotions that are eligible to the given subject.

###### Coupon based promotions[¶](#coupon-based-promotions "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

Coupon based promotions require special needs that are covered by this documentation.

####### Coupon generator[¶](#coupon-generator "Permalink to this headline")

`SyliusPromotionBundle` provides a way of generating coupons for a promotion : the coupon generator.

Provided as a service `sylius.promotion\_coupon\_generator` via the class `Sylius\Component\Promotion\Generator\PromotionCouponGenerator`, its goal is to generate unique coupon codes.

####### PromotionCoupon controller[¶](#promotioncoupon-controller "Permalink to this headline")

The `Sylius\Bundle\PromotionBundle\Controller\PromotionCouponController` provides a method for generating new coupons.

###### Summary[¶](#summary "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

```

sylius\_promotion:

 driver: doctrine/orm

 resources:

 promotion\_subject:

 classes:

 model: Sylius\Component\Core\Model\Order

 promotion:

 classes:

 model: Sylius\Component\Promotion\Model\Promotion

 interface: Sylius\Component\Promotion\Model\PromotionInterface

 controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController

 repository: ~

 factory: Sylius\Component\Resource\Factory\Factory

 form: Sylius\Bundle\PromotionBundle\Form\Type\PromotionType

 promotion\_rule:

 classes:

 factory: Sylius\Component\Core\Factory\PromotionRuleFactory

 model: Sylius\Component\Promotion\Model\PromotionRule

 interface: Sylius\Component\Promotion\Model\PromotionRuleInterface

 controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController

 repository: ~

 form: Sylius\Bundle\PromotionBundle\Form\Type\PromotionRuleType

 promotion\_coupon:

 classes:

 model: Sylius\Component\Promotion\Model\PromotionAction

 interface: Sylius\Component\Promotion\Model\PromotionActionInterface

 controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController

 repository: ~

 factory: Sylius\Component\Resource\Factory\Factory

 form: Sylius\Bundle\PromotionBundle\Form\Type\PromotionActionType

 promotion\_action:

 classes:

 model: Sylius\Component\Promotion\Model\Coupon

 interface: Sylius\Component\Promotion\Model\CouponInterface

 controller: Sylius\Bundle\PromotionBundle\Controller\PromotionCouponController

 repository: ~

 factory: Sylius\Component\Resource\Factory\Factory

 form: Sylius\Bundle\PromotionBundle\Form\Type\PromotionActionType

```

####### Bug tracking[¶](#bug-tracking "Permalink to this headline")

This bundle uses [GitHub issues](https://github.com/Sylius/Sylius/issues).

If you have found bug, please create an issue.

###### Learn more[¶](#learn-more "Permalink to this headline")

* [Cart Promotions in the Sylius platform](index.html#document-book/orders/cart-promotions) - concept documentation

##### SyliusShippingBundle[¶](#syliusshippingbundle "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

**SyliusShippingBundle** is the shipment management component for Symfony e-commerce applications.

If you need to manage shipments, shipping methods and deal with complex cost calculation, this bundle can

help you a lot!

Your products or whatever you need to deliver, can be categorized under unlimited set of categories.

You can display appropriate shipping methods available to the user, based on object category, weight, dimensions and anything you can imagine.

Flexible shipping cost calculation system allows you to create your own calculator services.

###### Installation[¶](#installation "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

We assume you’re familiar with [Composer](https://packagist.org), a dependency manager for PHP.

Use the following command to add the bundle to your composer.json and download package.

If you have [Composer installed globally](https://getcomposer.org/doc/00-intro.md#globally).

```

composer require sylius/shipping-bundle

```

Otherwise you have to download .phar file.

```

curl -sS https://getcomposer.org/installer | php

php composer.phar require sylius/shipping-bundle

```

####### Adding required bundles to the kernel[¶](#adding-required-bundles-to-the-kernel "Permalink to this headline")

You need to enable the bundle inside the kernel.

If you’re not using any other Sylius bundles, you will also need to add SyliusResourceBundle and its dependencies.

Don’t worry, everything was automatically installed via Composer.

```

<?php

// app/AppKernel.php

public function registerBundles()

{

    $bundles = array(

        new winzou\Bundle\StateMachineBundle\winzouStateMachineBundle(),

        new FOS\RestBundle\FOSRestBundle(),

        new JMS\SerializerBundle\JMSSerializerBundle(),

        new Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle(),

        new BabDev\PagerfantaBundle\BabDevPagerfantaBundle(),

        new Sylius\Bundle\ShippingBundle\SyliusShippingBundle(),

        new Sylius\Bundle\ResourceBundle\SyliusResourceBundle(),

        // Other bundles...

        new Doctrine\Bundle\DoctrineBundle\DoctrineBundle(),

    );

}

```

####### Container configuration[¶](#container-configuration "Permalink to this headline")

Put this configuration inside your `app/config/config.yml`.

```

sylius\_shipping:

 driver: doctrine/orm # Configure the Doctrine ORM driver used in documentation.

```

Configure doctrine extensions which are used by this bundle.

```

stof\_doctrine\_extensions:

 orm:

 default:

 timestampable: true

```

####### Routing configuration[¶](#routing-configuration "Permalink to this headline")

Add the following to your `config/routes.yaml`.

```

sylius\_shipping:

 resource: "@SyliusShipping/Resources/config/routing.yml"

```

####### Updating database schema[¶](#updating-database-schema "Permalink to this headline")

Run the following command.

```

php bin/console doctrine:schema:update --force

```

Warning

This should be done only in **dev** environment! We recommend using Doctrine migrations, to safely update your schema.

###### The ShippableInterface[¶](#the-shippableinterface "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

In order to handle your merchandise through the Sylius shipping engine, your models need to implement **ShippableInterface**.

####### Implementing the interface[¶](#implementing-the-interface "Permalink to this headline")

Let’s assume that you have a **Book** entity in your application.

First step is to implement the simple interface, which contains few simple methods.

```

namespace Acme\Bundle\ShopBundle\Entity;

use Sylius\Component\Shipping\Model\ShippableInterface;

use Sylius\Component\Shipping\Model\ShippingCategoryInterface;

class Book implements ShippableInterface

{

    private $shippingCategory;

    public function getShippingCategory()

    {

        return $this->shippingCategory;

    }

    public function setShippingCategory(ShippingCategoryInterface $shippingCategory) // This method is not required.

    {

        $this->shippingCategory = $shippingCategory;

        return $this;

    }

    public function getShippingWeight()

    {

        // return integer representing the object weight.

    }

    public function getShippingWidth()

    {

        // return integer representing the book width.

    }

    public function getShippingHeight()

    {

        // return integer representing the book height.

    }

    public function getShippingDepth()

    {

        // return integer representing the book depth.

    }

}

```

Second and last task is to define the relation inside `Resources/config/doctrine/Book.orm.xml` of your bundle.

```

<?xml version="1.0" encoding="UTF-8"?>

<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping"

 xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"

 xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping

 https://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">

 <entity name="Acme\ShopBundle\Entity\Book" table="acme\_book">

 <!-- your mappings... -->

 <many-to-one field="shippingCategory" target-entity="Sylius\Bundle\ShippingBundle\Model\ShippingCategoryInterface">

 <join-column name="shipping\_category\_id" referenced-column-name="id" nullable="false" />

 </many-to-one>

 </entity>

</doctrine-mapping>

```

Done! Now your **Book** model can be used in Sylius shippingation engine.

####### Forms[¶](#forms "Permalink to this headline")

If you want to add a shipping category selection field to your model form, simply use the `sylius\_shipping\_category\_choice` type.

```

namespace Acme\ShopBundle\Form\Type;

use Symfony\Component\Form\FormBuilderInterface;

use Symfony\Component\Form\AbstractType;

class BookType extends AbstractType

{

    public function buildForm(FormBuilderInterface $builder, array $options)

    {

        $builder

            ->add('title', 'text')

            ->add('shippingCategory', 'sylius\_shipping\_category\_choice')

        ;

    }

}

```

###### The ShippingSubjectInterface[¶](#the-shippingsubjectinterface "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

The find available shipping methods or calculate shipping cost you need to use object implementing `ShippingSubjectInterface`.

The default **Shipment** model is already implementing `ShippingSubjectInterface`.

####### Interface methods[¶](#interface-methods "Permalink to this headline")

* The `getShippingMethod` returns a `ShippingMethodInterface` instance, representing the method.

* The `getShippingItemCount` provides you with the count of items to ship.

* The `getShippingItemTotal` returns the total value of shipment, if applicable. The default **Shipment** model returns 0.

* The `getShippingWeight` returns the total shipment weight.

* The `getShippables` returns a collection of unique `ShippableInterface` instances.

###### The Shipping Categories[¶](#the-shipping-categories "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

Every shippable object needs to have a shipping category assigned. The **ShippingCategory** model is extremely simple and described below.

| Attribute | Description |

| --- | --- |

| id | Unique id of the shipping category |

| name | Name of the shipping category |

| description | Human friendly description of the classification |

| createdAt | Date when the category was created |

| updatedAt | Date of the last shipping category update |

###### The Shipping Method[¶](#the-shipping-method "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

**ShippingMethod** model represents the way that goods need to be shipped. An example of shipping method may be “DHL Express” or “FedEx World Shipping”.

| Attribute | Description |

| --- | --- |

| id | Unique id of the shipping method |

| name | Name of the shipping method |

| category | Reference to **ShippingCategory** (optional) |

| categoryRequirement | Category requirement |

| calculator | Name of the cost calculator |

| configuration | Configuration for the calculator |

| createdAt | Date when the method was created |

| updatedAt | Date of the last shipping method update |

###### Calculating shipping cost[¶](#calculating-shipping-cost "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

Calculating shipping cost is as simple as using the `sylius.shipping\_calculator` service and calling `calculate` method on `ShippingSubjectInterface`.

Let’s calculate the cost of existing shipment.

```

public function myAction()

{

    $calculator = $this->get('sylius.shipping\_calculator');

    $shipment = $this->get('sylius.repository.shipment')->find(5);

    echo $calculator->calculate($shipment); // Returns price in cents. (integer)

}

```

What has happened?

* The delegating calculator gets the **ShippingMethod** from the **ShippingSubjectInterface** (Shipment).

* Appropriate **Calculator** instance is loaded, based on the **ShippingMethod.calculator** parameter.

* The `calculate(ShippingSubjectInterface, array $configuration)` is called, where configuration is taken from **ShippingMethod.configuration** attribute.

###### Default calculators[¶](#default-calculators "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

Default calculators can be sufficient solution for many use cases.

####### Flat rate[¶](#flat-rate "Permalink to this headline")

The `flat\_rate` calculator, charges concrete amount per shipment.

####### Per item rate[¶](#per-item-rate "Permalink to this headline")

The `per\_item\_rate` calculator, charges concrete amount per shipment item.

####### More calculators[¶](#more-calculators "Permalink to this headline")

Depending on community contributions and Sylius resources, more default calculators can be implemented, for example `weight\_range\_rate`.

###### Custom calculators[¶](#custom-calculators "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

Sylius ships with several default calculators, but you can easily register your own.

####### Simple calculators[¶](#simple-calculators "Permalink to this headline")

All shipping cost calculators implement `CalculatorInterface`. In our example we’ll create a calculator which calls an external API to obtain the shipping cost.

```

<?php

# src/Shipping/Calculator/DHLCalculator.php

declare(strict\_types=1);

namespace App\Shipping\Calculator;

use Sylius\Component\Shipping\Calculator\CalculatorInterface;

use Sylius\Component\Shipping\Model\ShipmentInterface;

final class DHLCalculator implements CalculatorInterface

{

    /\*\*

 \* @var DHLService

 \*/

    private $dhlService;

    /\*\*

 \* @param DHLService $dhlService

 \*/

    public function \_\_construct(DHLService $dhlService)

    {

        $this->dhlService = $dhlService;

    }

    /\*\*

 \* {@inheritdoc}

 \*/

    public function calculate(ShipmentInterface $subject, array $configuration): int

    {

        return $this->dhlService->getShippingCostForWeight($subject->getShippingWeight());

    }

    /\*\*

 \* {@inheritdoc}

 \*/

    public function getType(): string

    {

        return 'dhl';

    }

}

```

Now, you need to register your new service in container and tag it with `sylius.shipping\_calculator`.

```

services:

 app.shipping\_calculator.dhl:

 class: App\Shipping\Calculator\DHLCalculator

 arguments: ['@app.dhl\_service']

 tags:

 - { name: sylius.shipping\_calculator, calculator: dhl, label: "DHL" }

```

That would be all. This new option (“DHL”) will appear on the **ShippingMethod** creation form, in the “calculator” field.

####### Configurable calculators[¶](#configurable-calculators "Permalink to this headline")

You can also create configurable calculators, meaning that you can have several **ShippingMethod**’s using same type of calculator, with different settings.

Let’s modify the **DHLCalculator**, so that it charges 0 if shipping more than X items.

First step is to create a form type which will be displayed if our calculator is selected.

```

<?php

# src/Form/Type/Shipping/Calculator/DHLConfigurationType.php

declare(strict\_types=1);

namespace App\Form\Type\Shipping\Calculator;

use Symfony\Component\Form\AbstractType;

use Symfony\Component\Form\Extension\Core\Type\IntegerType;

use Symfony\Component\Form\FormBuilderInterface;

use Symfony\Component\OptionsResolver\OptionsResolver;

use Symfony\Component\Validator\Constraints\NotBlank;

use Symfony\Component\Validator\Constraints\Type;

final class DHLConfigurationType extends AbstractType

{

    /\*\*

 \* {@inheritdoc}

 \*/

    public function buildForm(FormBuilderInterface $builder, array $options): void

    {

        $builder

            ->add('limit', IntegerType::class, [

                'label' => 'Free shipping above total items',

                'constraints' => [

                    new NotBlank(),

                    new Type(['type' => 'integer']),

                ]

            ])

        ;

    }

    /\*\*

 \* {@inheritdoc}

 \*/

    public function configureOptions(OptionsResolver $resolver): void

    {

        $resolver

            ->setDefaults([

                'data\_class' => null,

                'limit' => 10,

            ])

            ->setAllowedTypes('limit', 'integer')

        ;

    }

    /\*\*

 \* {@inheritdoc}

 \*/

    public function getBlockPrefix(): string

    {

        return 'app\_shipping\_calculator\_dhl';

    }

}

```

We also need to register the form type in the container and set this form type in the definition of the calculator.

```

services:

 app.shipping\_calculator.dhl:

 class: App\Shipping\Calculator\DHLCalculator

 arguments: ['@app.dhl\_service']

 tags:

 - { name: sylius.shipping\_calculator, calculator: dhl, form\_type: App\Form\Type\Shipping\Calculator\DHLConfigurationType, label: "DHL" }

 app.form.type.shipping\_calculator.dhl:

 class: App\Form\Type\Shipping\Calculator\DHLConfigurationType

 tags:

 - { name: form.type }

```

Perfect, now we’re able to use the configuration inside the `calculate` method.

```

<?php

# src/Shipping/Calculator/DHLCalculator.php

declare(strict\_types=1);

namespace App\Shipping\Calculator;

use Sylius\Component\Shipping\Calculator\CalculatorInterface;

use Sylius\Component\Shipping\Model\ShipmentInterface;

final class DHLCalculator implements CalculatorInterface

{

    /\*\*

 \* @var DHLService

 \*/

    private $dhlService;

    /\*\*

 \* @param DHLService $dhlService

 \*/

    public function \_\_construct(DHLService $dhlService)

    {

        $this->dhlService = $dhlService;

    }

    /\*\*

 \* {@inheritdoc}

 \*/

    public function calculate(ShipmentInterface $subject, array $configuration): int

    {

        if ($subject->getShippingUnitCount() > $configuration['limit']) {

            return 0;

        }

        return $this->dhlService->getShippingCostForWeight($subject->getShippingWeight());

    }

    /\*\*

 \* {@inheritdoc}

 \*/

    public function getType(): string

    {

        return 'dhl';

    }

}

```

Your new configurable calculator is ready to use. When you select the “DHL” calculator in **ShippingMethod** form, configuration fields will appear automatically.

###### Resolving available shipping methods[¶](#resolving-available-shipping-methods "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

In many use cases, you want to decide which shipping methods are available for user.

Sylius has a dedicated service which serves this purpose.

####### ShippingMethodsResolver[¶](#shippingmethodsresolver "Permalink to this headline")

This service also works with the `ShippingSubjectInterface`. To get all shipping methods which support given subject, simply call the `getSupportedMethods` function.

```

public function myAction()

{

    $resolver = $this->get('sylius.shipping\_methods\_resolver');

    $shipment = $this->get('sylius.repository.shipment')->find(5);

    foreach ($resolver->getSupportedMethods($shipment) as $method) {

        echo $method->getName();

    }

}

```

You can also pass the criteria array to initially filter the shipping methods pool.

```

public function myAction()

{

    $country = $this->getUser()->getCountry();

    $resolver = $this->get('sylius.shipping\_methods\_resolver');

    $shipment = $this->get('sylius.repository.shipment')->find(5);

    foreach ($resolver->getSupportedMethods($shipment, array('country' => $country)) as $method) {

        echo $method->getName();

    }

}

```

####### In forms[¶](#in-forms "Permalink to this headline")

To display a select field with all the available methods for given subject, you can use the `sylius\_shipping\_method\_choice` type.

It supports two special options, required `subject` and optional `criteria`.

```

<?php

class ShippingController extends Controller

{

    public function selectMethodAction(Request $request)

    {

        $shipment = $this->get('sylius.repository.shipment')->find(5);

        $form = $this->get('form.factory')->create(ShippingMethodChoiceType::class, null, array('subject' => $shipment));

    }

}

```

This form type internally calls the **ShippingMethodsResolver** service and creates a list of available methods.

###### Shipping method requirements[¶](#shipping-method-requirements "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

Sylius has a very flexible system for displaying only the right shipping methods to the user.

####### Shipping categories[¶](#shipping-categories "Permalink to this headline")

Every **ShippableInterface** can hold a reference to **ShippingCategory**.

The **ShippingSubjectInterface** (or **ShipmentInterface**) returns a collection of shippables.

**ShippingMethod** has an optional shipping category setting as well as **categoryRequirement** which has 3 options.

If this setting is set to null, categories system is ignored.

######## “Match any” requirement[¶](#match-any-requirement "Permalink to this headline")

With this requirement, the shipping method will support any shipment (or shipping subject) which contains at least one shippable with the same category.

######## “Match all” requirement[¶](#match-all-requirement "Permalink to this headline")

All shippables have to reference the same category as the **ShippingMethod**.

######## “Match none” requirement[¶](#match-none-requirement "Permalink to this headline")

None of the shippables can have the same shipping category.

###### Summary[¶](#summary "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### Configuration Reference[¶](#configuration-reference "Permalink to this headline")

```

sylius\_shipping:

 # The driver used for persistence layer.

 driver: ~

 classes:

 shipment:

 classes:

 model: Sylius\Component\Shipping\Model\Shipment

 interface: Sylius\Component\Shipping\Model\ShipmentInterface

 controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController

 repository: ~

 factory: Sylius\Component\Resource\Factory\Factory

 form: Sylius\Bundle\ShippingBundle\Form\Type\ShipmentType

 shipment\_item:

 classes:

 model: Sylius\Component\Shipping\Model\ShipmentItem

 interface: Sylius\Component\Shipping\Model\ShipmentItemInterface

 controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController

 repository: ~

 factory: Sylius\Component\Resource\Factory\Factory

 form: Sylius\Bundle\ShippingBundle\Form\Type\ShipmentItemType

 shipping\_method:

 classes:

 model: Sylius\Component\Shipping\Model\ShippingMethod

 interface: Sylius\Component\Shipping\Model\ShippingMethodInterface

 controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController

 repository: ~

 factory: Sylius\Component\Resource\Factory\Factory

 form: Sylius\Bundle\ShippingBundle\Form\Type\ShippingMethodType

 translation:

 classes:

 model: Sylius\Component\Shipping\Model\ShippingMethodTranslation

 interface: Sylius\Component\Shipping\Model\ShippingMethodTranslationInterface

 controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController

 repository: ~

 factory: Sylius\Component\Resource\Factory\Factory

 form: Sylius\Bundle\ShippingBundle\Form\Type\ShippingMethodTranslationType

 shipping\_category:

 classes:

 model: Sylius\Component\Shipping\Model\ShippingCategory

 interface: Sylius\Component\Shipping\Model\ShippingCategoryInterface

 controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController

 repository: ~

 factory: Sylius\Component\Resource\Factory\Factory

 form: Sylius\Bundle\ShippingBundle\Form\Type\ShippingCategoryType

```

####### Bug tracking[¶](#bug-tracking "Permalink to this headline")

This bundle uses [GitHub issues](https://github.com/Sylius/Sylius/issues).

If you have found bug, please create an issue.

###### Learn more[¶](#learn-more "Permalink to this headline")

* [Shipments in the Sylius platform](index.html#document-book/orders/shipments) - concept documentation

##### SyliusTaxationBundle[¶](#syliustaxationbundle "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

Calculating and applying taxes is a common task for most of ecommerce applications. **SyliusTaxationBundle** is a reusable taxation component for Symfony.

You can integrate it into your existing application and enable the tax calculation logic for any model implementing the `TaxableInterface`.

It supports different tax categories and customizable tax calculators - you’re able to easily implement your own calculator services.

The default implementation handles tax included in and excluded from the price.

As with any Sylius bundle, you can override all the models, controllers, repositories, forms and services.

###### Installation[¶](#installation "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

We assume you’re familiar with [Composer](https://packagist.org), a dependency manager for PHP.

Use the following command to add the bundle to your composer.json and download package.

If you have [Composer installed globally](https://getcomposer.org/doc/00-intro.md#globally).

```

composer require sylius/taxation-bundle

```

Otherwise you have to download .phar file.

```

curl -sS https://getcomposer.org/installer | php

php composer.phar require sylius/taxation-bundle

```

####### Adding required bundles to the kernel[¶](#adding-required-bundles-to-the-kernel "Permalink to this headline")

First, you need to enable the bundle inside the kernel.

If you’re not using any other Sylius bundles, you will also need to add SyliusResourceBundle and its dependencies to kernel.

Don’t worry, everything was automatically installed via Composer.

```

<?php

// app/AppKernel.php

public function registerBundles()

{

    $bundles = array(

        new winzou\Bundle\StateMachineBundle\winzouStateMachineBundle(),

        new FOS\RestBundle\FOSRestBundle(),

        new JMS\SerializerBundle\JMSSerializerBundle(),

        new Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle(),

        new BabDev\PagerfantaBundle\BabDevPagerfantaBundle(),

        new Sylius\Bundle\TaxationBundle\SyliusTaxationBundle(),

        new Sylius\Bundle\ResourceBundle\SyliusResourceBundle(),

        // Other bundles...

        new Doctrine\Bundle\DoctrineBundle\DoctrineBundle(),

    );

}

```

####### Container configuration[¶](#container-configuration "Permalink to this headline")

Put this configuration inside your `app/config/config.yml`.

```

sylius\_taxation:

 driver: doctrine/orm # Configure the Doctrine ORM driver used in documentation.

```

And configure doctrine extensions which are used by this bundle:

```

stof\_doctrine\_extensions:

 orm:

 default:

 timestampable: true

```

####### Updating database schema[¶](#updating-database-schema "Permalink to this headline")

Run the following command.

```

php bin/console doctrine:schema:update --force

```

Warning

This should be done only in **dev** environment! We recommend using Doctrine migrations, to safely update your schema.

###### The TaxableInterface[¶](#the-taxableinterface "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

In order to calculate the taxes for a model in your application, it needs to implement the `TaxableInterface`

It is a very simple interface, with only one method - the `getTaxCategory()`, as every taxable has to belong to a specific tax category.

####### Implementing the interface[¶](#implementing-the-interface "Permalink to this headline")

Let’s assume that you have a **Server** entity in your application. Every server has it’s price and other parameters, but you would like to calculate the tax included in price.

You could calculate the math in a simple method, but it’s not enough when you have to handle multiple tax rates, categories and zones.

First step is to implement the simple interface.

```

namespace AcmeBundle\Entity;

use Sylius\Component\Taxation\Model\TaxCategoryInterface;

use Sylius\Component\Taxation\Model\TaxableInterface;

class Server implements TaxableInterface

{

    private $name;

    private $taxCategory;

    public function getName()

    {

        return $this->name;

    }

    public function setName($name)

    {

        $this->name = $name;

    }

    public function getTaxCategory()

    {

        return $this->taxCategory;

    }

    public function setTaxCategory(TaxCategoryInterface $taxCategory) // This method is not required.

    {

        $this->taxCategory = $taxCategory;

    }

}

```

Second and last task is to define the relation inside `Resources/config/doctrine/Server.orm.xml` of your bundle.

```

<?xml version="1.0" encoding="UTF-8"?>

<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping"

 xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"

 xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping

 https://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">

 <entity name="AcmeBundle\Entity\Server" table="acme\_server">

 <!-- your mappings... -->

 <many-to-one field="taxCategory" target-entity="Sylius\Component\Taxation\Model\TaxCategoryInterface">

 <join-column name="tax\_category\_id" referenced-column-name="id" nullable="false" />

 </many-to-one>

 </entity>

</doctrine-mapping>

```

Done! Now your **Server** model can be used in Sylius taxation engine.

####### Forms[¶](#forms "Permalink to this headline")

If you want to add a tax category selection field to your model form, simply use the `sylius\_tax\_category\_choice` type.

```

namespace AcmeBundle\Form\Type;

use Symfony\Component\Form\FormBuilderInterface;

use Symfony\Component\Form\AbstractType;

class ServerType extends AbstractType

{

    public function buildForm(FormBuilderInterface $builder, array $options)

    {

        $builder

            ->add('name', 'text')

            ->add('taxCategory', 'sylius\_tax\_category\_choice')

        ;

    }

    public function getName()

    {

        return 'acme\_server';

    }

}

```

###### Configuring taxation[¶](#configuring-taxation "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

To start calculating taxes, we need to configure the system. In most cases, the configuration process is done via web interface, but you can also do it programatically.

####### Creating the tax categories[¶](#creating-the-tax-categories "Permalink to this headline")

First step is to create a new tax category.

```

<?php

public function configureAction()

{

    $taxCategoryFactory = $this->container->get('sylius.factory.tax\_category');

    $taxCategoryManager = $this->container->get('sylius.manager.tax\_category');

    $clothingTaxCategory = $taxCategoryFactory->createNew();

    $clothingTaxCategory->setName('Clothing');

    $clothingTaxCategory->setDescription('T-Shirts and this kind of stuff.');

    $foodTaxCategory = $taxCategoryFactory->createNew();

    $foodTaxCategory->setName('Food');

    $foodTaxCategory->setDescription('Yummy!');

    $taxCategoryManager->persist($clothingTaxCategory);

    $taxCategoryManager->persist($foodTaxCategory);

    $taxCategoryManager->flush();

}

```

####### Categorizing the taxables[¶](#categorizing-the-taxables "Permalink to this headline")

Second thing to do is to classify the taxables, in our example we’ll get two products and assign the proper categories to them.

```

<?php

public function configureAction()

{

    $tshirtProduct = // ...

    $bananaProduct = // ... Some logic behind loading entities.

    $taxCategoryRepository = $this->container->get('sylius.repository.tax\_category');

    $clothingTaxCategory = $taxCategoryRepository->findOneBy(['name' => 'Clothing']);

    $foodTaxCategory = $taxCategoryRepository->findOneBy(['name' => 'Food']);

    $tshirtProduct->setTaxCategory($clothingTaxCategory);

    $bananaProduct->setTaxCategory($foodTaxCategory);

    // Save the product entities.

}

```

####### Configuring the tax rates[¶](#configuring-the-tax-rates "Permalink to this headline")

Finally, you have to create appropriate tax rates for each of categories.

```

<?php

public function configureAction()

{

    $taxCategoryRepository = $this->container->get('sylius.repository.tax\_category');

    $clothingTaxCategory = $taxCategoryRepository->findOneBy(['name' => 'Clothing']);

    $foodTaxCategory = $taxCategoryRepository->findOneBy(['name' => 'Food']);

    $taxRateFactory = $this->container->get('sylius.factory.tax\_rate');

    $taxRateManager = $this->container->get('sylius.repository.tax\_rate');

    $clothingTaxRate = $taxRateFactory->createNew();

    $clothingTaxRate->setCategory($clothingTaxCategory);

    $clothingTaxRate->setName('Clothing Tax');

    $clothingTaxRate->setCode('CT');

    $clothingTaxRate->setAmount(0.08);

    $clothingTaxRate->setCalculator('default');

    $foodTaxRate = $taxRateFactory->createNew();

    $foodTaxRate->setCategory($foodTaxCategory);

    $foodTaxRate->setName('Food');

    $foodTaxRate->setCode('F');

    $foodTaxRate->setAmount(0.12);

    $foodTaxRate->setCalculator('default');

    $taxRateManager->persist($clothingTaxRate);

    $taxRateManager->persist($foodTaxRate);

    $taxRateManager->flush();

}

```

Done! See the [“Calculating Taxes” chapter](index.html#document-components_and_bundles/bundles/SyliusTaxationBundle/calculating_taxes) to see how to apply these rates.

###### Calculating taxes[¶](#calculating-taxes "Permalink to this headline")

Warning

When using the CoreBundle (i.e: full stack Sylius framework), the taxes are already calculated at each cart change.

It is implemented by the `TaxationProcessor` class, which is called by the `OrderTaxationListener`.

In order to calculate tax amount for given taxable, we need to find out the applicable tax rate.

The tax rate resolver service is available under `sylius.tax\_rate\_resolver` id, while the delegating tax calculator is accessible via `sylius.tax\_calculator` name.

####### Resolving rate and using calculator[¶](#resolving-rate-and-using-calculator "Permalink to this headline")

```

<?php

namespace Acme\ShopBundle\Taxation;

use Acme\ShopBundle\Entity\Order\Order;

use Sylius\Component\Taxation\Calculator\CalculatorInterface;

use Sylius\Component\Taxation\Resolver\TaxRateResolverInterface;

class TaxApplicator

{

    private $calculator;

    private $taxRateResolver;

    public function \_\_construct(

        CalculatorInterface $calculator,

        TaxRateResolverInterface $taxRateResolver

    )

    {

        $this->calculator = $calculator;

        $this->taxRateResolver = $taxRateResolver;

    }

    public function applyTaxes(Order $order)

    {

        $tax = 0;

        foreach ($order->getItems() as $item) {

            $taxable = $item->getVariant();

            $rate = $this->taxRateResolver->resolve($taxable);

            if (null === $rate) {

                continue; // Skip this item, there is no matching tax rate.

            }

            $tax += $this->calculator->calculate($item->getTotal(), $rate);

        }

        $order->setTaxTotal($tax); // Set the calculated taxes.

    }

}

```

###### Using custom tax calculators[¶](#using-custom-tax-calculators "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

Every **TaxRate** model holds a *calculator* variable with the name of the tax calculation service, used to compute the tax amount.

While the default calculator should fit for most common use cases, you’re free to define your own implementation.

####### Creating the calculator[¶](#creating-the-calculator "Permalink to this headline")

All tax calculators implement the `CalculatorInterface`. In our example we’ll create a simple fee calculator. First, you need to create a new class.

```

<?php

# src/Taxation/Calculator/FeeCalculator.php

declare(strict\_types=1);

namespace App\Taxation\Calculator;

use Sylius\Component\Taxation\Calculator\CalculatorInterface;

use Sylius\Component\Taxation\Model\TaxRateInterface;

final class FeeCalculator implements CalculatorInterface

{

    /\*\*

 \* {@inheritdoc}

 \*/

    public function calculate(float $base, TaxRateInterface $rate): float

    {

        return $base \* ($rate->getAmount() + 0.15 \* 0.30);

    }

}

```

Now, you need to register your new service in container and tag it with `sylius.tax\_calculator`.

```

services:

 app.tax\_calculator.fee:

 class: App\Taxation\Calculator\FeeCalculator

 tags:

 - { name: sylius.tax\_calculator, calculator: fee, label: "Fee" }

```

That would be all. This new option (“Fee”) will appear on the **TaxRate** creation form, in the “calculator” field.

###### Summary[¶](#summary "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### Configuration Reference[¶](#configuration-reference "Permalink to this headline")

```

sylius\_taxation:

 # The driver used for persistence layer.

 driver: ~

 resources:

 tax\_category:

 classes:

 model: Sylius\Component\Taxation\Model\TaxCategory

 interface: Sylius\Component\Taxation\Model\TaxCategoryInterface

 controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController

 repository: ~

 factory: Sylius\Component\Resource\Factory\Factory

 form: Sylius\Bundle\TaxationBundle\Form\Type\TaxCategoryType

 tax\_rate:

 classes:

 model: Sylius\Component\Taxation\Model\TaxRate

 interface: Sylius\Component\Taxation\Model\TaxRateInterface

 controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController

 repository: ~

 factory: Sylius\Component\Resource\Factory\Factory

 form: Sylius\Bundle\TaxationBundle\Form\Type\TaxRateType

```

####### Bug tracking[¶](#bug-tracking "Permalink to this headline")

This bundle uses [GitHub issues](https://github.com/Sylius/Sylius/issues).

If you have found bug, please create an issue.

###### Learn more[¶](#learn-more "Permalink to this headline")

* [Taxation in the Sylius platform](index.html#document-book/orders/taxation) - concept documentation

##### SyliusTaxonomyBundle[¶](#syliustaxonomybundle "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

Flexible categorization system for Symfony applications.

###### Installation[¶](#installation "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

We assume you’re familiar with [Composer](https://packagist.org), a dependency manager for PHP.

Use the following command to add the bundle to your composer.json and download the package.

If you have [Composer installed globally](https://getcomposer.org/doc/00-intro.md#globally).

```

composer require sylius/taxonomy-bundle

```

Otherwise you have to download .phar file.

```

curl -sS https://getcomposer.org/installer | php

php composer.phar require sylius/taxonomy-bundle

```

Note

This version is compatible only with Symfony 2.3 or newer. Please see the CHANGELOG file in the repository, to find version to use with older vendors.

####### Adding required bundles to the kernel[¶](#adding-required-bundles-to-the-kernel "Permalink to this headline")

First, you need to enable the bundle inside the kernel.

If you’re not using any other Sylius bundles, you will also need to add SyliusResourceBundle and its dependencies to the kernel.

Don’t worry, everything was automatically installed via Composer.

```

<?php

// app/AppKernel.php

public function registerBundles()

{

    $bundles = array(

        new winzou\Bundle\StateMachineBundle\winzouStateMachineBundle(),

        new FOS\RestBundle\FOSRestBundle(),

        new JMS\SerializerBundle\JMSSerializerBundle(),

        new Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle(),

        new BabDev\PagerfantaBundle\BabDevPagerfantaBundle(),

        new Sylius\Bundle\TaxonomyBundle\SyliusTaxonomyBundle(),

        new Sylius\Bundle\ResourceBundle\SyliusResourceBundle(),

        // Other bundles...

        new Doctrine\Bundle\DoctrineBundle\DoctrineBundle(),

    );

}

```

####### Container configuration[¶](#container-configuration "Permalink to this headline")

Configure doctrine extensions which are used in the taxonomy bundle:

```

stof\_doctrine\_extensions:

 orm:

 default:

 tree: true

 sluggable: true

 sortable: true

```

####### Updating database schema[¶](#updating-database-schema "Permalink to this headline")

Run the following command.

```

php bin/console doctrine:schema:update --force

```

Warning

This should be done only in **dev** environment! We recommend using Doctrine migrations, to safely update your schema.

###### Taxons[¶](#taxons "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### Taxons[¶](#id1 "Permalink to this headline")

Retrieving taxons from database should always happen via repository, which implements `Sylius\Bundle\ResourceBundle\Model\RepositoryInterface`.

If you are using Doctrine, you’re already familiar with this concept, as it extends the native Doctrine `ObjectRepository` interface.

Your taxon repository is always accessible via `sylius.repository.taxon` service.

Taxon contains methods which allow you to retrieve the child taxons. Let’s look at our example tree.

```

| Categories

|--  T-Shirts

|    |-- Men

|    `-- Women

|--  Stickers

|--  Mugs

`--  Books

```

To get a collection of child taxons under taxon, use the `findChildren` method.

```

<?php

class MyActionController

{

    public function myAction(Request $request)

    {

        // Find the parent taxon

        $taxonRepository = $this->container->get('sylius.repository.taxon');

        $taxon = $taxonRepository->findOneByName('Categories');

        $taxonRepository = $this->container->get('sylius.repository.taxon');

        $taxons = $taxonRepository->findChildren($taxon);

    }

}

```

`$taxons` variable will now contain a list (ArrayCollection) of taxons in following order: T-Shirts, Men, Women, Stickers, Mugs, Books.

###### Categorization[¶](#categorization "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

In this example, we will use taxonomies to categorize products and build a nice catalog.

We think that **keeping the app-specific bundle structure simple** is a good practice, so

let’s assume you have your `ShopBundle` registered under `Acme\ShopBundle` namespace.

```

<?php

// src/Acme/ShopBundle/Entity/Product.php

namespace Acme\ShopBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

use Doctrine\Common\Collections\ArrayCollection;

use Doctrine\Common\Collections\Collection;

class Product

{

    protected $taxons;

    public function \_\_construct()

    {

        $this->taxons = new ArrayCollection();

    }

    public function getTaxons()

    {

        return $this->taxons;

    }

    public function setTaxons(Collection $taxons)

    {

        $this->taxons = $taxons;

    }

}

```

You also need to define the doctrine mapping with a many-to-many relation between Product and Taxons.

Your product entity mapping should live inside `Resources/config/doctrine/Product.orm.xml` of your bundle.

```

<?xml version="1.0" encoding="UTF-8"?>

<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping"

 xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"

 xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping

 https://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">

 <entity name="Acme\ShopBundle\Entity\Product" table="sylius\_product">

 <id name="id" column="id" type="integer">

 <generator strategy="AUTO" />

 </id>

 <!-- Your other mappings. -->

 <many-to-many field="taxons" target-entity="Sylius\Component\Taxonomy\Model\TaxonInterface">

 <join-table name="sylius\_product\_taxon">

 <join-columns>

 <join-column name="product\_id" referenced-column-name="id" nullable="false" />

 </join-columns>

 <inverse-join-columns>

 <join-column name="taxon\_id" referenced-column-name="id" nullable="false" />

 </inverse-join-columns>

 </join-table>

 </many-to-many>

 </entity>

</doctrine-mapping>

```

Product is just an example where we have many to many relationship with taxons,

which will make it possible to categorize products and list them by taxon later.

You can classify any other model in your application the same way.

####### Creating your forms[¶](#creating-your-forms "Permalink to this headline")

To be able to apply taxonomies on your products, or whatever you are categorizing or tagging,

it is handy to use sylius\_taxon\_choice form type:

```

<?php

// src/Acme/ShopBundle/Form/ProductType.php

namespace Acme\ShopBundle\Form;

use Symfony\Component\Form\AbstractType;

use Symfony\Component\Form\FormBuilderInterface;

use Symfony\Component\OptionsResolver\OptionsResolver;

class ProductType extends AbstractType

{

    public function buildForm(FormBuilderInterface $builder, array $options)

    {

        $builder->add('taxons', 'sylius\_taxon\_choice');

    }

    public function configureOptions(OptionsResolver $resolver)

    {

        $resolver

            ->setDefaults(array(

                'data\_class' => 'Acme\ShopBundle\Entity\Product'

            ))

        ;

    }

}

```

This sylius\_taxon\_choice type will add a select input field for each taxonomy, with select option for each taxon.

###### Summary[¶](#summary "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### Configuration Reference[¶](#configuration-reference "Permalink to this headline")

```

sylius\_taxonomy:

 # The driver used for persistence layer.

 driver: ~

 resources:

 taxon:

 classes:

 model: Sylius\Component\Taxonomy\Model\Taxon

 interface: Sylius\Component\Taxonomy\Model\TaxonInterface

 controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController

 repository: ~

 factory: Sylius\Component\Resource\Factory\TranslatableFactory

 form: Sylius\Bundle\TaxonomyBundle\Form\Type\TaxonType

 translation:

 classes:

 model: Sylius\Component\Taxonomy\Model\TaxonTranslation

 interface: Sylius\Component\Taxonomy\Model\TaxonTranslationInterface

 controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController

 repository: ~

 factory: Sylius\Component\Resource\Factory\Factory

 form: Sylius\Bundle\TaxonomyBundle\Form\Type\TaxonTranslationType

```

####### Bug tracking[¶](#bug-tracking "Permalink to this headline")

This bundle uses [GitHub issues](https://github.com/Sylius/Sylius/issues).

If you have found bug, please create an issue.

###### Learn more[¶](#learn-more "Permalink to this headline")

* [Taxons in the Sylius platform](index.html#document-book/products/taxons) - concept documentation

##### SyliusUserBundle[¶](#syliususerbundle "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

A solution for user management system inside of a Symfony application.

###### Installation[¶](#installation "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

We assume you’re familiar with [Composer](https://packagist.org), a dependency manager for PHP.

Use the following command to add the bundle to your composer.json and download the package.

If you have [Composer installed globally](https://getcomposer.org/doc/00-intro.md#globally).

```

composer require sylius/user-bundle

```

Otherwise you have to download .phar file.

```

curl -sS https://getcomposer.org/installer | php

php composer.phar require sylius/user-bundle

```

####### Adding required bundles to the kernel[¶](#adding-required-bundles-to-the-kernel "Permalink to this headline")

You need to enable the bundle inside the kernel.

If you’re not using any other Sylius bundles, you will also need to add SyliusResourceBundle and its dependencies to kernel.

Don’t worry, everything was automatically installed via Composer.

```

<?php

// app/AppKernel.php

public function registerBundles()

{

    $bundles = array(

        new FOS\RestBundle\FOSRestBundle(),

        new JMS\SerializerBundle\JMSSerializerBundle(),

        new Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle(),

        new BabDev\PagerfantaBundle\BabDevPagerfantaBundle(),

        new Bazinga\Bundle\HateoasBundle\BazingaHateoasBundle(),

        new winzou\Bundle\StateMachineBundle\winzouStateMachineBundle(),

        new Sylius\Bundle\ResourceBundle\SyliusResourceBundle(),

        new Sylius\Bundle\MailerBundle\SyliusMailerBundle(),

        new Sylius\Bundle\UserBundle\SyliusUserBundle(),

        // Other bundles...

        new Doctrine\Bundle\DoctrineBundle\DoctrineBundle(),

    );

}

```

####### Configure Doctrine extensions[¶](#configure-doctrine-extensions "Permalink to this headline")

Configure doctrine extensions which are used by the bundle.

```

# app/config/config.yml

stof\_doctrine\_extensions:

 orm:

 default:

 timestampable: true

```

####### Updating database schema[¶](#updating-database-schema "Permalink to this headline")

Run the following command.

```

php bin/console doctrine:schema:update --force

```

Warning

This should be done only in **dev** environment! We recommend using Doctrine migrations, to safely update your schema.

Congratulations! The bundle is now installed and ready to use.

###### Summary[¶](#summary "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### Configuration reference[¶](#configuration-reference "Permalink to this headline")

```

sylius\_user:

 driver: doctrine/orm

 encoder: argon2i

 resources:

 admin:

 user:

 classes:

 model: Sylius\Component\Core\Model\AdminUser

 repository: Sylius\Bundle\UserBundle\Doctrine\ORM\UserRepository

 form: Sylius\Bundle\CoreBundle\Form\Type\User\AdminUserType

 interface: Sylius\Component\User\Model\UserInterface

 controller: Sylius\Bundle\UserBundle\Controller\UserController

 factory: Sylius\Component\Resource\Factory\Factory

 templates: 'SyliusUserBundle:User'

 encoder: null

 login\_tracking\_interval: null

 resetting:

 token:

 ttl: P1D

 length: 16

 field\_name: passwordResetToken

 pin:

 length: 4

 field\_name: passwordResetToken

 verification:

 token:

 length: 16

 field\_name: emailVerificationToken

 shop:

 user:

 classes:

 model: Sylius\Component\Core\Model\ShopUser

 repository: Sylius\Bundle\CoreBundle\Doctrine\ORM\UserRepository

 form: Sylius\Bundle\CoreBundle\Form\Type\User\ShopUserType

 interface: Sylius\Component\User\Model\UserInterface

 controller: Sylius\Bundle\UserBundle\Controller\UserController

 factory: Sylius\Component\Resource\Factory\Factory

 templates: 'SyliusUserBundle:User'

 encoder: null

 login\_tracking\_interval: null

 resetting:

 token:

 ttl: P1D

 length: 16

 field\_name: passwordResetToken

 pin:

 length: 4

 field\_name: passwordResetToken

 verification:

 token:

 length: 16

 field\_name: emailVerificationToken

 oauth:

 user:

 classes:

 model: Sylius\Component\User\Model\UserOAuth

 interface: Sylius\Component\User\Model\UserOAuthInterface

 controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController

 factory: Sylius\Component\Resource\Factory\Factory

 form: Sylius\Bundle\UserBundle\Form\Type\UserType

 templates: 'SyliusUserBundle:User'

 encoder: false

 login\_tracking\_interval: null

 resetting:

 token:

 ttl: P1D

 length: 16

 field\_name: passwordResetToken

 pin:

 length: 4

 field\_name: passwordResetToken

 verification:

 token:

 length: 16

 field\_name: emailVerificationToken

```

####### Bug tracking[¶](#bug-tracking "Permalink to this headline")

This bundle uses [GitHub issues](https://github.com/Sylius/Sylius/issues).

If you have found bug, please create an issue.

###### Learn more[¶](#learn-more "Permalink to this headline")

* [Users & Customers in the Sylius platform](index.html#document-book/customers/index) - concept documentation

* [SyliusAddressingBundle](index.html#document-components_and_bundles/bundles/SyliusAddressingBundle/index)

* [SyliusAttributeBundle](index.html#document-components_and_bundles/bundles/SyliusAttributeBundle/index)

* [SyliusCustomerBundle](index.html#document-components_and_bundles/bundles/SyliusCustomerBundle/index)

* [SyliusFixturesBundle](https://github.com/Sylius/SyliusFixturesBundle/blob/master/docs/index.md)

* [SyliusGridBundle](https://github.com/Sylius/SyliusGridBundle/blob/master/docs/index.md)

* [SyliusInventoryBundle](index.html#document-components_and_bundles/bundles/SyliusInventoryBundle/index)

* [SyliusMailerBundle](https://github.com/Sylius/SyliusMailerBundle/blob/master/docs/index.md)

* [SyliusOrderBundle](index.html#document-components_and_bundles/bundles/SyliusOrderBundle/index)

* [SyliusProductBundle](index.html#document-components_and_bundles/bundles/SyliusProductBundle/index)

* [SyliusPromotionBundle](index.html#document-components_and_bundles/bundles/SyliusPromotionBundle/index)

* [SyliusResourceBundle](https://github.com/Sylius/SyliusResourceBundle/blob/master/docs/index.md)

* [SyliusShippingBundle](index.html#document-components_and_bundles/bundles/SyliusShippingBundle/index)

* [SyliusTaxationBundle](index.html#document-components_and_bundles/bundles/SyliusTaxationBundle/index)

* [SyliusTaxonomyBundle](index.html#document-components_and_bundles/bundles/SyliusTaxonomyBundle/index)

* [SyliusThemeBundle](https://github.com/Sylius/SyliusThemeBundle/blob/master/docs/index.md)

* [SyliusUserBundle](index.html#document-components_and_bundles/bundles/SyliusUserBundle/index)

#### Sylius Components Documentation[¶](#sylius-components-documentation "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

We provide a set of well-tested and decoupled PHP libraries.

The components are the foundation of the Sylius platform, but they can also

be used standalone with any PHP application even if you use a framework different than Symfony.

These packages solve common E-Commerce and web application problems. Have a look around this documentation to see if you will find them useful!

We recommend checking out [Components General Guide](index.html#document-components_and_bundles/components/general/index), which will get you started in minutes.

##### Components General Guide[¶](#components-general-guide "Permalink to this headline")

All Sylius components have very similar structure and this guide will introduce you these conventions.

Through this documentation, you will learn how to install and use them in any PHP application.

###### How to Install and Use the Sylius Components[¶](#how-to-install-and-use-the-sylius-components "Permalink to this headline")

If you’re starting a new project (or already have a project) that will use

one or more components, the easiest way to integrate everything is with [Composer](https://getcomposer.org).

Composer is smart enough to download the component(s) that you need and take

care of autoloading so that you can begin using the libraries immediately.

This article will take you through using [Taxation](index.html#document-components_and_bundles/components/Taxation/index), though

this applies to using any component.

####### Using the Taxation Component[¶](#using-the-taxation-component "Permalink to this headline")

**1.** If you’re creating a new project, create a new empty directory for it.

```

mkdir project/

cd project/

```

**2.** Open a terminal and use Composer to grab the library.

Tip

[Install Composer](https://getcomposer.org/download) if you don’t have it already present on your system.

Depending on how you install, you may end up with a `composer.phar`

file in your directory. In that case, no worries! Just run

`php composer.phar require sylius/taxation`.

```

composer require sylius/taxation

```

The name `sylius/taxation` is written at the top of the documentation for

whatever component you want.

**3.** Write your code!

Once Composer has downloaded the component(s), all you need to do is include

the `vendor/autoload.php` file that was generated by Composer. This file

takes care of autoloading all of the libraries so that you can use them

immediately. Open your favorite code editor and start coding:

```

<?php

// Sample script.php file.

require\_once \_\_DIR\_\_.'/vendor/autoload.php';

use Sylius\Component\Taxation\Calculator\DefaultCalculator;

use Sylius\Component\Taxation\Model\TaxRate;

$calculator = new DefaultCalculator();

$taxRate = new TaxRate();

$taxRate->setAmount(0.23);

$taxAmount = $calculator->calculate(100, $taxRate);

echo $taxAmount; // Outputs "23".

```

You can open the “script.php” file in browser or run it via console:

```

php script.php

```

####### Using all of the Components[¶](#using-all-of-the-components "Permalink to this headline")

If you want to use all of the Sylius Components, then instead of adding

them one by one, you can include the `sylius/sylius` package:

```

composer require sylius/sylius

```

####### Now what?[¶](#now-what "Permalink to this headline")

Check out [What is a Resource?](index.html#document-components_and_bundles/components/general/what_is_resource), which will give you basic understanding about how all Sylius components look and work like.

Enjoy!

###### What is a Resource?[¶](#what-is-a-resource "Permalink to this headline")

We refer to data models as “Resources”. In the simplest words, some examples that you will find in Sylius:

* Product

* TaxRate

* Order

* OrderItem

* ShippingMethod

* PaymentMethod

As you can already guess, there are many more Resources in Sylius. It is a really simple but powerful concept that allows us to create a nice abstraction to handle all the complex logic of E-Commerce.

When using Components, you will have access to the resources provided by them out-of-the-box.

What is more, you will be able to create your own, custom Resources and benefit from all features provided by Sylius.

####### Now what?[¶](#now-what "Permalink to this headline")

Learn how we handle [Creating Resources](index.html#document-components_and_bundles/components/general/creating_resources) via Factory pattern.

###### Creating Resources[¶](#creating-resources "Permalink to this headline")

Every resource provided by a Sylius component should be created via a factory.

Some resources use the default resource class while some use custom implementations to provide extra functionality.

####### Using Factory To Create New Resource[¶](#using-factory-to-create-new-resource "Permalink to this headline")

To create new resources you should use the default factory implementation.

```

<?php

use Sylius\Component\Product\Model\Product;

use Sylius\Component\Resource\Factory\Factory;

$factory = new Factory(Product::class);

$product = $factory->createNew();

```

That’s it! The `$product` variable will hold a clean instance of the Product model.

####### Why Even Bother?[¶](#why-even-bother "Permalink to this headline")

“Hey! This is same as `$product = new Product();`!”

Yes, and no. Every Factory implements FactoryInterface and this allows you to abstract the way that resources are created.

It also makes testing much simpler because you can mock the Factory and use it as a test double in your service.

What is more, thanks to usage of Factory pattern, Sylius is able to easily swap the default Product (or any other resource) model with your custom implementation, without changing code.

Caution

In a concrete Component’s documentation we will use `new` keyword to create resources - just to keep things simpler to read.

##### Addressing[¶](#addressing "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

Sylius Addressing component for PHP E-Commerce applications which

provides you with basic Address, Country, Province and Zone models.

###### Installation[¶](#installation "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

You can install the component in 2 different ways:

* [Install it via Composer](index.html#document-components_and_bundles/components/general/using_components) (`sylius/addressing` on [Packagist](https://packagist.org/packages/sylius/addressing));

* Use the official Git repository (<https://github.com/Sylius/Addressing>).

Then, require the `vendor/autoload.php` file to enable the autoloading mechanism

provided by Composer. Otherwise, your application won’t be able to find the classes

of this Sylius component.

###### Basic Usage[¶](#basic-usage "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### ZoneMatcher[¶](#zonematcher "Permalink to this headline")

Zones are not very useful by themselves, but they can take part in e.g. a complex taxation/shipping

system. This service is capable of getting a [Zone](index.html#component-addressing-model-zone)

specific for given [Address](index.html#component-addressing-model-address).

It uses a collaborator implementing Doctrine’s

[ObjectRepository](http://www.doctrine-project.org/api/common/2.4/class-Doctrine.Common.Persistence.ObjectRepository.html) interface to obtain all available zones,

compare them with given [Address](index.html#component-addressing-model-address)

and return best fitted [Zone](index.html#component-addressing-model-zone).

First lets make some preparations.

```

<?php

use Sylius\Component\Addressing\Model\Address;

use Sylius\Component\Addressing\Model\Zone;

use Sylius\Component\Addressing\Model\ZoneInterface;

use Sylius\Component\Addressing\Model\ZoneMember;

use Sylius\Component\Resource\Repository\InMemoryRepository;

$zoneRepository = new InMemoryRepository(ZoneInterface::class);

$zone = new Zone();

$zoneMember = new ZoneMember();

$address = new Address();

$address->setCountry('US');

$zoneMember->setCode('US');

$zoneMember->setBelongsTo($zone);

$zone->addMember($zoneMember);

$zoneRepository->add($zone);

```

Now that we have all the needed parts lets match something.

```

<?php

use Sylius\Component\Addressing\Matcher\ZoneMatcher;

$zoneMatcher = new ZoneMatcher($zoneRepository);

$zoneMatcher->match($address); // returns the best matching zone

                               // for the address given, in this case $zone

```

**ZoneMatcher** can also return all zones containing given [Address](index.html#component-addressing-model-address).

```

<?php

$zoneMatcher->matchAll($address); // returns all zones containing given $address

```

To be more specific you can provide a `scope` which will

narrow the search only to zones with same corresponding property.

```

<?php

$zone->setScope('earth');

$zoneMatcher->match($address, 'earth'); // returns $zone

$zoneMatcher->matchAll($address, 'mars'); // returns null as there is no

                                          // zone with 'mars' scope

```

Note

This service implements the [ZoneMatcherInterface](index.html#component-addressing-matcher-zone-matcher-interface).

Caution

Throws [\InvalidArgumentException](https://php.net/manual/en/class.invalidargumentexception.php).

###### Models[¶](#models "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### Address[¶](#address "Permalink to this headline")

The customer’s address is represented by an **Address** model. It should contain all data

concerning customer’s address and as default has the following properties:

| Property | Description |

| --- | --- |

| id | Unique id of the address |

| firstName | Customer’s first name |

| lastName | Customer’s last name |

| phoneNumber | Customer’s phone number |

| company | Company name |

| country | Country’s ISO code |

| province | Province’s code |

| street | Address’ street |

| city | Address’ city |

| postcode | Address’ postcode |

| createdAt | Date when address was created |

| updatedAt | Date of last address’ update |

Note

This model implements the [AddressInterface](index.html#component-addressing-model-address-interface).

####### Country[¶](#country "Permalink to this headline")

The geographical area of a country is represented by a **Country** model.

It should contain all data concerning a country and as default has the following properties:

| Property | Description |

| --- | --- |

| id | Unique id of the country |

| code | Country’s ISO code |

| provinces | Collection of **Province** objects |

| enabled | Indicates whether country is enabled |

Note

This model implements the [CountryInterface](index.html#component-addressing-model-country-interface)

and [CodeAwareInterface](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Component/Model/CodeAwareInterface.php).

####### Province[¶](#province "Permalink to this headline")

Smaller area inside a country is represented by a **Province** model.

You can use it to manage provinces or states and assign it to an address as well.

It should contain all data concerning a province and as default has the following properties:

| Property | Description |

| --- | --- |

| id | Unique id of the province |

| code | Unique code of the province |

| name | Province’s name |

| country | The **Country** this province is assigned to |

Note

This model implements the [ProvinceInterface](index.html#component-addressing-model-province-interface)

and [CodeAwareInterface](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Component/Model/CodeAwareInterface.php).

####### Zone[¶](#zone "Permalink to this headline")

The geographical area is represented by a **Zone** model.

It should contain all data concerning a zone and as default has the following properties:

| Property | Description |

| --- | --- |

| id | Unique id of the zone |

| code | Unique code of the zone |

| name | Zone’s name |

| type | Zone’s type |

| scope | Zone’s scope |

| members | All of the **ZoneMember** objects assigned to this zone |

Note

This model implements the [ZoneInterface](index.html#component-addressing-model-zone-interface)

and [CodeAwareInterface](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Component/Model/CodeAwareInterface.php).

####### ZoneMember[¶](#zonemember "Permalink to this headline")

In order to add a specific location to a **Zone**,

an instance of **ZoneMember** must be created with that location’s code.

On default this model has the following properties:

| Property | Description |

| --- | --- |

| id | Unique id of the zone member |

| code | Unique code of affiliated member i.e. country’s code |

| belongsTo | The **Zone** this member is assigned to |

Note

This model implements [ZoneMemberInterface](index.html#component-addressing-model-zone-member-interface)

and [CodeAwareInterface](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Component/Model/CodeAwareInterface.php).

###### Interfaces[¶](#interfaces "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### Model Interfaces[¶](#model-interfaces "Permalink to this headline")

######## AddressInterface[¶](#addressinterface "Permalink to this headline")

This interface should be implemented by models representing the customer’s address.

Note

This interface extends [TimestampableInterface](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Component/Model/TimestampableInterface.php).

######## CountryInterface[¶](#countryinterface "Permalink to this headline")

This interfaces should be implemented by models representing a country.

Note

This interface extends [ToggleableInterface](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Component/Model/ToggleableInterface.php).

######## ProvinceInterface[¶](#provinceinterface "Permalink to this headline")

This interface should be implemented by models representing a part of a country.

######## ZoneInterface[¶](#zoneinterface "Permalink to this headline")

This interface should be implemented by models representing a single zone.

It also holds all the [Zone Types](index.html#document-components_and_bundles/components/Addressing/zone_types).

######## ZoneMemberInterface[¶](#zonememberinterface "Permalink to this headline")

This interface should be implemented by models that represent an area a specific

zone contains, e.g. all countries in the European Union.

####### Service Interfaces[¶](#service-interfaces "Permalink to this headline")

######## RestrictedZoneCheckerInterface[¶](#restrictedzonecheckerinterface "Permalink to this headline")

A service implementing this interface should be able to check

if given [Address](index.html#component-addressing-model-address) is in a restricted zone.

######## ZoneMatcherInterface[¶](#zonematcherinterface "Permalink to this headline")

This interface should be implemented by a service responsible of finding the best matching zone,

and all zones containing the provided [Address](index.html#component-addressing-model-address).

###### Zone Types[¶](#zone-types "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

There are three zone types available by default:

| Related constant | Type |

| --- | --- |

| TYPE\_COUNTRY | country |

| TYPE\_PROVINCE | province |

| TYPE\_ZONE | zone |

Note

All of the above types are constant fields in the [ZoneInterface](index.html#component-addressing-model-zone-interface).

###### Learn more[¶](#learn-more "Permalink to this headline")

* [Addresses in the Sylius platform](index.html#document-book/customers/addresses/index) - concept documentation

##### Attribute[¶](#attribute "Permalink to this headline")

Handling of dynamic attributes on PHP models is a common task for most of modern business applications.

Sylius component makes it easier to handle different types of attributes

and attach them to any object by implementing a simple interface.

###### Installation[¶](#installation "Permalink to this headline")

You can install the component in 2 different ways:

* [Install it via Composer](index.html#document-components_and_bundles/components/general/using_components) (`sylius/attribute` on [Packagist](https://packagist.org/packages/sylius/attribute)) via command

```

composer require sylius/attribute

```

* Use the official Git repository (<https://github.com/Sylius/Attribute>).

Then, require the `vendor/autoload.php` file to enable the autoloading mechanism

provided by Composer. Otherwise, your application won’t be able to find the classes

of this Sylius component.

###### Basic Usage[¶](#basic-usage "Permalink to this headline")

####### Creating an attributable class[¶](#creating-an-attributable-class "Permalink to this headline")

In the following example you will see a minimalistic implementation

of the [AttributeSubjectInterface](index.html#component-attribute-model-attribute-subject-interface).

```

<?php

namespace App\Model;

use Doctrine\Common\Collections\Collection;

use Sylius\Component\Attribute\Model\AttributeSubjectInterface;

use Sylius\Component\Attribute\Model\AttributeValueInterface;

class Shirt implements AttributeSubjectInterface

{

    private Collection $attributes;

    public function getAttributes(): Collection

    {

        return $this->attributes;

    }

    public function setAttributes(Collection $attributes): void

    {

        foreach ($attributes as $attribute) {

            $this->addAttribute($attribute);

        }

    }

    public function addAttribute(AttributeValueInterface $attribute): void

    {

        if (!$this->hasAttribute($attribute)) {

            $attribute->setSubject($this);

            $this->attributes->add($attribute);

        }

    }

    public function removeAttribute(AttributeValueInterface $attribute): void

    {

        if ($this->hasAttribute($attribute)) {

            $attribute->setSubject(null);

            $this->attributes->removeElement($attribute);

        }

    }

    public function hasAttribute(AttributeValueInterface $attribute): bool

    {

        return $this->attributes->contains($attribute);

    }

    public function hasAttributeByCodeAndLocale($attributeCode, $localeCode = null): bool

    {

        return (bool) $this->getAttributeByCodeAndLocale($attributeCode, $localeCode);

    }

    public function getAttributeByCodeAndLocale(string $attributeCode, string $localeCode = null): ?AttributeValueInterface

    {

        return $this->attributes->filter(fn (AttributeValueInterface $attribute) => $attributeCode === $attribute->getCode() &&

            ($attribute->getLocaleCode() === $localeCode || null === $attribute->getLocaleCode()))

            ->first();

    }

    public function getAttributesByLocale(string $localeCode, string $fallbackLocaleCode, ?string $baseLocaleCode = null): Collection

    {

        return $this->attributes->filter(function (AttributeValueInterface $attribute) use ($localeCode) {

                return $attribute->getLocaleCode() === $localeCode;

            }

        );

    }

    // Optional: you can search attributes by name

    public function hasAttributeByName(string $attributeName): bool

    {

        return (bool) $this->getAttributeByName($attributeName);

    }

    public function getAttributeByName(string $attributeName): Collection

    {

        return $this->attributes->filter(fn ($attribute) => $attributeName === $attribute->getName());

    }

}

```

Note

An implementation similar to the one above has been done in the [Product](index.html#component-product-model-product) model.

####### Adding attributes to an object[¶](#adding-attributes-to-an-object "Permalink to this headline")

Once we have our class we can characterize it with attributes.

```

<?php

use App\Model\Shirt;

use Sylius\Component\Attribute\Model\Attribute;

use Sylius\Component\Attribute\Model\AttributeValue;

use Sylius\Component\Attribute\AttributeType\TextAttributeType;

use Sylius\Component\Attribute\Model\AttributeValueInterface;

$attribute = new Attribute();

$attribute->setName('Size');

$attribute->setType(TextAttributeType::TYPE);

$attribute->setStorageType(AttributeValueInterface::STORAGE\_TEXT);

$smallSize = new AttributeValue();

$mediumSize = new AttributeValue();

$smallSize->setAttribute($attribute);

$mediumSize->setAttribute($attribute);

$smallSize->setValue('S');

$mediumSize->setValue('M');

$shirt = new Shirt();

$shirt->addAttribute($smallSize);

$shirt->addAttribute($mediumSize);

```

Or you can just add all attributes needed using a class implementing

Doctrine’s `Collection` interface, e.g. the `ArrayCollection` class.

Warning

Beware! It’s really important to set proper attribute storage type, which should reflect value type that is set in AttributeValue.

```

<?php

use Doctrine\Common\Collections\ArrayCollection;

$attributes = new ArrayCollection();

$attributes->add($smallSize);

$attributes->add($mediumSize);

$shirt->setAttributes($attributes);

```

Note

Notice that you don’t actually add an [Attribute](index.html#component-attribute-model-attribute) to the subject,

instead you need to add every [AttributeValue](index.html#component-attribute-model-attribute-value) assigned to the attribute.

####### Accessing attributes[¶](#accessing-attributes "Permalink to this headline")

```

<?php

$shirt->getAttributes(); // returns an array containing all set attributes

$shirt->hasAttribute($smallSize); // returns true

$shirt->hasAttribute($hugeSize); // returns false

```

####### Accessing attributes by name[¶](#accessing-attributes-by-name "Permalink to this headline")

If you are using the optional functions that checks attributes by name you can access them by this value

```

<?php

$shirt->hasAttributeByName('Size'); // returns true

$shirt->getAttributeByName('Size'); // returns $smallSize

```

####### Removing an attribute[¶](#removing-an-attribute "Permalink to this headline")

```

<?php

// in example implementation, removeAttribute function checks if collection has attribute

$shirt->hasAttribute($smallSize); // returns true

$shirt->removeAttribute($smallSize);

$shirt->hasAttribute($smallSize); // now returns false

```

###### Models[¶](#models "Permalink to this headline")

####### Attribute[¶](#attribute "Permalink to this headline")

Every attribute is represented by the **Attribute** model which by default has the following properties:

| Property | Description |

| --- | --- |

| id | Unique id of the attribute |

| type | Attribute’s type (‘text’ by default) |

| name | Attribute’s name (from AttributeTranslation) |

| configuration | Attribute’s configuration |

| translatable | Attribute possibility to be translated |

| storageType | Defines how attribute value should be stored in database |

| createdAt | Date when attribute was created |

| updatedAt | Date of last attribute update |

Note

This model uses the [TranslatableTrait](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Component/Model/TranslatableTrait.php)

and implements the [AttributeInterface](index.html#component-attribute-model-attribute-interface).

For more detailed information go to [Sylius Attribute Component Attribute](https://github.com/Sylius/Attribute/blob/master/Model/Attribute.php).

Attention

Attribute’s type is an alias of AttributeType service.

####### AttributeValue[¶](#attributevalue "Permalink to this headline")

This model binds the subject and the attribute,

it is used to store the value of the attribute for the subject.

It has the following properties:

| Property | Description |

| --- | --- |

| id | Unique id of the attribute value |

| subject | Reference to attribute’s subject |

| attribute | Reference to an attribute |

| value | Attribute’s value (not mapped) |

| text | Value of attribute stored as text |

| boolean | Value of attribute stored as boolean |

| integer | Value of attribute stored as integer |

| float | Value of attribute stored as float |

| datetime | Value of attribute stored as datetime |

| date | Value of attribute stored as date |

| json | Value of attribute stored as array |

Attention

`Value` property is used only as proxy, that stores data in proper field. It’s crucial to set attribute value in field, that is mapped as attribute’s storage type.

Note

This model implements the [AttributeValueInterface](index.html#component-attribute-model-attribute-value-interface).

> 

> For more detailed information go to [Sylius Attribute Component AttributeValue](https://github.com/Sylius/Attribute/blob/master/Model/AttributeValue.php).

####### AttributeTranslation[¶](#attributetranslation "Permalink to this headline")

The attribute’s name for different locales is represented by the **AttributeTranslation**

model which has the following properties:

| Property | Description |

| --- | --- |

| id | Unique id of the attribute translation |

| name | Attribute’s name for given locale |

Note

This model extends the [AbstractTranslation](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Component/Model/AbstractTranslation.php) class

and implements the [AttributeTranslationInterface](index.html#component-attribute-model-attribute-translation-interface).

For more detailed information go to [Sylius Attribute Component AttributeTranslation](https://github.com/Sylius/Attribute/blob/master/Model/AttributeTranslation.php).

###### Interfaces[¶](#interfaces "Permalink to this headline")

####### Model Interfaces[¶](#model-interfaces "Permalink to this headline")

######## AttributeInterface[¶](#attributeinterface "Permalink to this headline")

This interface should be implemented by models

used for describing a product’s attribute.

Note

This interface extends the [TimestampableInterface](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Component/Model/TimestampableInterface.php) and

[ToggleableInterface](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Component/Model/ToggleableInterface.php)

and [CodeAwareInterface](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Component/Model/CodeAwareInterface.php).

For more detailed information go to [Sylius Attribute Component AttributeInterface](https://github.com/Sylius/Attribute/blob/master/Model/AttributeInterface.php).

######## AttributeValueInterface[¶](#attributevalueinterface "Permalink to this headline")

This interface should be implemented by models used for

binding an [Attribute](index.html#component-attribute-model-attribute)

with a model implementing the [AttributeSubjectInterface](#component-attribute-model-attribute-subject-interface)

e.g. the [Product](index.html#component-product-model-product).

Note

For more detailed information go to [Sylius Attribute Component AttributeValueInterface](https://github.com/Sylius/Attribute/blob/master/Model/AttributeValueInterface.php).

######## AttributeTranslationInterface[¶](#attributetranslationinterface "Permalink to this headline")

This interface should be implemented by models maintaining a translation

of an [Attribute](index.html#component-attribute-model-attribute) for specified locale.

Note

For more detailed information go to [Sylius Attribute Component AttributeTranslationInterface](https://github.com/Sylius/Attribute/blob/master/Model/AttributeTranslationInterface.php).

######## AttributeSubjectInterface[¶](#attributesubjectinterface "Permalink to this headline")

This interface should be implemented by models you want to characterize with

various [AttributeValue](index.html#component-attribute-model-attribute-value) objects.

It will ask you to implement the management of [AttributeValue](index.html#component-attribute-model-attribute-value) models.

Note

For more detailed information go to [Sylius Attribute Component AttributeSubjectInterface](https://github.com/Sylius/Attribute/blob/master/Model/AttributeSubjectInterface.php).

###### Learn more[¶](#learn-more "Permalink to this headline")

* [Attributes in the Sylius platform](index.html#document-book/products/attributes) - concept documentation

##### Channel[¶](#channel "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

Sale channels management implementation in PHP.

###### Installation[¶](#installation "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

You can install the component in 2 different ways:

* [Install it via Composer](index.html#document-components_and_bundles/components/general/using_components) (`sylius/channel` on [Packagist](https://packagist.org/packages/sylius/channel));

* Use the official Git repository (<https://github.com/Sylius/Channel>).

Then, require the `vendor/autoload.php` file to enable the autoloading mechanism

provided by Composer. Otherwise, your application won’t be able to find the classes

of this Sylius component.

###### Models[¶](#models "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### Channel[¶](#channel "Permalink to this headline")

Sale channel is represented by a **Channel** model. It should have everything

concerning channel’s data and as default has the following properties:

| Property | Description |

| --- | --- |

| id | Unique id of the channel |

| code | Channel’s code |

| name | Channel’s name |

| description | Channel’s description |

| url | Channel’s URL |

| color | Channel’s color |

| enabled | Indicates whether channel is available |

| createdAt | Date of creation |

| updatedAt | Date of update |

Note

This model implements [ChannelInterface](index.html#component-channel-model-channel-interface).

###### Interfaces[¶](#interfaces "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### Model Interfaces[¶](#model-interfaces "Permalink to this headline")

######## ChannelInterface[¶](#channelinterface "Permalink to this headline")

This interface should be implemented by every custom sale channel model.

Note

This interface extends [TimestampableInterface](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Component/Model/TimestampableInterface.php) and [CodeAwareInterface](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Component/Model/CodeAwareInterface.php).

######## ChannelAwareInterface[¶](#channelawareinterface "Permalink to this headline")

This interface should be implemented by models associated

with a specific sale channel.

######## ChannelsAwareInterface[¶](#channelsawareinterface "Permalink to this headline")

This interface should be implemented by models associated with multiple channels.

####### Service Interfaces[¶](#service-interfaces "Permalink to this headline")

###### Learn more[¶](#learn-more "Permalink to this headline")

* [Channels in the Sylius platform](index.html#document-book/configuration/channels) - concept documentation

##### Currency[¶](#currency "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

Managing different currencies, exchange rates and converting cash amounts for PHP applications.

###### Installation[¶](#installation "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

You can install the component in 2 different ways:

* [Install it via Composer](index.html#document-components_and_bundles/components/general/using_components) (`sylius/currency` on [Packagist](https://packagist.org/packages/sylius/currency));

* Use the official Git repository (<https://github.com/Sylius/Currency>).

Then, require the `vendor/autoload.php` file to enable the autoloading mechanism

provided by Composer. Otherwise, your application won’t be able to find the classes

of this Sylius component.

###### Basic Usage[¶](#basic-usage "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### Getting a Currency name[¶](#getting-a-currency-name "Permalink to this headline")

```

<?php

use Sylius\Component\Currency\Model\Currency;

$currency = new Currency();

$currency->setCode('USD');

$currency->getName(); // Returns 'US Dollar'.

```

The `getName` method uses Symfony’s [Intl](https://symfony.com/doc/current/components/intl.html) class to

convert currency’s code into a human friendly form.

Note

The output of `getName` may vary as the name is generated accordingly to the set locale.

####### CurrencyConverter[¶](#currencyconverter "Permalink to this headline")

The **CurrencyConverter** allows you to convert a value accordingly to the exchange rate of specified currency.

This behaviour is used just for displaying the *approximate* value in another currency than the base currency of the channel.

Note

This service implements the [CurrencyConverterInterface](index.html#component-currency-converter-currency-converter-interface).

Caution

Throws [UnavailableCurrencyException](index.html#component-currency-converter-unavailable-currency-exception).

####### CurrencyProvider[¶](#currencyprovider "Permalink to this headline")

The **CurrencyProvider** allows you to get all available currencies.

```

<?php

use Sylius\Component\Currency\Provider\CurrencyProvider;

use Sylius\Component\Resource\Repository\InMemoryRepository;

$currencyRepository = new InMemoryRepository();

$currencyProvider = new CurrencyProvider($currencyRepository);

$currencyProvider->getAvailableCurrencies(); // Returns an array of Currency objects.

```

The `getAvailableCurrencies` method retrieves all currencies which `enabled`

property is set to true and have been inserted in the given repository.

Note

This service implements the [CurrencyProviderInterface](index.html#component-currency-provider-currency-provider-interface).

###### Models[¶](#models "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### Currency[¶](#currency "Permalink to this headline")

Every currency is represented by a **Currency** model which by default has the following properties:

| Method | Description |

| --- | --- |

| id | Unique id of the currency |

| code | Currency’s code |

| createdAt | Date of creation |

| updatedAt | Date of last update |

Note

This model implements [CurrencyInterface](index.html#component-currency-model-currency-interface).

###### Interfaces[¶](#interfaces "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### Model Interfaces[¶](#model-interfaces "Permalink to this headline")

######## CurrencyInterface[¶](#currencyinterface "Permalink to this headline")

This interface provides you with basic management of a currency’s code,

name, exchange rate and whether the currency should be enabled or not.

Note

This interface extends [CodeAwareInterface](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Component/Model/CodeAwareInterface.php) and [TimestampableInterface](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Component/Model/TimestampableInterface.php).

####### Service Interfaces[¶](#service-interfaces "Permalink to this headline")

######## CurrenciesAwareInterface[¶](#currenciesawareinterface "Permalink to this headline")

Any container used to store, and manage currencies should implement this interface.

######## CurrencyContextInterface[¶](#currencycontextinterface "Permalink to this headline")

This interface should be implemented by a service used for managing the currency name.

It also contains the default storage key:

| Related constant | Storage key |

| --- | --- |

| STORAGE\_KEY | \_sylius\_currency |

######## CurrencyConverterInterface[¶](#currencyconverterinterface "Permalink to this headline")

This interface should be implemented by any service used to convert

the amount of money from one currency to another, according to their exchange rates.

######## CurrencyProviderInterface[¶](#currencyproviderinterface "Permalink to this headline")

This interface allows you to implement one fast service which gets

all available currencies from any container you would like.

###### UnavailableCurrencyException[¶](#unavailablecurrencyexception "Permalink to this headline")

This exception is thrown when you try converting

to a currency which is not present in the provided repository.

Note

This exception extends the [\InvalidArgumentException](https://php.net/manual/en/class.invalidargumentexception.php).

###### Learn more[¶](#learn-more "Permalink to this headline")

* [Currencies in the Sylius platform](index.html#document-book/configuration/currencies) - concept documentation

##### Inventory[¶](#inventory "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

Inventory management for PHP applications.

###### Installation[¶](#installation "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

You can install the component in 2 different ways:

* [Install it via Composer](index.html#document-components_and_bundles/components/general/using_components) (`sylius/inventory` on [Packagist](https://packagist.org/packages/sylius/inventory));

* Use the official Git repository (<https://github.com/Sylius/Inventory>).

Then, require the `vendor/autoload.php` file to enable the autoloading mechanism

provided by Composer. Otherwise, your application won’t be able to find the classes

of this Sylius component.

###### Basic Usage[¶](#basic-usage "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### Stockable Object[¶](#stockable-object "Permalink to this headline")

The first thing you should do it is implementing stockable object.

Example implementation:

```

<?php

class Product implements StockableInterface

{

    /\*\*

 \* Get stock keeping unit.

 \*

 \* @return mixed

 \*/

    public function getSku()

    {

        // TODO: Implement getSku() method.

    }

    /\*\*

 \* Get inventory displayed name.

 \*

 \* @return string

 \*/

    public function getInventoryName()

    {

        // TODO: Implement getInventoryName() method.

    }

    /\*\*

 \* Simply checks if there any stock available.

 \*

 \* @return Boolean

 \*/

    public function isInStock()

    {

        // TODO: Implement isInStock() method.

    }

    /\*\*

 \* Get stock on hold.

 \*

 \* @return integer

 \*/

    public function getOnHold()

    {

        // TODO: Implement getOnHold() method.

    }

    /\*\*

 \* Set stock on hold.

 \*

 \* @param integer

 \*/

    public function setOnHold($onHold)

    {

        // TODO: Implement setOnHold() method.

    }

    /\*\*

 \* Get stock on hand.

 \*

 \* @return integer

 \*/

    public function getOnHand()

    {

        // TODO: Implement getOnHand() method.

    }

    /\*\*

 \* Set stock on hand.

 \*

 \* @param integer $onHand

 \*/

    public function setOnHand($onHand)

    {

        // TODO: Implement setOnHand() method.

    }

}

```

####### InventoryOperator[¶](#inventoryoperator "Permalink to this headline")

The **InventoryOperator** provides basic operations on your inventory.

```

<?php

use Sylius\Component\Inventory\Operator\InventoryOperator;

use Sylius\Component\Inventory\Checker\AvailabilityChecker;

use Sylius\Component\Resource\Repository\InMemoryRepository;

$inMemoryRepository = new InMemoryRepository(); // Repository model.

$product = new Product(); // Stockable model.

$eventDispatcher; // It gives a possibility to hook before or after each operation.

// If you are not familiar with events, check the symfony Event Dispatcher.

$availabilityChecker = new AvailabilityChecker(false);

$inventoryOperator = new InventoryOperator($availabilityChecker, $eventDispatcher);

$product->getOnHand(); // Output will be 0.

$inventoryOperator->increase($product, 5);

$product->getOnHand(); // Output will be 5.

$product->getOnHold(); // Output will be 0.

$inventoryOperator->hold($product, 4);

$product->getOnHold(); // Output will be 4.

$inventoryOperator->release($product, 3);

$product->getOnHold(); // Output will be 1.

```

######## Decrease[¶](#decrease "Permalink to this headline")

```

<?php

use Sylius\Component\Inventory\Operator\InventoryOperator;

use Sylius\Component\Inventory\Checker\AvailabilityChecker;

use Doctrine\Common\Collections\ArrayCollection;

use Sylius\Component\Inventory\Model\InventoryUnit;

use Sylius\Component\Inventory\Model\InventoryUnitInterface;

$inventoryUnitRepository; // Repository model.

$product = new Product(); // Stockable model.

$eventDispatcher; // It gives possibility to hook before or after each operation.

// If you are not familiar with events. Check symfony event dispatcher.

$availabilityChecker = new AvailabilityChecker(false);

$inventoryOperator = new InventoryOperator($availabilityChecker, $eventDispatcher);

$inventoryUnit1 = new InventoryUnit();

$inventoryUnit2 = new InventoryUnit();

$inventoryUnits = new ArrayCollection();

$product->getOnHand(); // Output will be 5.

$inventoryUnit1->setStockable($product);

$inventoryUnit1->setInventoryState(InventoryUnitInterface::STATE\_SOLD);

$inventoryUnit2->setStockable($product);

$inventoryUnits->add($inventoryUnit1);

$inventoryUnits->add($inventoryUnit2);

count($inventoryUnits); // Output will be 2.

$inventoryOperator->decrease($inventoryUnits);

$product->getOnHand(); // Output will be 4.

```

Caution

All methods in **InventoryOperator** throw [InvalidArgumentException](https://php.net/manual/en/class.invalidargumentexception.php) or InsufficientStockException if an error occurs.

Hint

To understand how events work check [Symfony EventDispatcher](https://symfony.com/doc/current/components/event_dispatcher/introduction.html).

####### NoopInventoryOperator[¶](#noopinventoryoperator "Permalink to this headline")

In some cases, you may want to have unlimited inventory, this operator will allow you to do that.

Hint

This operator is based on the null object pattern. For more detailed information go to [Null Object pattern](https://en.wikipedia.org/wiki/Null_Object_pattern).

####### AvailabilityChecker[¶](#availabilitychecker "Permalink to this headline")

The **AvailabilityChecker** checks availability of a given stockable object.

To characterize an object which is an **AvailabilityChecker**, it needs to implement the [AvailabilityCheckerInterface](index.html#component-inventory-checker-availability-checker-interface).

Second parameter of the `->isStockSufficient()` method gives a possibility to check for a given quantity of a stockable.

```

<?php

use Sylius\Component\Inventory\Checker\AvailabilityChecker;

$product = new Product(); // Stockable model.

$product->getOnHand(); // Output will be 5

$product->getOnHold(); // Output will be 4

$availabilityChecker = new AvailabilityChecker(false);

$availabilityChecker->isStockAvailable($product); // Output will be true.

$availabilityChecker->isStockSufficient($product, 5); // Output will be false.

```

####### InventoryUnitFactory[¶](#inventoryunitfactory "Permalink to this headline")

The **InventoryUnitFactory** creates a collection of new inventory units.

```

<?php

use Sylius\Component\Inventory\Factory\InventoryUnitFactory;

use Sylius\Component\Inventory\Model\InventoryUnitInterface;

$inventoryUnitRepository; // Repository model.

$product = new Product(); // Stockable model.

$inventoryUnitFactory = new InventoryUnitFactory($inventoryUnitRepository);

$inventoryUnits = $inventoryUnitFactory->create($product, 10, InventoryUnitInterface::STATE\_RETURNED);

// Output will be collection of inventory units.

$inventoryUnits[0]->getStockable(); // Output will be your's stockable model.

$inventoryUnits[0]->getInventoryState(); // Output will be 'returned'.

count($inventoryUnits); // Output will be 10.

```

###### Models[¶](#models "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### InventoryUnit[¶](#inventoryunit "Permalink to this headline")

**InventoryUnit** object represents an inventory unit.

InventoryUnits have the following properties:

| Property | Description |

| --- | --- |

| id | Unique id of the inventory unit |

| stockable | Reference to any stockable unit. (Implements [StockableInterface](index.html#component-inventory-model-stockable-interface)) |

| inventoryState | State of the inventory unit (e.g. “checkout”, “sold”) |

| createdAt | Date when inventory unit was created |

| updatedAt | Date of last change |

Note

This model implements the [InventoryUnitInterface](index.html#component-inventory-model-inventory-unit-interface)

###### Interfaces[¶](#interfaces "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### Model Interfaces[¶](#model-interfaces "Permalink to this headline")

######## InventoryUnitInterface[¶](#inventoryunitinterface "Permalink to this headline")

This interface should be implemented by model representing a single InventoryUnit.

Hint

It also contains the default [State Machine](index.html#document-components_and_bundles/components/Inventory/state_machine).

Note

This interface extends [TimestampableInterface](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Component/Model/TimestampableInterface.php).

######## StockableInterface[¶](#stockableinterface "Permalink to this headline")

This interface provides basic operations for any model that can be stored.

####### Service Interfaces[¶](#service-interfaces "Permalink to this headline")

######## AvailabilityCheckerInterface[¶](#availabilitycheckerinterface "Permalink to this headline")

This interface provides methods for checking availability of stockable objects.

######## InventoryUnitFactoryInterface[¶](#inventoryunitfactoryinterface "Permalink to this headline")

This interface is implemented by services responsible for creating collection of new inventory units.

###### State Machine[¶](#state-machine "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### Inventory Unit States[¶](#inventory-unit-states "Permalink to this headline")

Sylius itself uses a complex state machine system to manage all states of the business domain.

This component has some sensible default states defined in the **InventoryUnitInterface**.

All new **InventoryUnit** instances have the state `checkout` by default, which means they are in the cart and wait for verification.

The following states are defined:

| Related constant | State | Description |

| --- | --- | --- |

| STATE\_CHECKOUT | checkout | Item is in the cart |

| STATE\_ONHOLD | onhold | Item is hold (e.g. waiting for the payment) |

| STATE\_SOLD | sold | Item has been sold and is no longer in the warehouse |

| STATE\_RETURNED | returned | Item has been sold, but returned and is in stock |

Tip

Please keep in mind that these states are just default, you can define and use your own.

If you use this component with [SyliusInventoryBundle](index.html#document-components_and_bundles/bundles/SyliusInventoryBundle/index) and Symfony, you will have full state machine configuration at your disposal.

####### Inventory Unit Transitions[¶](#inventory-unit-transitions "Permalink to this headline")

There are the following order’s transitions by default:

| Related constant | Transition |

| --- | --- |

| SYLIUS\_HOLD | hold |

| SYLIUS\_SELL | sell |

| SYLIUS\_RELEASE | release |

| SYLIUS\_RETURN | return |

There is also the default graph name included:

| Related constant | Name |

| --- | --- |

| GRAPH | sylius\_inventory\_unit |

Note

All of above transitions and the graph are constant fields in the **InventoryUnitTransitions** class.

###### Learn more[¶](#learn-more "Permalink to this headline")

* [Inventory in the Sylius platform](index.html#document-book/products/inventory) - concept documentation

##### Locale[¶](#locale "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

Managing different locales for PHP apps.

###### Installation[¶](#installation "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

You can install the component in 2 different ways:

* [Install it via Composer](index.html#document-components_and_bundles/components/general/using_components) (`sylius/locale` on [Packagist](https://packagist.org/packages/sylius/locale));

* Use the official Git repository (<https://github.com/Sylius/Locale>).

Then, require the `vendor/autoload.php` file to enable the autoloading mechanism

provided by Composer. Otherwise, your application won’t be able to find the classes

of this Sylius component.

###### Basic Usage[¶](#basic-usage "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### LocaleContext[¶](#localecontext "Permalink to this headline")

In the Locale component there are three LocaleContexts defined:

\* `CompositeLocaleContext`

\* `ImmutableLocaleContext`

\* `ProviderBasedLocaleContext`

######## CompositeLocaleContext[¶](#compositelocalecontext "Permalink to this headline")

It is a composite of different contexts available in your application, which are prioritized while being injected here (the one with highest priority is used).

It has the `getLocaleCode()` method available, that helps you to get the currently used locale.

####### LocaleProvider[¶](#localeprovider "Permalink to this headline")

The **LocaleProvider** allows you to get all available locales.

```

<?php

use Sylius\Component\Locale\Provider\LocaleProvider;

$locales = new InMemoryRepository();

$localeProvider = new LocaleProvider($locales);

$localeProvider->getAvailableLocalesCodes(); //Output will be a collection of available locales

$localeProvider->isLocaleAvailable('en'); //It will check if that locale is enabled

```

###### Models[¶](#models "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### Locale[¶](#locale "Permalink to this headline")

**Locale** represents one locale available in the application.

It uses [Symfony Intl component](https://symfony.com/doc/current/components/intl.html) to return locale name.

Locale has the following properties:

| Property | Description |

| --- | --- |

| id | Unique id of the locale |

| code | Locale’s code |

| createdAt | Date when locale was created |

| updatedAt | Date of last change |

Hint

This model has one const `STORAGE\_KEY` it is key used to store the locale in storage.

Note

This model implements the [LocaleInterface](index.html#component-locale-model-locale-interface)

###### Interfaces[¶](#interfaces "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### Model Interfaces[¶](#model-interfaces "Permalink to this headline")

######## LocaleInterface[¶](#localeinterface "Permalink to this headline")

This interface should be implemented by models representing a single **Locale**.

Note

This interface extends [CodeAwareInterface](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Component/Model/CodeAwareInterface.php) and

[TimestampableInterface](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Component/Model/TimestampableInterface.php).

######## LocalesAwareInterface[¶](#localesawareinterface "Permalink to this headline")

This interface provides basic operations for locale management.

If you want to have locales in your model just implement this interface.

####### Service Interfaces[¶](#service-interfaces "Permalink to this headline")

######## LocaleContextInterface[¶](#localecontextinterface "Permalink to this headline")

This interface is implemented by the service responsible for managing the current locale.

######## LocaleProviderInterface[¶](#localeproviderinterface "Permalink to this headline")

This interface is implemented by the service responsible for providing you with a list of available locales.

###### Learn more[¶](#learn-more "Permalink to this headline")

* [Locales in the Sylius platform](index.html#document-book/configuration/locales) - concept documentation

##### Order[¶](#order "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

E-Commerce PHP library for creating and managing sales orders.

###### Installation[¶](#installation "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

You can install the component in 2 different ways:

* [Install it via Composer](index.html#document-components_and_bundles/components/general/using_components) (`sylius/order` on [Packagist](https://packagist.org/packages/sylius/order));

* Use the official Git repository (<https://github.com/Sylius/Order>).

Then, require the `vendor/autoload.php` file to enable the autoloading mechanism

provided by Composer. Otherwise, your application won’t be able to find the classes

of this Sylius component.

###### Basic Usage[¶](#basic-usage "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### Order[¶](#order "Permalink to this headline")

Every order has 2 main identifiers, an ID and a human-friendly number. You can access those by calling `->getId()` and `->getNumber()` respectively.

The number is mutable, so you can change it by calling `->setNumber('E001')` on the order instance.

######## Order Totals[¶](#order-totals "Permalink to this headline")

Note

All money amounts in Sylius are represented as “cents” - integers.

An order has 3 basic totals, which are all persisted together with the order.

The first total is the *items total*, it is calculated as the sum of all item totals.

The second total is the *adjustments total*, you can read more about this in next chapter.

```

<?php

echo $order->getItemsTotal(); //Output will be 1900.

echo $order->getAdjustmentsTotal(); //Output will be -250.

$order->calculateTotal();

echo $order->getTotal(); //Output will be 1650.

```

The main order total is a sum of the previously mentioned values.

You can access the order total value using the `->getTotal()` method.

Recalculation of totals can happen by calling `->calculateTotal()` method, using the simplest math. It will also update the item totals.

######## Items Management[¶](#items-management "Permalink to this headline")

The collection of items (Implementing the `Doctrine\Common\Collections\Collection` interface) can be obtained using the `->getItems()`.

To add or remove items, you can simply use the `addItem` and `removeItem` methods.

```

<?php

use Sylius\Component\Order\Model\Order;

use Sylius\Component\Order\Model\OrderItem;

$order = new Order();

$item1 = new OrderItem();

$item1->setName('Super cool product');

$item1->setUnitPrice(1999); // 19.99!

$item1->setQuantity(2);

$item2 = new OrderItem();

$item2->setName('Interesting t-shirt');

$item2->setUnitPrice(2549); // 25.49!

$order->addItem($item1);

$order->addItem($item2);

$order->removeItem($item1);

```

####### Order Item[¶](#order-item "Permalink to this headline")

An order item model has only the **id** property as identifier and it has the order reference, accessible via `->getOrder()` method.

######## Order Item totals[¶](#order-item-totals "Permalink to this headline")

Just like for the order, the total is available via the same method, but the unit price is accessible using the `->getUnitPrice()`

Each item also can calculate its total, using the quantity (`->getQuantity()`) and the unit price.

```

<?php

use Sylius\Component\Order\Model\OrderItem;

$item = new OrderItem();

$item->setUnitPrice(2000);

$item->setQuantity(4);

$item->calculateTotal();

$item->getTotal(); //Output will be 8000.

```

######## Applying adjustments to OrderItem[¶](#applying-adjustments-to-orderitem "Permalink to this headline")

An OrderItem can also hold adjustments.

```

<?php

use Sylius\Component\Order\Model\OrderItem;

use Sylius\Component\Order\Model\Adjustment;

$adjustment = new Adjustment();

$adjustment->setAmount(1200);

$adjustment->setType('tax');

$item = new OrderItem();

$item->addAdjustment($adjustment);

$item->setUnitPrice(2000);

$item->setQuantity(2);

$item->calculateTotal();

$item->getTotal(); //Output will be 5200.

```

####### Adjustments[¶](#adjustments "Permalink to this headline")

######## Neutral Adjustments[¶](#neutral-adjustments "Permalink to this headline")

In some cases, you may want to use **Adjustment** just for displaying purposes.

For example, when your order items have the tax already included in the price.

Every **Adjustment** instance has the `neutral` property, which indicates if it should be counted against object total.

```

<?php

use Sylius\Component\Order\Order;

use Sylius\Component\Order\OrderItem;

use Sylius\Component\Order\Adjustment;

$order = new Order();

$tshirt = new OrderItem();

$tshirt->setUnitPrice(4999);

$shippingFees = new Adjustment();

$shippingFees->setAmount(1000);

$tax = new Adjustment();

$tax->setAmount(1150);

$tax->setNeutral(true);

$order->addItem($tshirt);

$order->addAdjustment($shippingFees);

$order->addAdjustment($tax);

$order->calculateTotal();

$order->getTotal();  // Output will be 5999.

```

######## Negative Adjustments[¶](#negative-adjustments "Permalink to this headline")

**Adjustments** can also have negative amounts, which means that they will decrease the order total by certain amount.

Let’s add a 5$ discount to the previous example.

```

<?php

use Sylius\Component\Order\Order;

use Sylius\Component\Order\OrderItem;

use Sylius\Component\Order\Adjustment;

$order = new Order();

$tshirt = new OrderItem();

$tshirt->setUnitPrice(4999);

$shippingFees = new Adjustment();

$shippingFees->setAmount(1000);

$tax = new Adjustment();

$tax->setAmount(1150);

$tax->setNeutral(true);

$discount = new Adjustment();

$discount->setAmount(-500);

$order->addItem($tshirt);

$order->addAdjustment($shippingFees);

$order->addAdjustment($tax);

$order->addAdjustment($discount);

$order->calculateTotal();

$order->getTotal(); // Output will be 5499.

```

######## Locked Adjustments[¶](#locked-adjustments "Permalink to this headline")

You can also lock an adjustment, this will ensure that it won’t be deleted from order or order item.

```

<?php

use Sylius\Component\Order\Order;

use Sylius\Component\Order\OrderItem;

use Sylius\Component\Order\Adjustment;

$order = new Order();

$tshirt = new OrderItem();

$tshirt->setUnitPrice(4999);

$shippingFees = new Adjustment();

$shippingFees->setAmount(1000);

$shippingFees->lock();

$discount = new Adjustment();

$discount->setAmount(-500);

$order->addItem($tshirt);

$order->addAdjustment($shippingFees);

$order->addAdjustment($discount);

$order->removeAdjustment($shippingFees);

$order->calculateTotal();

$order->getTotal(); // Output will be 5499.

```

###### Models[¶](#models "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### Order[¶](#order "Permalink to this headline")

**Order** object represents order.

Orders have the following properties:

| Property | Description |

| --- | --- |

| id | Unique id of the order |

| checkoutCompletedAt | The time at which checkout was completed |

| number | Number is human-friendly identifier |

| notes | Additional information about order |

| items | Collection of items |

| itemsTotal | Total value of items in order (default 0) |

| adjustments | Collection of adjustments |

| adjustmentsTotal | Total value of adjustments (default 0) |

| total | Calculated total (items + adjustments) |

| state | State of the order (e.g. “cart”, “pending”) |

| createdAt | Date when order was created |

| updatedAt | Date of last change |

Note

This model implements the [OrderInterface](index.html#component-order-model-order-interface)

####### OrderItem[¶](#orderitem "Permalink to this headline")

**OrderItem** object represents items in order.

OrderItems have the following properties:

| Property | Description |

| --- | --- |

| id | Unique id of the orderItem |

| order | Reference to Order |

| quantity | Items quantity |

| unitPrice | The price of a single unit |

| adjustments | Collection of adjustments |

| adjustmentsTotal | Total of the adjustments in orderItem |

| total | Total of the orderItem (unitPrice \* quantity + adjustmentsTotal) |

| immutable | Boolean flag of immutability |

Note

This model implements the [OrderItemInterface](index.html#component-order-model-order-item-interface)

####### OrderItemUnit[¶](#orderitemunit "Permalink to this headline")

**OrderItemUnit** object represents every single unit of order (for example `OrderItem` with quantity 5 should have 5 units).

OrderItemUnits have the following properties:

| Property | Description |

| --- | --- |

| id | Unique id of the orderItem |

| total | Total of the orderItemUnit (orderItem unitPrice + adjustmentsTotal) |

| orderItem | Reference to OrderItem |

| adjustments | Collection of adjustments |

| adjustmentsTotal | Total of the adjustments in orderItem |

Note

This model implements the [OrderItemUnitInterface](index.html#component-order-model-order-item-unit-interface)

####### Adjustment[¶](#adjustment "Permalink to this headline")

**Adjustment** object represents an adjustment to the order’s or order item’s total.

Their amount can be positive (charges - taxes, shipping fees etc.) or negative (discounts etc.).

Adjustments have the following properties:

| Property | Description |

| --- | --- |

| id | Unique id of the adjustment |

| order | Reference to Order |

| orderItem | Reference to OrderItem |

| orderItemUnit | Reference to OrderItemUnit |

| type | Type of the adjustment (e.g. “tax”) |

| label | e.g. “Clothing Tax 9%” |

| amount | Adjustment amount |

| neutral | Boolean flag of neutrality |

| locked | Adjustment lock (prevent from deletion) |

| originId | Origin id of the adjustment |

| originType | Origin type of the adjustment |

| createdAt | Date when adjustment was created |

| updatedAt | Date of last change |

Note

This model implements the [AdjustmentInterface](index.html#component-order-model-adjustment-interface)

###### Interfaces[¶](#interfaces "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### Model Interfaces[¶](#model-interfaces "Permalink to this headline")

######## OrderInterface[¶](#orderinterface "Permalink to this headline")

This interface should be implemented by model representing a single Order.

Hint

It also contains the default [State Machine](index.html#document-components_and_bundles/components/Order/state_machine).

Note

This interface extends [TimestampableInterface](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Component/Model/TimestampableInterface.php), [TimestampableInterface](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Component/Model/TimestampableInterface.php),

[AdjustableInterface](#component-order-model-adjustable-interface) and [CommentAwareInterface](#component-order-model-comment-aware-interface)

######## OrderAwareInterface[¶](#orderawareinterface "Permalink to this headline")

This interface provides basic operations for order management.

If you want to have orders in your model just implement this interface.

######## OrderItemInterface[¶](#orderiteminterface "Permalink to this headline")

This interface should be implemented by model representing a single OrderItem.

Note

This interface extends the [OrderAwareInterface](#component-order-model-order-aware-interface) and the [AdjustableInterface](#component-order-model-adjustable-interface),

######## OrderItemUnitInterface[¶](#orderitemunitinterface "Permalink to this headline")

This interface should be implemented by model representing a single OrderItemUnit.

Note

This interface extends the [AdjustableInterface](#component-order-model-adjustable-interface),

######## AdjustmentInterface[¶](#adjustmentinterface "Permalink to this headline")

This interface should be implemented by model representing a single Adjustment.

Note

This interface extends the [TimestampableInterface](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Component/Model/TimestampableInterface.php).

######## AdjustableInterface[¶](#adjustableinterface "Permalink to this headline")

This interface provides basic operations for adjustment management.

Use this interface if you want to make a model adjustable.

For example following models implement this interface:

* [Order](index.html#component-order-model-order)

* [OrderItem](index.html#component-order-model-order-item)

######## CommentInterface[¶](#commentinterface "Permalink to this headline")

This interface should be implemented by model representing a single Comment.

Note

This interface extends the [TimestampableInterface](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Component/Model/TimestampableInterface.php)

######## CommentAwareInterface[¶](#commentawareinterface "Permalink to this headline")

This interface provides basic operations for comments management.

If you want to have comments in your model just implement this interface.

######## IdentityInterface[¶](#component-order-model-identity-interface "Permalink to this headline")

This interface should be implemented by model representing a single Identity. It can be used for storing external identifications.

####### Services Interfaces[¶](#services-interfaces "Permalink to this headline")

######## OrderRepositoryInterface[¶](#orderrepositoryinterface "Permalink to this headline")

In order to decouple from storage that provides recently completed orders or check if given order’s number is already used,

you should create repository class which implements this interface.

Note

This interface extends the [RepositoryInterface](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Component/Repository/RepositoryInterface.php).

###### State Machine[¶](#state-machine "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### Order States[¶](#order-states "Permalink to this headline")

Sylius itself uses a complex state machine system to manage all states of the business domain.

This component has some sensible default states defined in the **OrderInterface**.

All new **Order** instances have the state `cart` by default, which means they are unconfirmed.

The following states are defined:

| Related constant | State | Description |

| --- | --- | --- |

| STATE\_CART | cart | Unconfirmed order, ready to add/remove items |

| STATE\_NEW | new | Confirmed order |

| STATE\_CANCELLED | cancelled | Cancelled by customer or manager |

| STATE\_FULFILLED | fulfilled | Order has been fulfilled |

Tip

Please keep in mind that these states are just default, you can define and use your own.

If you use this component with [SyliusOrderBundle](index.html#document-components_and_bundles/bundles/SyliusOrderBundle/index) and Symfony, you will have full state machine configuration at your disposal.

####### Order Transitions[¶](#order-transitions "Permalink to this headline")

There are following order’s transitions by default:

| Related constant | Transition |

| --- | --- |

| SYLIUS\_CREATE | create |

| SYLIUS\_CANCEL | cancel |

| SYLIUS\_FULFILL | fulfill |

There is also the default graph name included:

| Related constant | Name |

| --- | --- |

| GRAPH | sylius\_order |

Note

All of above transitions and the graph are constant fields in the **OrderTransitions** class.

###### Processors[¶](#processors "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

Order processors are responsible for manipulating the orders to apply different predefined adjustments or other modifications based on order state.

####### OrderProcessorInterface[¶](#orderprocessorinterface "Permalink to this headline")

You can use it when you want to create your own custom processor.

The following code applies 10% discount adjustment to orders above 100€.

```

<?php

use Sylius\Component\Order\Processor\OrderProcessorInterface;

use Sylius\Component\Order\Model\OrderInterface;

use Sylius\Component\Order\Model\Adjustment;

class DiscountByPriceOrderProcessor implements OrderProcessorInterface

{

    public function process(OrderInterface $order)

    {

        if($order->getTotal() > 10000) {

            $discount10Percent = new Adjustment();

            $discount10Percent->setAmount($order->getTotal() / 100 \* 10);

            $discount10Percent->setType('Percent Discount');

            // It would be good practice to set `label` but it's not mandatory

            $discount10Percent->setLabel('10% discount');

            $order->addAdjustment($discount10Percent);

        }

    }

}

```

####### CompositeOrderProcessor[¶](#compositeorderprocessor "Permalink to this headline")

Composite order processor works as a registry of processors, allowing to run multiple processors in priority order.

###### Learn more[¶](#learn-more "Permalink to this headline")

* [Carts & Orders in the Sylius platform](index.html#document-book/orders/index) - concept documentation

##### Payment[¶](#payment "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

PHP library which provides abstraction of payments management.

It ships with default **Payment** and **PaymentMethod** models.

Note

This component does not provide any payment gateway.

Integrate it with [Payum](https://github.com/Payum/Payum/).

###### Installation[¶](#installation "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

You can install the component in 2 different ways:

* [Install it via Composer](index.html#document-components_and_bundles/components/general/using_components) (`sylius/payment` on [Packagist](https://packagist.org/packages/sylius/payment));

* Use the official Git repository (<https://github.com/Sylius/Payment>).

Then, require the `vendor/autoload.php` file to enable the autoloading mechanism

provided by Composer. Otherwise, your application won’t be able to find the classes

of this Sylius component.

###### Models[¶](#models "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### Payment[¶](#payment "Permalink to this headline")

Every payment is represented by a **Payment** instance and has the following properties:

| Property | Description |

| --- | --- |

| id | Unique id of the payment |

| method | Payment method associated with this payment |

| currency | Payment’s currency |

| amount | Payment’s amount |

| state | Payment’s state |

| details | Payment’s details |

| createdAt | Date of creation |

| updatedAt | Date of the last update |

Note

This model implements the [PaymentInterface](index.html#component-payment-model-payment-interface).

Hint

All default payment states are available in [Payment States](index.html#component-payment-payment-states).

####### PaymentMethod[¶](#paymentmethod "Permalink to this headline")

Every method of payment is represented by a **PaymentMethod** instance and has the following properties:

| Property | Description |

| --- | --- |

| id | Unique id of the payment method |

| code | Unique code of the payment method |

| name | Payment method’s name |

| enabled | Indicate whether the payment method is enabled |

| description | Payment method’s description |

| gatewayConfig | Payment method’s gateway (and its configuration) to use |

| position | Payment method’s position among other methods |

| environment | Required app environment |

| createdAt | Date of creation |

| updatedAt | Date of the last update |

Note

This model implements the [PaymentMethodInterface](index.html#component-payment-model-payment-method-interface).

####### PaymentMethodTranslation[¶](#paymentmethodtranslation "Permalink to this headline")

This model is used to ensure that different locales have the

correct representation of the following payment properties:

| Property | Description |

| --- | --- |

| id | Unique id of the payment method |

| name | Payment method’s name |

| description | Payment method’s description |

Note

This model implements the [PaymentMethodTranslationInterface](index.html#component-payment-model-payment-method-translation-interface).

###### Interfaces[¶](#interfaces "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### Model Interfaces[¶](#model-interfaces "Permalink to this headline")

######## PaymentInterface[¶](#paymentinterface "Permalink to this headline")

This interface should be implemented by any custom model representing a payment.

Also it keeps all of the default [Payment States](index.html#component-payment-payment-states).

Note

This interface extends the [CodeAwareInterface](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Component/Model/CodeAwareInterface.php) and

[TimestampableInterface](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Component/Model/TimestampableInterface.php).

######## PaymentMethodInterface[¶](#paymentmethodinterface "Permalink to this headline")

In order to create a custom payment method class, which could be used by other

models or services from this component, it needs to implement this interface.

Note

This interface extends the [TimestampableInterface](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Component/Model/TimestampableInterface.php)

and the [PaymentMethodTranslationInterface](#component-payment-model-payment-method-translation-interface).

######## PaymentMethodsAwareInterface[¶](#paymentmethodsawareinterface "Permalink to this headline")

This interface should be implemented by any custom

storage used to store representations of the payment method.

######## PaymentMethodTranslationInterface[¶](#paymentmethodtranslationinterface "Permalink to this headline")

This interface is needed in creating a custom payment method translation class,

which then could be used by the payment method itself.

######## PaymentSourceInterface[¶](#paymentsourceinterface "Permalink to this headline")

This interface needs to be implemented by any custom payment source.

######## PaymentsSubjectInterface[¶](#paymentssubjectinterface "Permalink to this headline")

Any container which manages multiple payments should implement this interface.

####### Service Interfaces[¶](#service-interfaces "Permalink to this headline")

######## PaymentMethodRepositoryInterface[¶](#paymentmethodrepositoryinterface "Permalink to this headline")

This interface should be implemented by your custom repository,

used to handle payment method objects.

###### State Machine[¶](#state-machine "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### Payment States[¶](#payment-states "Permalink to this headline")

The following payment states are available by default:

| Related constant | State | Description |

| --- | --- | --- |

| STATE\_CART | cart | Initial; Before the subject of payment is completed |

| STATE\_NEW | new | After completion of the payment subject |

| STATE\_PROCESSING | processing | Payment which is in process of verification |

| STATE\_COMPLETED | completed | Completed payment |

| STATE\_FAILED | failed | Payment has failed |

| STATE\_CANCELLED | cancelled | Cancelled by a customer or manager |

| STATE\_REFUNDED | refunded | A completed payment which has been refunded |

| STATE\_UNKNOWN | unknown | Auxiliary state for handling external states |

Note

All the above states are constant fields in the [PaymentInterface](index.html#component-payment-model-payment-interface).

####### Payment Transitions[¶](#payment-transitions "Permalink to this headline")

The following payment transitions are available by default:

| Related constant | Transition |

| --- | --- |

| SYLIUS\_CREATE | create |

| SYLIUS\_PROCESS | process |

| SYLIUS\_COMPLETE | complete |

| SYLIUS\_FAIL | fail |

| SYLIUS\_CANCEL | cancel |

| SYLIUS\_REFUND | refund |

There’s also the default graph name included:

| Related constant | Name |

| --- | --- |

| GRAPH | sylius\_payment |

Note

All of above transitions and the graph are constant fields in the **PaymentTransitions** class.

###### Learn more[¶](#learn-more "Permalink to this headline")

* [Payments in the Sylius platform](index.html#document-book/orders/payments) - concept documentation

##### Product[¶](#product "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

Powerful products catalog for PHP applications.

###### Installation[¶](#installation "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

You can install the component in 2 different ways:

* [Install it via Composer](index.html#document-components_and_bundles/components/general/using_components) (`sylius/product` on [Packagist](https://packagist.org/packages/sylius/product)).

* Use the official Git repository (<https://github.com/Sylius/Product>).

Then, require the `vendor/autoload.php` file to enable the autoloading mechanism

provided by Composer. Otherwise, your application won’t be able to find the classes

of this Sylius component.

###### Basic Usage[¶](#basic-usage "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### Creating a product[¶](#creating-a-product "Permalink to this headline")

```

<?php

use Sylius\Component\Product\Model\Product;

$product = new Product();

$product->getCreatedAt(); // Returns the \DateTime when it was created.

```

####### Product attributes management[¶](#product-attributes-management "Permalink to this headline")

```

<?php

use Sylius\Component\Product\Model\Attribute;

use Sylius\Component\Product\Model\AttributeValue;

use Doctrine\Common\Collections\ArrayCollection;

$attribute = new Attribute();

$colorGreen = new AttributeValue();

$colorRed = new AttributeValue();

$attributes = new ArrayCollection();

$attribute->setName('Color');

$colorGreen->setValue('Green');

$colorRed->setValue('Red');

$colorGreen->setAttribute($attribute);

$colorRed->setAttribute($attribute);

$product->addAttribute($colorGreen);

$product->hasAttribute($colorGreen); // Returns true.

$product->removeAttribute($colorGreen);

$attributes->add($colorGreen);

$attributes->add($colorRed);

$product->setAttributes($attributes);

$product->hasAttributeByName('Color');

$product->getAttributeByName('Color'); // Returns $colorGreen.

$product->getAttributes(); // Returns $attributes.

```

Note

Only instances of **AttributeValue** from the [Product](index.html#document-components_and_bundles/components/Product/index)

component can be used with the [Product](index.html#component-product-model-product) model.

Hint

The `getAttributeByName` will only return the first occurrence of **AttributeValue**

assigned to the **Attribute** with specified name, the rest will be omitted.

####### Product variants management[¶](#product-variants-management "Permalink to this headline")

```

<?php

use Sylius\Component\Product\Model\Variant;

$variant = new Variant();

$availableVariant = new Variant();

$variants = new ArrayCollection();

$availableVariant->setAvailableOn(new \DateTime());

$product->hasVariants(); // return false

$product->addVariant($variant);

$product->hasVariant($variant); // returns true

$product->hasVariants(); // returns true

$product->removeVariant($variant);

$variants->add($variant);

$variants->add($availableVariant);

$product->setVariants($variants);

$product->getVariants(); // Returns an array containing $variant and $availableVariant.

```

Note

Only instances of **Variant** from the [Product](index.html#document-components_and_bundles/components/Product/index) component

can be used with the [Product](index.html#component-product-model-product) model.

####### Product options management[¶](#product-options-management "Permalink to this headline")

```

<?php

use Sylius\Component\Product\Model\Option;

$firstOption = new Option();

$secondOption = new Option();

$options = new ArrayCollection();

$product->addOption($firstOption);

$product->hasOption($firstOption); // Returns true.

$product->removeOption($firstOption);

$options->add($firstOption);

$options->add($secondOption);

$product->setOptions($options);

$product->hasOptions(); // Returns true.

$product->getOptions(); // Returns an array containing all inserted options.

```

###### Models[¶](#models "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### Product[¶](#product "Permalink to this headline")

The **Product** model represents every unique product in the catalog.

By default it contains the following properties:

| Property | Description |

| --- | --- |

| id | Unique id of the product |

| name | Product’s name taken from the `ProductTranslation` |

| slug | Product’s urlized name taken from the `ProductTranslation` |

| description | Product’s description taken from the `ProductTranslation` |

| metaKeywords | Product’s meta keywords taken from the `ProductTranslation` |

| metaDescription | Product’s meta description taken from the `ProductTranslation` |

| attributes | Attributes assigned to this product |

| variants | Variants assigned to this product |

| options | Options assigned to this product |

| createdAt | Product’s date of creation |

| updatedAt | Product’s date of update |

Note

This model uses the [TranslatableTrait](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Component/Model/TranslatableTrait.php)

and implements the [ProductInterface](index.html#component-product-model-product-interface).

####### ProductTranslation[¶](#producttranslation "Permalink to this headline")

This model is responsible for keeping a translation

of product’s simple properties according to given locale.

By default it has the following properties:

| Property | Description |

| --- | --- |

| id | Unique id of the product translation |

Note

This model extends the [AbstractTranslation](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Component/Model/AbstractTranslation.php) class

and implements the [ProductTranslationInterface](index.html#component-product-model-product-translation-interface).

####### AttributeValue[¶](#attributevalue "Permalink to this headline")

This **AttributeValue** extension ensures that it’s **subject**

is an instance of the [ProductInterface](index.html#component-product-model-product-interface).

Note

This model extends the [AttributeValue](index.html#component-attribute-model-attribute-value)

and implements the [AttributeValueInterface](index.html#component-product-model-attribute-value-interface).

####### Variant[¶](#variant "Permalink to this headline")

This **Variant** extension ensures that it’s **object**

is an instance of the [ProductInterface](index.html#component-product-model-product-interface)

and provides an additional property:

| Property | Description |

| --- | --- |

| availableOn | The date indicating when a product variant is available |

Note

This model implements the [ProductVariantInterface](index.html#component-product-model-variant-interface).

###### Interfaces[¶](#interfaces "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### Model Interfaces[¶](#model-interfaces "Permalink to this headline")

######## ProductInterface[¶](#productinterface "Permalink to this headline")

This interface should be implemented by models characterizing a product.

Note

This interface extends [SlugAwareInterface](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Component/Model/SlugAwareInterface.php),

[TimestampableInterface](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Component/Model/TimestampableInterface.php)

and [ProductTranslationInterface](#component-product-model-product-translation-interface).

######## ProductTranslationInterface[¶](#producttranslationinterface "Permalink to this headline")

This interface should be implemented by models used for storing a single translation of product fields.

Note

This interface extends the [SlugAwareInterface](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Component/Model/SlugAwareInterface.php).

######## AttributeValueInterface[¶](#attributevalueinterface "Permalink to this headline")

This interfaces should be implemented by models used

to bind an attribute and a value to a specific product.

Note

This interface extends the [AttributeValueInterface](index.html#component-attribute-model-attribute-value-interface).

######## ProductVariantInterface[¶](#productvariantinterface "Permalink to this headline")

This interface should be implemented by models binding a product with a specific combination of attributes.

###### Learn more[¶](#learn-more "Permalink to this headline")

* [Products in the Sylius platform](index.html#document-book/products/index) - concept documentation

##### Promotion[¶](#promotion "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

Super-flexible promotions system with support of complex rules and actions. Coupon codes included!

###### Installation[¶](#installation "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

You can install the component in 2 different ways:

* [Install it via Composer](index.html#document-components_and_bundles/components/general/using_components) (`sylius/promotion` on [Packagist](https://packagist.org/packages/sylius/promotion));

* Use the official Git repository (<https://github.com/Sylius/Promotion>).

Then, require the `vendor/autoload.php` file to enable the autoloading mechanism

provided by Composer. Otherwise, your application won’t be able to find the classes

of this Sylius component.

###### Basic Usage[¶](#basic-usage "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

In order to benefit from the component’s features at first you need to create a basic class that will implement

the [PromotionSubjectInterface](index.html#component-promotion-model-promotion-subject-interface). Let’s assume that you would like to

have a system that applies promotions on Tickets. Your **Ticket** class therefore will implement the

[CountablePromotionSubjectInterface](index.html#component-promotion-model-promotion-countable-subject-interface) to give you an ability to count the subjects

for promotion application purposes.

```

<?php

namespace App\Entity;

use Doctrine\Common\Collections\Collection;

use Doctrine\Common\Collections\ArrayCollection;

use Sylius\Component\Promotion\Model\CountablePromotionSubjectInterface;

use Sylius\Component\Promotion\Model\PromotionSubjectInterface;

use Sylius\Component\Promotion\Model\PromotionInterface;

class Ticket implements CountablePromotionSubjectInterface

{

    /\*\*

 \* @var int

 \*/

    private $quantity;

    /\*\*

 \* @var Collection

 \*/

    private $promotions;

    /\*\*

 \* @var int

 \*/

    private $unitPrice;

    public function \_\_construct()

    {

        $this->promotions = new ArrayCollection();

    }

    /\*\*

 \* @return int

 \*/

    public function getQuantity()

    {

        return $this->quantity;

    }

    /\*\*

 \* @param int $quantity

 \*/

    public function setQuantity($quantity)

    {

        $this->quantity = $quantity;

    }

    /\*\*

 \* {@inheritdoc}

 \*/

    public function getPromotions()

    {

        return $this->promotions;

    }

    /\*\*

 \* {@inheritdoc}

 \*/

    public function hasPromotion(PromotionInterface $promotion)

    {

        return $this->promotions->contains($promotion);

    }

    /\*\*

 \* {@inheritdoc}

 \*/

    public function getPromotionSubjectTotal()

    {

        //implementation

    }

    /\*\*

 \* {@inheritdoc}

 \*/

    public function addPromotion(PromotionInterface $promotion)

    {

        if (!$this->hasPromotion($promotion)) {

            $this->promotions->add($promotion);

        }

    }

    /\*\*

 \* {@inheritdoc}

 \*/

    public function removePromotion(PromotionInterface $promotion)

    {

        if($this->hasPromotion($promotion))

        {

            $this->promotions->removeElement($promotion);

        }

    }

    /\*\*

 \* {@inheritdoc}

 \*/

    public function getPromotionSubjectCount()

    {

        return $this->getQuantity();

    }

    /\*\*

 \* @return int

 \*/

    public function getUnitPrice()

    {

        return $this->unitPrice;

    }

    /\*\*

 \* @param int $price

 \*/

    public function setUnitPrice($price)

    {

        $this->unitPrice = $price;

    }

    /\*\*

 \* @return int

 \*/

    public function getTotal()

    {

        return $this->getUnitPrice() \* $this->getQuantity();

    }

}

```

####### PromotionProcessor[¶](#promotionprocessor "Permalink to this headline")

The component provides us with a **PromotionProcessor** which checks all rules of a subject

and applies configured actions if rules are eligible.

```

<?php

use Sylius\Component\Promotion\Processor\PromotionProcessor;

use App\Entity\Ticket;

/\*\*

 \* @param PromotionRepositoryInterface $repository

 \* @param PromotionEligibilityCheckerInterface $checker

 \* @param PromotionApplicatorInterface $applicator

 \*/

$processor = new PromotionProcessor($repository, $checker, $applicator);

$subject = new Ticket();

$processor->process($subject);

```

Note

It implements the [PromotionProcessorInterface](index.html#component-promotion-processor-promotion-processor-interface).

####### CompositePromotionEligibilityChecker[¶](#compositepromotioneligibilitychecker "Permalink to this headline")

The Promotion component provides us with a delegating service - the **CompositePromotionEligibilityChecker** that checks if the promotion rules are eligible for a given subject.

Below you can see how it works:

Warning

Remember! That before you start using rule checkers you need to have two Registries - rule checker registry and promotion action registry.

In these you have to register your rule checkers and promotion actions. You will also need working services - ‘item\_count’ rule checker service for our example:

```

<?php

use Sylius\Component\Promotion\Model\Promotion;

use Sylius\Component\Promotion\Model\PromotionAction;

use Sylius\Component\Promotion\Model\PromotionRule;

use Sylius\Component\Promotion\Checker\CompositePromotionEligibilityChecker;

use App\Entity\Ticket;

$checkerRegistry = new ServiceRegistry('Sylius\Component\Promotion\Checker\RuleCheckerInterface');

$actionRegistry = new ServiceRegistry('Sylius\Component\Promotion\Model\PromotionActionInterface');

$ruleRegistry = new ServiceRegistry('Sylius\Component\Promotion\Model\PromotionRuleInterface');

$dispatcher = new EventDispatcher();

/\*\*

 \* @param ServiceRegistryInterface $registry

 \* @param EventDispatcherInterface $dispatcher

 \*/

$checker = new CompositePromotionEligibilityChecker($checkerRegistry, $dispatcher);

$itemCountChecker = new ItemCountRuleChecker();

$checkerRegistry->register('item\_count', $itemCountChecker);

// Let's create a new promotion

$promotion = new Promotion();

$promotion->setName('Test');

// And a new action for that promotion, that will give a fixed discount of 10

$action = new PromotionAction();

$action->setType('fixed\_discount');

$action->setConfiguration(array('amount' => 10));

$action->setPromotion($promotion);

$actionRegistry->register('fixed\_discount', $action);

// That promotion will also have a rule - works for item amounts over 2

$rule = new PromotionRule();

$rule->setType('item\_count');

$configuration = array('count' => 2);

$rule->setConfiguration($configuration);

$ruleRegistry->register('item\_count', $rule);

$promotion->addRule($rule);

// Now we need an object that implements the PromotionSubjectInterface

// so we will use our custom Ticket class.

$subject = new Ticket();

$subject->addPromotion($promotion);

$subject->setQuantity(3);

$subject->setUnitPrice(10);

$checker->isEligible($subject, $promotion); // Returns true

```

Note

It implements the [PromotionEligibilityCheckerInterface](index.html#component-promotion-checker-promotion-eligibility-checker-interface).

####### PromotionApplicator[¶](#promotionapplicator "Permalink to this headline")

In order to automate the process of promotion application the component provides us with a Promotion Applicator,

which is able to apply and revert single promotions on a subject implementing the **PromotionSubjectInterface**.

```

<?php

use Sylius\Component\Promotion\PromotionAction\PromotionApplicator;

use Sylius\Component\Promotion\Model\Promotion;

use Sylius\Component\Registry\ServiceRegistry;

use App\Entity\Ticket;

// In order for the applicator to work properly you need to have your actions created and registered before.

$registry = new ServiceRegistry('Sylius\Component\Promotion\Model\PromotionActionInterface');

$promotionApplicator = new PromotionApplicator($registry);

$promotion = new Promotion();

$subject = new Ticket();

$subject->addPromotion($promotion);

$promotionApplicator->apply($subject, $promotion);

$promotionApplicator->revert($subject, $promotion);

```

Note

It implements the [PromotionApplicatorInterface](index.html#component-promotion-action-promotion-applicator-interface).

####### PromotionCouponGenerator[¶](#promotioncoupongenerator "Permalink to this headline")

In order to automate the process of coupon generation the component provides us with a Coupon Generator.

```

<?php

use Sylius\Component\Promotion\Model\Promotion;

use Sylius\Component\Promotion\Generator\PromotionCouponGeneratorInstruction;

use Sylius\Component\Promotion\Generator\PromotionCouponGenerator;

$promotion = new Promotion();

$instruction = new PromotionCouponGeneratorInstruction(); // $amount = 5 by default

/\*\*

 \* @param RepositoryInterface $repository

 \* @param EntityManagerInterface $manager

 \*/

$generator = new PromotionCouponGenerator($repository, $manager);

//This will generate and persist 5 coupons into the database

//basing on the instruction provided for the given promotion object

$generator->generate($promotion, $instruction);

// We can also generate one unique code, and assign it to a new Coupon.

$code = $generator->generateUniqueCode();

$coupon = new Coupon();

$coupon->setCode($code);

```

###### Checkers[¶](#checkers "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### ItemCountRuleChecker[¶](#itemcountrulechecker "Permalink to this headline")

You can use it when your subject implements the [CountablePromotionSubjectInterface](index.html#component-promotion-model-promotion-countable-subject-interface):

```

<?php

$itemCountChecker = new ItemCountRuleChecker();

// a Subject that implements the CountablePromotionSubjectInterface

$subject->setQuantity(3);

$configuration = array('count' => 2);

$itemCountChecker->isEligible($subject, $configuration); // returns true

```

####### ItemTotalRuleChecker[¶](#itemtotalrulechecker "Permalink to this headline")

If your subject implements the [PromotionSubjectInterface](index.html#component-promotion-model-promotion-subject-interface) you can use it with this checker.

```

<?php

$itemTotalChecker = new ItemTotalRuleChecker();

// a Subject that implements the PromotionSubjectInterface

// Let's assume the subject->getSubjectItemTotal() returns 199

$configuration = array('amount' => 199);

$itemTotalChecker->isEligible($subject, $configuration); // returns true

```

###### Models[¶](#models "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### Promotion[¶](#promotion "Permalink to this headline")

The promotion is represented by a **Promotion** instance. It has the following properties as default:

| Property | Description |

| --- | --- |

| id | Unique id of the promotion |

| code | Unique code of the promotion |

| name | Promotion’s name |

| description | Promotion’s description |

| priority | When exclusive, promotion with top priority will be applied |

| exclusive | Cannot be applied together with other promotions |

| usageLimit | Promotion’s usage limit |

| used | Number of times this coupon has been used |

| startsAt | Start date |

| endsAt | End date |

| couponBased | Whether this promotion is triggered by a coupon |

| coupons | Associated coupons |

| rules | Associated rules |

| actions | Associated actions |

| createdAt | Date of creation |

| updatedAt | Date of update |

Note

This model implements the [PromotionInterface](index.html#component-promotion-model-promotion-interface) .

####### Coupon[¶](#coupon "Permalink to this headline")

The coupon is represented by a **Coupon** instance. It has the following properties as default:

| Property | Description |

| --- | --- |

| id | Unique id of the coupon |

| code | Coupon’s code |

| usageLimit | Coupon’s usage limit |

| used | Number of times the coupon has been used |

| promotion | Associated promotion |

| expiresAt | Expiration date |

| createdAt | Date of creation |

| updatedAt | Date of update |

Note

This model implements the [CouponInterface](index.html#component-promotion-model-coupon-interface).

####### PromotionRule[¶](#promotionrule "Permalink to this headline")

The promotion rule is represented by a **PromotionRule** instance. PromotionRule is a requirement that has to be satisfied by the promotion subject.

It has the following properties as default:

| Property | Description |

| --- | --- |

| id | Unique id of the coupon |

| type | Rule’s type |

| configuration | Rule’s configuration |

| promotion | Associated promotion |

Note

This model implements the [PromotionRuleInterface](index.html#component-promotion-model-rule-interface).

####### PromotionAction[¶](#promotionaction "Permalink to this headline")

The promotion action is represented by an **PromotionAction** instance. PromotionAction takes place if the rules of a promotion are satisfied.

It has the following properties as default:

| Property | Description |

| --- | --- |

| id | Unique id of the action |

| type | Rule’s type |

| configuration | Rule’s configuration |

| promotion | Associated promotion |

Note

This model implements the [PromotionActionInterface](index.html#component-promotion-model-action-interface).

###### Interfaces[¶](#interfaces "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### Model Interfaces[¶](#model-interfaces "Permalink to this headline")

######## PromotionSubjectInterface[¶](#promotionsubjectinterface "Permalink to this headline")

To characterize an object with attributes and options from a promotion, the object class needs to implement

the **PromotionSubjectInterface**.

######## PromotionInterface[¶](#promotioninterface "Permalink to this headline")

This interface should be implemented by models representing a **Promotion**.

Note

This interface extends the [CodeAwareInterface](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Component/Model/CodeAwareInterface.php) and [TimestampableInterface](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Component/Model/TimestampableInterface.php).

######## PromotionActionInterface[¶](#promotionactioninterface "Permalink to this headline")

This interface should be implemented by models representing an **PromotionAction**.

An **PromotionActionInterface** has two defined types by default:

| Related constant | Type |

| --- | --- |

| TYPE\_FIXED\_DISCOUNT | fixed\_discount |

| TYPE\_PERCENTAGE\_DISCOUNT | percentage\_discount |

######## CouponInterface[¶](#couponinterface "Permalink to this headline")

This interface should be implemented by models representing a **Coupon**.

Note

This interface extends the [CodeAwareInterface](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Component/Model/CodeAwareInterface.php)

and the [TimestampableInterface](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Component/Model/TimestampableInterface.php).

######## PromotionRuleInterface[¶](#promotionruleinterface "Permalink to this headline")

This interface should be implemented by models representing a **PromotionRule**.

A **PromotionRuleInterface** has two defined types by default:

| Related constant | Type |

| --- | --- |

| TYPE\_ITEM\_TOTAL | item\_total |

| TYPE\_ITEM\_COUNT | item\_count |

######## CountablePromotionSubjectInterface[¶](#countablepromotionsubjectinterface "Permalink to this headline")

To be able to count the object’s promotion subjects, the object class needs to implement

the `CountablePromotionSubjectInterface`.

Note

This interface extends the [PromotionSubjectInterface](#component-promotion-model-promotion-subject-interface).

######## PromotionCouponAwarePromotionSubjectInterface[¶](#promotioncouponawarepromotionsubjectinterface "Permalink to this headline")

To make the object able to get its associated coupon, the object class needs to implement

the `PromotionCouponAwarePromotionSubjectInterface`.

Note

This interface extends the [PromotionSubjectInterface](#component-promotion-model-promotion-subject-interface).

######## PromotionCouponsAwareSubjectInterface[¶](#promotioncouponsawaresubjectinterface "Permalink to this headline")

To make the object able to get its associated coupons collection, the object class needs to implement

the `PromotionCouponsAwareSubjectInterface`.

Note

This interface extends the [PromotionSubjectInterface](#component-promotion-model-promotion-subject-interface).

####### Services Interfaces[¶](#services-interfaces "Permalink to this headline")

######## PromotionEligibilityCheckerInterface[¶](#promotioneligibilitycheckerinterface "Permalink to this headline")

Services responsible for checking the promotions eligibility on the promotion subjects should implement this interface.

######## RuleCheckerInterface[¶](#rulecheckerinterface "Permalink to this headline")

Services responsible for checking the rules eligibility should implement this interface.

######## PromotionApplicatorInterface[¶](#promotionapplicatorinterface "Permalink to this headline")

Service responsible for applying promotions in your system should implement this interface.

######## PromotionProcessorInterface[¶](#promotionprocessorinterface "Permalink to this headline")

Service responsible for checking all rules and applying configured actions if rules are eligible in your system should implement this interface.

######## PromotionRepositoryInterface[¶](#promotionrepositoryinterface "Permalink to this headline")

In order to be able to find active promotions in your system you should create a repository class which implements this interface.

Note

This interface extends the [RepositoryInterface](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Component/Repository/RepositoryInterface.php).

######## PromotionCouponGeneratorInterface[¶](#promotioncoupongeneratorinterface "Permalink to this headline")

In order to automate the process of coupon generation your system needs to have a service that will implement this interface.

######## PromotionActionCommandInterface[¶](#promotionactioncommandinterface "Permalink to this headline")

This interface should be implemented by services that execute actions on the promotion subjects.

###### Learn more[¶](#learn-more "Permalink to this headline")

* [Cart Promotions in the Sylius platform](index.html#document-book/orders/cart-promotions) - concept documentation

##### Shipping[¶](#shipping "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

Shipments and shipping methods management for PHP E-Commerce applications. It contains flexible calculators system for computing the shipping costs.

###### Installation[¶](#installation "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

You can install the component in 2 different ways:

* [Install it via Composer](index.html#document-components_and_bundles/components/general/using_components) (`sylius/shipping` on [Packagist](https://packagist.org/packages/sylius/shipping));

* Use the official Git repository (<https://github.com/Sylius/Shipping>).

Then, require the `vendor/autoload.php` file to enable the autoloading mechanism

provided by Composer. Otherwise, your application won’t be able to find the classes

of this Sylius component.

###### Basic Usage[¶](#basic-usage "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

In all examples is used an exemplary class implementing **ShippableInterface**, which looks like:

```

<?php

declare(strict\_types=1);

use Sylius\Component\Shipping\Model\ShippableInterface;

use Sylius\Component\Shipping\Model\ShippingCategoryInterface;

class Wardrobe implements ShippableInterface

{

    /\*\*

 \* @var ShippingCategoryInterface

 \*/

    private $category;

    /\*\*

 \* @var float

 \*/

    private $weight;

    /\*\*

 \* @var float

 \*/

    private $volume;

    /\*\*

 \* @var float

 \*/

    private $width;

    /\*\*

 \* @var float

 \*/

    private $height;

    /\*\*

 \* @var float

 \*/

    private $depth;

    /\*\*

 \* {@inheritdoc}

 \*/

    public function getShippingWeight(): float

    {

        return $this->weight;

    }

    /\*\*

 \* {@inheritdoc}

 \*/

    public function setShippingWeight(float $weight): void

    {

        $this->weight = $weight;

    }

    /\*\*

 \* {@inheritdoc}

 \*/

    public function getShippingVolume(): float

    {

        return $this->volume;

    }

    /\*\*

 \* @param int $volume

 \*/

    public function setShippingVolume(float $volume)

    {

        $this->volume = $volume;

    }

    /\*\*

 \* {@inheritdoc}

 \*/

    public function getShippingWidth(): float

    {

        return $this->width;

    }

    /\*\*

 \* {@inheritdoc}

 \*/

    public function setShippingWidth(float $width)

    {

        $this->width = $width;

    }

    /\*\*

 \* {@inheritdoc}

 \*/

    public function getShippingHeight(): float

    {

        return $this->height;

    }

    /\*\*

 \* {@inheritdoc}

 \*/

    public function setShippingHeight(float $height)

    {

        $this->height = $height;

    }

    /\*\*

 \* {@inheritdoc}

 \*/

    public function getShippingDepth(): float

    {

        return $this->depth;

    }

    /\*\*

 \* {@inheritdoc}

 \*/

    public function setShippingDepth(float $depth)

    {

        $this->depth = $depth;

    }

    /\*\*

 \* {@inheritdoc}

 \*/

    public function getShippingCategory(): ShippingCategoryInterface

    {

        return $this->category;

    }

    /\*\*

 \* {@inheritdoc}

 \*/

    public function setShippingCategory(ShippingCategoryInterface $category)

    {

        $this->category = $category;

    }

}

```

####### Shipping Category[¶](#shipping-category "Permalink to this headline")

Every shipping category has three identifiers, an ID, code and name. You can access those by calling `->getId()`, `->getCode()` and `->getName()`

methods respectively. The name is mutable, so you can change them by calling and `->setName('Regular')` on the shipping category instance.

####### Shipping Method[¶](#shipping-method "Permalink to this headline")

Every shipping method has three identifiers, an ID code and name. You can access those by calling `->getId()`, `->getCode()` and `->getName()`

methods respectively. The name is mutable, so you can change them by calling `->setName('FedEx')` on the shipping method instance.

######## Setting Shipping Category[¶](#setting-shipping-category "Permalink to this headline")

Every shipping method can have shipping category. You can simply set or unset it by calling `->setCategory()`.

```

<?php

use Sylius\Component\Shipping\Model\ShippingMethod;

use Sylius\Component\Shipping\Model\ShippingCategory;

use Sylius\Component\Shipping\Model\ShippingMethodInterface;

$shippingCategory = new ShippingCategory();

$shippingCategory->setName('Regular'); // Regular weight items

$shippingMethod = new ShippingMethod();

$shippingMethod->setCategory($shippingCategory); //default null, detach

$shippingMethod->getCategory(); // Output will be ShippingCategory object

$shippingMethod->setCategory(null);

```

####### Shipping Method Translation[¶](#shipping-method-translation "Permalink to this headline")

**ShippingMethodTranslation** allows shipping method’s name translation according to given locales. To see how to use translation please go to [ResourceBundle documentation](https://github.com/Sylius/SyliusResourceBundle/blob/master/docs/index.md).

####### Shipment Item[¶](#shipment-item "Permalink to this headline")

You can use a **ShippingItem** for connecting a shippable object with a proper **Shipment**.

Note that a **ShippingItem** can exist without a **Shipment** assigned.

```

<?php

use Sylius\Component\Shipping\Model\Shipment;

use Sylius\Component\Shipping\Model\ShipmentItem;

use Sylius\Component\Shipping\Model\ShipmentInterface;

$shipment = new Shipment();

$wardrobe = new Wardrobe();

$shipmentItem = new ShipmentItem();

$shipmentItem->setShipment($shipment);

$shipmentItem->getShipment(); // returns shipment object

$shipmentItem->setShipment(null);

$shipmentItem->setShippable($wardrobe);

$shipmentItem->getShippable(); // returns shippable object

$shipmentItem->getShippingState(); // returns const STATE\_READY

$shipmentItem->setShippingState(ShipmentInterface::STATE\_SOLD);

```

####### Shipment[¶](#shipment "Permalink to this headline")

Every **Shipment** can have the types of state defined in the **ShipmentInterface** and the **ShippingMethod**,

which describe the way of delivery.

```

<?php

use Sylius\Component\Shipping\Model\ShippingMethod;

use Sylius\Component\Shipping\Model\Shipment;

use Sylius\Component\Shipping\Model\ShipmentInterface;

$shippingMethod = new ShippingMethod();

$shipment = new Shipment();

$shipment->getState(); // returns const checkout

$shipment->setState(ShipmentInterface::STATE\_CANCELLED);

$shipment->setMethod($shippingMethod);

$shipment->getMethod();

```

######## Adding shipment item[¶](#adding-shipment-item "Permalink to this headline")

You can add many shipment items to shipment, which connect shipment with shippable object.

```

<?php

use Sylius\Component\Shipping\Model\Shipment;

use Sylius\Component\Shipping\Model\ShipmentItem;

$shipmentItem = new ShipmentItem();

$shipment = new Shipment();

$shipment->addItem($shipmentItem);

$shipment->hasItem($shipmentItem); // returns true

$shipment->getItems(); // returns collection of shipment items

$shipment->getShippingItemCount(); // returns 1

$shipment->removeItem($shipmentItem);

```

######## Tracking shipment[¶](#tracking-shipment "Permalink to this headline")

You can also define tracking code for your shipment:

```

<?php

use Sylius\Component\Shipping\Model\Shipment;

$shipment->isTracked();// returns false

$shipment->setTracking('5346172074');

$shipment->getTracking(); // returns 5346172074

$shipment->isTracked();// returns true

```

####### RuleCheckerInterface[¶](#rulecheckerinterface "Permalink to this headline")

This example shows how use an exemplary class implementing **RuleCheckerInterface**.

```

<?php

use Sylius\Component\Shipping\Model\Shipment;

use Sylius\Component\Shipping\Model\ShipmentItem;

use Sylius\Component\Shipping\Model\Rule;

use Sylius\Component\Shipping\Checker\ItemCountRuleChecker;

$rule = new Rule();

$rule->setConfiguration(array('count' => 5, 'equal' => true));

$wardrobe = new Wardrobe();

$shipmentItem = new ShipmentItem();

$shipmentItem->setShippable($wardrobe);

$shipment = new Shipment();

$shipment->addItem($shipmentItem);

$ruleChecker = new ItemCountRuleChecker();

$ruleChecker->isEligible($shipment, $rule->getConfiguration()); // returns false, because

// quantity of shipping item in shipment is smaller than count from rule's configuration

```

Hint

You can read more about each of the available checkers in the [Checkers](index.html#document-components_and_bundles/components/Shipping/checkers) chapter.

####### Delegating calculation to correct calculator instance[¶](#delegating-calculation-to-correct-calculator-instance "Permalink to this headline")

**DelegatingCalculator** class delegates the calculation of charge for particular shipping subject to a correct calculator instance,

based on the name defined on the shipping method. It uses **ServiceRegistry** to keep all calculators registered inside

container. The calculators are retrieved by name.

```

<?php

use Sylius\Component\Shipping\Model\ShippingMethod;

use Sylius\Component\Shipping\Calculator\DefaultCalculators;

use Sylius\Component\Shipping\Calculator\PerItemRateCalculator;

use Sylius\Component\Shipping\Calculator\FlexibleRateCalculator;

use Sylius\Component\Shipping\Model\Shipment;

use Sylius\Component\Shipping\Model\ShipmentItem;

use Sylius\Component\Shipping\Calculator\DelegatingCalculator;

use Sylius\Component\Registry\ServiceRegistry;

$configuration = array(

    'first\_item\_cost'       => 1000,

    'additional\_item\_cost'  => 200,

    'additional\_item\_limit' => 2

);

$shippingMethod = new ShippingMethod();

$shippingMethod->setConfiguration($configuration);

$shippingMethod->setCalculator(DefaultCalculators::FLEXIBLE\_RATE);

$shipmentItem = new ShipmentItem();

$shipment = new Shipment();

$shipment->setMethod($shippingMethod);

$shipment->addItem($shipmentItem);

$flexibleRateCalculator = new FlexibleRateCalculator();

$perItemRateCalculator = new PerItemRateCalculator();

$calculatorRegistry = new ServiceRegistry(CalculatorInterface::class);

$calculatorRegistry->register(DefaultCalculators::FLEXIBLE\_RATE, $flexibleRateCalculator);

$calculatorRegistry->register(DefaultCalculators::PER\_ITEM\_RATE, $perItemRateCalculator);

$delegatingCalculators = new DelegatingCalculator($calculatorRegistry);

$delegatingCalculators->calculate($shipment); // returns 1000

$configuration2 = array('amount' => 200);

$shippingMethod2 = new ShippingMethod();

$shippingMethod2->setConfiguration($configuration2);

$shippingMethod2->setCalculator(DefaultCalculators::PER\_ITEM\_RATE);

$shipment->setMethod($shippingMethod2);

$delegatingCalculators->calculate($shipment); // returns 200

```

Caution

The method `->register()` and `->get()` used in `->calculate` throw [InvalidArgumentException](https://php.net/manual/en/class.invalidargumentexception.php).

The method `->calculate` throws UndefinedShippingMethodException when given shipment does not have a shipping method defined.

Hint

You can read more about each of the available calculators in the [Calculators](index.html#document-components_and_bundles/components/Shipping/calculators) chapter.

####### Resolvers[¶](#resolvers "Permalink to this headline")

######## ShippingMethodsResolver[¶](#shippingmethodsresolver "Permalink to this headline")

Sylius has flexible system for displaying the shipping methods available for given shippables (subjects which implement

**ShippableInterface**), which is base on **ShippingCategory** objects and category requirements. The requirements are constant

default defined in **ShippingMethodInterface**. To provide information about the number of allowed methods it use **ShippingMethodResolver**.

First you need to create a few instances of **ShippingCategory** class:

```

<?php

use Sylius\Component\Shipping\Model\ShippingCategory;

$shippingCategory = new ShippingCategory();

$shippingCategory->setName('Regular');

$shippingCategory1 = new ShippingCategory();

$shippingCategory1->setName('Light');

```

Next you have to create a repository w which holds a few instances of **ShippingMethod**. An InMemoryRepository,

which holds a collection of **ShippingMethod** objects, was used. The configuration is shown below:

```

<?php

// ...

// notice:

// $categories = array($shippingCategory, $shippingCategory1);

$firstMethod = new ShippingMethod();

$firstMethod->setCategory($categories[0]);

$secondMethod = new ShippingMethod();

$secondMethod->setCategory($categories[1]);

$thirdMethod = new ShippingMethod();

$thirdMethod->setCategory($categories[1]);

// ...

```

Finally you can create a method resolver:

```

<?php

use Sylius\Component\Shipping\Model\ShippingCategory;

use Sylius\Component\Shipping\Model\Shipment;

use Sylius\Component\Shipping\Model\ShipmentItem;

use Sylius\Component\Shipping\Model\RuleInterface;

use Sylius\Component\Shipping\Checker\Registry\RuleCheckerRegistry;

use Sylius\Component\Shipping\Checker\ItemCountRuleChecker;

use Sylius\Component\Shipping\Resolver\ShippingMethodsResolver;

use Sylius\Component\Shipping\Checker\ShippingMethodEligibilityChecker;

$ruleCheckerRegistry = new RuleCheckerRegistry();

$methodEligibilityChecker = new shippingMethodEligibilityChecker($ruleCheckerRegistry);

$shippingRepository = new InMemoryRepository(); //it has collection of shipping methods

$wardrobe = new Wardrobe();

$wardrobe->setShippingCategory($shippingCategory);

$wardrobe2 = new Wardrobe();

$wardrobe2->setShippingCategory($shippingCategory1);

$shipmentItem = new ShipmentItem();

$shipmentItem->setShippable($wardrobe);

$shipmentItem2 = new ShipmentItem();

$shipmentItem2->setShippable($wardrobe2);

$shipment = new Shipment();

$shipment->addItem($shipmentItem);

$shipment->addItem($shipmentItem2);

$methodResolver = new ShippingMethodsResolver($shippingRepository, $methodEligibilityChecker);

$methodResolver->getSupportedMethods($shipment);

```

The `->getSupportedMethods($shipment)` method return the number of methods allowed for shipment object.

There are a few possibilities:

1. All shippable objects and all ShippingMethod have category *Regular*. The returned number will be 3.

2. All ShippingMethod and one shippable object have category *Regular*. Second shippable object has category *Light*. The returned number will be 3.

3. Two ShippingMethod and one shippable object have category *Regular*. Second shippable object and one ShippingMethod have category *Light*. The returned number will be 3.

4. Two ShippingMethod and one shippable object have category *Regular*. Second shippable object and second ShippingMethod have category *Light*. The second Shipping category sets the category requirements as CATEGORY\_REQUIREMENT\_MATCH\_NONE. The returned number will be 2.

5. Two ShippingMethod and all shippable objects have category *Regular*. Second ShippingMethod has category *Light*. The second Shipping category sets the category requirements as CATEGORY\_REQUIREMENT\_MATCH\_NONE. The returned number will be 3.

6. Two ShippingMethod and one shippable object have category *Regular*. Second shippable object and second ShippingMethod have category *Light*. The second Shipping category sets the category requirements as CATEGORY\_REQUIREMENT\_MATCH\_ALL. The returned number will be 2.

7. Two ShippingMethod have category *Regular*. All shippable object and second ShippingMethod have category *Light*. The second Shipping category sets the category requirements as CATEGORY\_REQUIREMENT\_MATCH\_ALL. The returned number will be 1.

Note

The categoryRequirement property in **ShippingMethod** is set default to CATEGORY\_REQUIREMENT\_MATCH\_ANY.

For more detailed information about requirements please go to [Shipping method requirements](index.html#document-components_and_bundles/bundles/SyliusShippingBundle/shipping_requirements).

###### Calculators[¶](#calculators "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### FlatRateCalculator[¶](#flatratecalculator "Permalink to this headline")

**FlatRateCalculator** class charges a flat rate per shipment.

```

<?php

use Sylius\Component\Shipping\Model\Shipment;

use Sylius\Component\Shipping\Calculator\FlatRateCalculator;

use Sylius\Component\Shipping\Model\ShipmentItem;

$shipmentItem = new ShipmentItem();

$shipment = new Shipment();

$shipment->addItem($shipmentItem);

$flatRateCalculator = new FlatRateCalculator();

// this configuration should be defined in shipping method allowed for shipment

$configuration = array('amount' => 1500);

$flatRateCalculator->calculate($shipment, $configuration); // returns 1500

$configuration = array('amount' => 500);

$flatRateCalculator->calculate($shipment, $configuration); // returns 500

```

####### FlexibleRateCalculator[¶](#flexibleratecalculator "Permalink to this headline")

**FlexibleRateCalculator** calculates a shipping charge, where first item has different cost that other items.

```

<?php

use Sylius\Component\Shipping\Model\Shipment;

use Sylius\Component\Shipping\Calculator\FlexibleRateCalculator;

$shipment = new Shipment();

$shipmentItem = new ShipmentItem();

$shipmentItem2 = new ShipmentItem();

$shipmentItem3 = new ShipmentItem();

$shipmentItem4 = new ShipmentItem();

// this configuration should be defined in shipping method allowed for shipment

$configuration = array(

    'first\_item\_cost'       => 1000,

    'additional\_item\_cost'  => 200,

    'additional\_item\_limit' => 2

);

$flexibleRateCalculator = new FlexibleRateCalculator();

$shipment->addItem($shipmentItem);

$flexibleRateCalculator->calculate($shipment, $configuration); // returns 1000

$shipment->addItem($shipmentItem2);

$shipment->addItem($shipmentItem3);

$flexibleRateCalculator->calculate($shipment, $configuration); // returns 1400

$shipment->addItem($shipmentItem4);

$flexibleRateCalculator->calculate($shipment, $configuration);

// returns 1400, because additional item limit is 3

```

####### PerItemRateCalculator[¶](#peritemratecalculator "Permalink to this headline")

**PerItemRateCalculator** charges a flat rate per item.

```

<?php

use Sylius\Component\Shipping\Model\Shipment;

use Sylius\Component\Shipping\Model\ShipmentItem;

use Sylius\Component\Shipping\Calculator\PerItemRateCalculator;

// this configuration should be defined in shipping method allowed for shipment

$configuration = array('amount' => 200);

$perItemRateCalculator = new PerItemRateCalculator();

$shipment = new Shipment();

$shipmentItem = new ShipmentItem();

$shipmentItem2 = new ShipmentItem();

$perItemRateCalculator->calculate($shipment, $configuration); // returns 0

$shipment->addItem($shipmentItem);

$perItemRateCalculator->calculate($shipment, $configuration); // returns 200

$shipment->addItem($shipmentItem2);

$perItemRateCalculator->calculate($shipment, $configuration); // returns 400

```

####### VolumeRateCalculator[¶](#volumeratecalculator "Permalink to this headline")

**VolumeRateCalculator** charges amount rate per volume.

```

<?php

use Sylius\Component\Shipping\Model\Shipment;

use Sylius\Component\Shipping\Model\ShipmentItem;

use Sylius\Component\Shipping\Calculator\VolumeRateCalculator;

$wardrobe = new Wardrobe();

$shipmentItem = new ShipmentItem();

$shipmentItem->setShippable($wardrobe);

$shipment = new Shipment();

$shipment->addItem($shipmentItem);

$configuration = array('amount' => 200, 'division' => 5);

// this configuration should be defined in shipping method allowed for shipment

$volumeRateCalculator = new VolumeRateCalculator();

$wardrobe->setShippingVolume(100);

$volumeRateCalculator->calculate($shipment, $configuration); // returns 4000

$wardrobe->setShippingVolume(20);

$volumeRateCalculator->calculate($shipment, $configuration); // returns 800

```

Hint

To see implementation of Wardrobe class please go to [Basic Usage](index.html#basic-usage).

####### WeightRateCalculator[¶](#weightratecalculator "Permalink to this headline")

**WeightRateCalculator** charges amount rate per weight.

```

<?php

use Sylius\Component\Shipping\Model\Shipment;

use Sylius\Component\Shipping\Model\ShipmentItem;

use Sylius\Component\Shipping\Calculator\WeightRateCalculator;

$configuration = array('fixed' => 200, 'variable' => 500, 'division' => 5);

// this configuration should be defined in shipping method allowed for shipment

$weightRateCalculator = new WeightRateCalculator();

$wardrobe = new Wardrobe();

$shipmentItem = new ShipmentItem();

$shipmentItem->setShippable($wardrobe);

$shipment = new Shipment();

$shipment->addItem($shipmentItem);

$wardrobe->setShippingWeight(100);

$weightRateCalculator->calculate($shipment, $configuration); // returns 10200

$wardrobe->setShippingWeight(10);

$weightRateCalculator->calculate($shipment, $configuration); // returns 1200

```

Hint

To see implementation of Wardrobe class please go to [Basic Usage](index.html#basic-usage).

###### Checkers[¶](#checkers "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### ItemCountRuleChecker[¶](#itemcountrulechecker "Permalink to this headline")

This class checks if item count exceeds (or at least is equal) the configured count.

An example about how to use it is on [RuleCheckerInterface](index.html#component-shipping-checker-rule-checker-interface).

Note

This checker implements the [RuleCheckerInterface](index.html#component-shipping-checker-rule-checker-interface).

####### ShippingMethodEligibilityChecker[¶](#shippingmethodeligibilitychecker "Permalink to this headline")

This class checks if shipping method rules are capable of shipping given subject.

```

<?php

use Sylius\Component\Shipping\Model\Rule;

use Sylius\Component\Shipping\Model\ShippingMethod;

use Sylius\Component\Shipping\Model\ShippingCategory;

use Sylius\Component\Shipping\Model\Shipment;

use Sylius\Component\Shipping\Model\ShipmentItem;

use Sylius\Component\Shipping\Model\ShippingMethodTranslation;

use Sylius\Component\Shipping\Model\RuleInterface;

use Sylius\Component\Shipping\Checker\ItemCountRuleChecker;

use Sylius\Component\Shipping\Checker\ShippingMethodEligibilityChecker;

use Sylius\Component\Shipping\Checker\RuleCheckerInterface;

use Sylius\Component\Registry\ServiceRegistry;

$rule = new Rule();

$rule->setConfiguration(array('count' => 0, 'equal' => true));

$rule->setType(RuleInterface::TYPE\_ITEM\_COUNT);

$shippingCategory = new ShippingCategory();

$shippingCategory->setName('Regular');

$hippingMethodTranslate = new ShippingMethodTranslation();

$hippingMethodTranslate->setLocale('en');

$hippingMethodTranslate->setName('First method');

$shippingMethod = new ShippingMethod();

$shippingMethod->setCategory($shippingCategory);

$shippingMethod->setCurrentLocale('en');

$shippingMethod->setFallbackLocale('en');

$shippingMethod->addTranslation($hippingMethodTranslate);

$shippingMethod->addRule($rule);

$shippable = new ShippableObject();

$shippable->setShippingCategory($shippingCategory);

$shipmentItem = new ShipmentItem();

$shipmentItem->setShippable($shippable);

$shipment = new Shipment();

$shipment->addItem($shipmentItem);

$ruleChecker = new ItemCountRuleChecker();

$ruleCheckerRegistry = new ServiceRegistry(RuleCheckerInterface::class);

$ruleCheckerRegistry->register(RuleInterface::TYPE\_ITEM\_COUNT, $ruleChecker);

$methodEligibilityChecker = new ShippingMethodEligibilityChecker($ruleCheckerRegistry);

///returns true, because quantity of shipping item in shipment is equal as count in rule's configuration

$methodEligibilityChecker->isEligible($shipment, $shippingMethod);

// returns true, because the shippable object has the same category as shippingMethod

// and shipping method has default category requirement

$methodEligibilityChecker->isCategoryEligible($shipment, $shippingMethod);

```

Caution

The method `->register()` throws [InvalidArgumentException](https://php.net/manual/en/class.invalidargumentexception.php).

Note

This model implements the [ShippingMethodEligibilityCheckerInterface](index.html#component-shipping-checker-shipping-method-eligibility-checker-interface).

###### Models[¶](#models "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### Shipment[¶](#shipment "Permalink to this headline")

**Shipment** object has methods to represent the events that take place during the process of shipment.

Shipment has the following properties:

| Property | Description |

| --- | --- |

| id | Unique id of the shipment |

| state | Reference to constant from ShipmentInterface |

| method | Reference to ShippingMethod |

| items | Reference to Collection of shipping items |

| tracking | Tracking code for shipment |

| createdAt | Creation time |

| updatedAt | Last update time |

Note

This model implements the [ShipmentInterface](index.html#component-shipping-model-shipment-interface).

####### ShipmentItem[¶](#shipmentitem "Permalink to this headline")

**ShipmentItem** object is used for connecting a shippable object with a proper shipment.

ShipmentItems have the following properties:

| Property | Description |

| --- | --- |

| id | Unique id of the ShipmentItem |

| shipment | Reference to Shipment |

| shippable | Reference to shippable object |

| shippingState | Reference to constant from ShipmentInterface |

| createdAt | Creation time |

| updatedAt | Last update time |

Note

This model implements the [ShipmentItemInterface](index.html#component-shipping-model-shipment-item-interface).

####### ShippingCategory[¶](#shippingcategory "Permalink to this headline")

**ShippingCategory** object represents category which can be common for **ShippingMethod** and object which implements

**ShippableInterface**.

ShippingCategory has the following properties:

| Property | Description |

| --- | --- |

| id | Unique id of the ShippingCategory |

| code | Unique code of the ShippingCategory |

| name | e.g. “Regular” |

| description | e.g. “Regular weight items” |

| createdAt | Creation time |

| updatedAt | Last update time |

Hint

To understand relationship between **ShippingMethod** and shippable object base on **ShippingCategory** go to

[Shipping method requirements](index.html#document-components_and_bundles/bundles/SyliusShippingBundle/shipping_requirements).

Note

This model implements the [ShippingCategoryInterface](index.html#component-shipping-model-shipping-category-interface).

####### ShippingMethod[¶](#shippingmethod "Permalink to this headline")

**ShippingMethod** object represents method of shipping allowed for given shipment.

It has the following properties:

| Property | Description |

| --- | --- |

| id | Unique id of the ShippingMethod |

| code | Unique code of the ShippingMethod |

| category | e.g. “Regular” |

| categoryRequirement | Reference to constant from ShippingMethodInterface |

| enabled | Boolean flag of enablement |

| calculator | Reference to constant from DefaultCalculators |

| configuration | Extra configuration for calculator |

| rules | Collection of Rules |

| createdAt | Creation time |

| updatedAt | Last update time |

| currentTranslation | Translation chosen from translations list accordingly to current locale |

| currentLocale | Currently set locale |

| translations | Collection of translations |

| fallbackLocale | Locale used in case no translation is available |

Note

This model implements the [ShippingMethodInterface](index.html#component-shipping-model-shipping-method-interface) and uses the

[TranslatableTrait](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Component/Model/TranslatableTrait.php).

####### ShippingMethodTranslation[¶](#shippingmethodtranslation "Permalink to this headline")

**ShippingMethodTranslation** object allows to translate the shipping method’s name accordingly to the provided locales.

It has the following properties:

| Property | Description |

| --- | --- |

| id | Unique id of the ShippingMethodTranslation |

| name | e.g. “FedEx” |

| locale | Translation locale |

| translatable | The translatable model assigned to this translation |

Note

This model implements the [ShippingMethodTranslationInterface](index.html#component-shipping-model-shipping-method-translation-interface) and extends

[AbstractTranslation](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Component/Model/AbstractTranslation.php) class.

###### Interfaces[¶](#interfaces "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### Model Interfaces[¶](#model-interfaces "Permalink to this headline")

######## RuleInterface[¶](#ruleinterface "Permalink to this headline")

This interface should be implemented by class which will provide additional restriction for **ShippingMethod**.

######## ShipmentInterface[¶](#shipmentinterface "Permalink to this headline")

This interface should be implemented by class which will provide information about shipment like: state, shipping method

and so on. It also has a method for shipment tracking.

Note

This interface extends the [ShippingSubjectInterface](#component-shipping-model-shipping-subject-interface).

######## ShipmentItemInterface[¶](#shipmentiteminterface "Permalink to this headline")

This interface is implemented by class responsible for connecting shippable object with proper shipment. It also

provides information about shipment state.

Note

This interface extends the [ShippingSubjectInterface](#component-shipping-model-shipping-subject-interface).

######## ShippableInterface[¶](#shippableinterface "Permalink to this headline")

This interface should be implemented by model representing physical object which can by stored in a shop.

######## ShippingCategoryInterface[¶](#shippingcategoryinterface "Permalink to this headline")

This interface should be implemented by model representing a shipping category and it is required if you want to classify

shipments and connect it with right shipment method.

Note

This interface extends the [CodeAwareInterface](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Component/Model/CodeAwareInterface.php) and [TimestampableInterface](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Component/Model/TimestampableInterface.php).

######## ShippingMethodInterface[¶](#shippingmethodinterface "Permalink to this headline")

This interface provides default requirements for system of matching shipping methods with shipments based on **ShippingCategory**

and allows to add a new restriction to a basic shipping method.

Note

This interface extends the [CodeAwareInterface](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Component/Model/CodeAwareInterface.php), [TimestampableInterface](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Component/Model/TimestampableInterface.php)

and [ShippingMethodTranslationInterface](#component-shipping-model-shipping-method-translation-interface).

######## ShippingMethodTranslationInterface[¶](#shippingmethodtranslationinterface "Permalink to this headline")

This interface should be implemented by model responsible for keeping translation for **ShippingMethod** name.

######## ShippingSubjectInterface[¶](#shippingsubjectinterface "Permalink to this headline")

This interface should be implemented by any object, which needs to be evaluated by default shipping calculators and rule checkers.

####### Calculator interfaces[¶](#calculator-interfaces "Permalink to this headline")

######## CalculatorInterface[¶](#calculatorinterface "Permalink to this headline")

This interface provides basic methods for calculators. Every custom calculator should implement **CalculatorInterface** or extends

class **Calculator**, which has a basic implementation of methods from this interface.

######## DelegatingCalculatorInterface[¶](#delegatingcalculatorinterface "Permalink to this headline")

This interface should be implemented by any object, which will be responsible for delegating the calculation to a correct calculator instance.

######## CalculatorRegistryInterface[¶](#calculatorregistryinterface "Permalink to this headline")

This interface should be implemented by an object, which will keep all calculators registered inside container.

####### Checker Interfaces[¶](#checker-interfaces "Permalink to this headline")

######## RuleCheckerRegistryInterface[¶](#rulecheckerregistryinterface "Permalink to this headline")

This interface should be implemented by an service responsible for providing an information about available rule checkers.

######## RuleCheckerInterface[¶](#rulecheckerinterface "Permalink to this headline")

This interface should be implemented by an object, which checks if a shipping subject meets the configured requirements.

######## ShippingMethodEligibilityCheckerInterface[¶](#shippingmethodeligibilitycheckerinterface "Permalink to this headline")

This interface should be implemented by an object, which checks if the given shipping subject is eligible for the shipping method rules.

####### Processor Interfaces[¶](#processor-interfaces "Permalink to this headline")

######## ShipmentProcessorInterface[¶](#shipmentprocessorinterface "Permalink to this headline")

This interface should be implemented by an object, which updates shipments and shipment items states.

####### Resolver Interfaces[¶](#resolver-interfaces "Permalink to this headline")

######## ShippingMethodsResolverInterface[¶](#shippingmethodsresolverinterface "Permalink to this headline")

This interface should be used to create object, which provides information about all allowed shipping methods

for given shipping subject.

###### State Machine[¶](#state-machine "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### Shipment States[¶](#shipment-states "Permalink to this headline")

Sylius itself uses a state machine system to manage all states of the business domain. This component has some

sensible default states defined in **ShipmentInterface**.

All new **Shipment** instances have the state `ready` by default, which means they are prepared to be sent.

The following states are defined:

| Related constant | State | Description |

| --- | --- | --- |

| STATE\_READY | ready | Payment received, shipment has been ready to be sent |

| STATE\_CHECKOUT | checkout | Shipment has been created |

| STATE\_ONHOLD | onhold | Shipment has been locked and it has been waiting to payment |

| STATE\_PENDING | pending | Shipment has been waiting for confirmation of receiving payment |

| STATE\_SHIPPED | shipped | Shipment has been sent to the customer |

| STATE\_CANCELLED | cancelled | Shipment has been cancelled |

| STATE\_RETURNED | returned | Shipment has been returned |

###### Learn more[¶](#learn-more "Permalink to this headline")

* [Shipments in the Sylius platform](index.html#document-book/orders/shipments) - concept documentation

##### Taxation[¶](#taxation "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

Tax rates and tax classification for PHP applications. You can define different tax categories and match them to objects.

###### Installation[¶](#installation "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

You can install the component in 2 different ways:

* [Install it via Composer](index.html#document-components_and_bundles/components/general/using_components) (`sylius/taxation` on [Packagist](https://packagist.org/packages/sylius/taxation));

* Use the official Git repository (<https://github.com/Sylius/Taxation>).

Then, require the `vendor/autoload.php` file to enable the autoloading mechanism

provided by Composer. Otherwise, your application won’t be able to find the classes

of this Sylius component.

###### Basic Usage[¶](#basic-usage "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### Tax Rate[¶](#tax-rate "Permalink to this headline")

Every tax rate has three identifiers, an ID, code and name. You can access those by calling `->getId()`, `->getCode()` and `getName()`

respectively. The name and code are mutable, so you can change them by calling `->setCode('X12XW')` and `->setName('EU VAT')` on the tax rate instance.

######## Setting Tax Amount[¶](#setting-tax-amount "Permalink to this headline")

A tax rate has two basic amounts - the *amount* and the *amount as percentage* (by default equal 0).

```

<?php

use Sylius\Component\Taxation\Model\TaxRate;

use Sylius\Component\Taxation\Model\TaxCategory;

$taxRate = new TaxRate();

$taxCategory = new TaxCategory();

$taxRate->setAmount(0.5);

$taxRate->getAmount(); // Output will be 0.5

$taxRate->getAmountAsPercentage(); // Output will be 50

```

######## Setting Tax Category[¶](#setting-tax-category "Permalink to this headline")

Every tax rate can have a tax category. You can simply set or unset it by calling `->setCategory()`.

```

<?php

$taxRate->setCategory($taxCategory);

$taxRate->getCategory(); // Output will be $taxCategory object

$taxRate->setCategory();

$taxRate->getCategory(); // Output will be null

```

######## Including tax rate in price[¶](#including-tax-rate-in-price "Permalink to this headline")

You can mark a tax rate as included in price by calling `setIncludedInPrice(true)` (false by default).

To check if tax rate is included in price call `isIncludedInPrice()`.

Hint

You can read how this property influences on the tax calculation in chapter [Default Calculator](#default-calculator).

######## Setting calculator[¶](#setting-calculator "Permalink to this headline")

To set type of calculator for your tax rate object call `setCalculator('nameOfCalculator')`. Notice that nameOfCalculator

should be the same as name of your calculator object.

Hint

To understand meaning of this property go to [Delegating Calculator](#delegating-calculator).

####### Tax Category[¶](#tax-category "Permalink to this headline")

Every tax category has three identifiers, an ID, code and name. You can access those by calling `->getId()`, `->getCode()` and `getName()`

respectively. The code and name are mutable, so you can change them by calling `->setCode('X12X')` and `->setName('Clothing')` on the tax category instance.

######## Tax Rate Management[¶](#tax-rate-management "Permalink to this headline")

The collection of tax rates (Implementing the `Doctrine\Common\Collections\Collection` interface) can be obtained using

the `getRates()` method. To add or remove tax rates, you can use the `addRate()` and `removeRate()` methods.

```

<?php

use Sylius\Component\Taxation\Model\TaxRate;

use Sylius\Component\Taxation\Model\TaxCategory;

$taxCategory = new TaxCategory();

$taxRate1 = new TaxRate();

$taxRate1->setName('taxRate1');

$taxRate2 = new TaxRate();

$taxRate2->setName('taxRate2');

$taxCategory->addRate($taxRate1);

$taxCategory->addRate($taxRate2);

$taxCategory->getRates();

//returns a collection of objects that implement the TaxRateInterface

$taxCategory->removeRate($taxRate1);

$taxCategory->hasRate($taxRate2); // returns true

$taxCategory->getRates(); // returns collection with one element

```

####### Calculators[¶](#calculators "Permalink to this headline")

######## Default Calculator[¶](#default-calculator "Permalink to this headline")

**Default Calculator** gives you the ability to calculate the tax amount for given base amount and tax rate.

```

<?php

use Sylius\Component\Taxation\Model\TaxRate;

use Sylius\Component\Taxation\Calculator\DefaultCalculator;

$taxRate = new TaxRate();

$taxRate->setAmount(0.2);

$basicPrice = 100;

$defaultCalculator = new DefaultCalculator();

$defaultCalculator->calculate($basicPrice, $taxRate); //return 20

$taxRate->setIncludedInPrice(true);

$defaultCalculator->calculate($basicPrice, $taxRate);

// return 17, because the tax is now included in price

```

######## Delegating Calculator[¶](#delegating-calculator "Permalink to this headline")

**Delegating Calculator** gives you the ability to delegate the calculation of amount of tax to a correct calculator

instance based on a type defined in an instance of **TaxRate** class.

```

<?php

use Sylius\Component\Taxation\Model\TaxRate;

use Sylius\Component\Taxation\Calculator\DefaultCalculator;

use Sylius\Component\Registry\ServiceRegistry;

use Sylius\Component\Taxation\Calculator\DelegatingCalculator;

use Sylius\Component\Taxation\Calculator\CalculatorInterface;

$taxRate = new TaxRate();

$taxRate->setAmount(0.2);

$base = 100; //set base price to 100

$defaultCalculator = new DefaultCalculator();

$serviceRegistry =

new ServiceRegistry(CalculatorInterface::class);

$serviceRegistry->register('default', $defaultCalculator);

$delegatingCalculator = new DelegatingCalculator($serviceRegistry);

$taxRate->setCalculator('default');

$delegatingCalculator->calculate($base, $taxRate); // returns 20

```

####### Tax Rate Resolver[¶](#tax-rate-resolver "Permalink to this headline")

**TaxRateResolver** gives you ability to get information about tax rate for given taxable object and specific criteria.

The criteria describes tax rate object.

```

<?php

use Sylius\Component\Taxation\Resolver\TaxRateResolver;

use Sylius\Component\Taxation\Model\TaxCategory;

$taxRepository = new InMemoryTaxRepository(); // class which implements RepositoryInterface

$taxRateResolver= new TaxRateResolver($taxRepository);

$taxCategory = new TaxCategory();

$taxCategory->setName('TaxableGoods');

$taxableObject = new TaxableObject(); // class which implements TaxableInterface

$taxableObject->setTaxCategory($taxCategory);

$criteria = array('name' => 'EU VAT');

$taxRateResolver->resolve($taxableObject, $criteria);

// returns instance of class TaxRate, which has name 'EU VAT' and category 'TaxableGoods'

```

###### Models[¶](#models "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### TaxRate[¶](#taxrate "Permalink to this headline")

Tax rate model holds the configuration for particular tax rate.

| Property | Description |

| --- | --- |

| id | Unique id of the tax rate |

| code | Unique code of the tax rate |

| category | Tax rate category |

| name | Name of the rate |

| amount | Amount as float (for example 0,23) |

| includedInPrice | Is the tax included in price? |

| calculator | Type of calculator |

| createdAt | Date when the rate was created |

| updatedAt | Date of the last tax rate update |

Note

This model implements `TaxRateInterface`.

####### TaxCategory[¶](#taxcategory "Permalink to this headline")

Tax category model holds the configuration for particular tax category.

| Property | Description |

| --- | --- |

| id | Unique id of the tax category |

| code | Unique code of the tax category |

| name | Name of the category |

| description | Description of tax category |

| rates | Collection of tax rates belonging to this tax category |

| createdAt | Date when the category was created |

| updatedAt | Date of the last tax category update |

Note

This model implements `TaxCategoryInterface`.

###### Interfaces[¶](#interfaces "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### Model Interfaces[¶](#model-interfaces "Permalink to this headline")

######## Taxable Interface[¶](#taxable-interface "Permalink to this headline")

To create taxable object which has specific type of tax category, the object class needs to implement

**TaxableInterface**.

######## Tax Category Interface[¶](#tax-category-interface "Permalink to this headline")

To create object which provides information about tax category, the object class needs to implement

**TaxCategoryInterface**.

Note

This interface extends [CodeAwareInterface](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Component/Model/CodeAwareInterface.php) and [TimestampableInterface](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Component/Model/TimestampableInterface.php).

######## Tax Rate Interface[¶](#tax-rate-interface "Permalink to this headline")

To create object which provides information about tax rate, the object class needs to implement

**TaxCategoryInterface**.

Note

This interface extends [CodeAwareInterface](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Component/Model/CodeAwareInterface.php) and [TimestampableInterface](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Component/Model/TimestampableInterface.php).

####### Calculator Interfaces[¶](#calculator-interfaces "Permalink to this headline")

######## CalculatorInterface[¶](#calculatorinterface "Permalink to this headline")

To make the calculator able to calculate the tax amount for given base amount and tax rate,

the calculator class needs implement the **CalculatorInterface**.

####### Resolver Interfaces[¶](#resolver-interfaces "Permalink to this headline")

######## TaxRateResolverInterface[¶](#taxrateresolverinterface "Permalink to this headline")

To create class which provides information about tax rate for given taxable object and specific criteria, the class needs to

implement **TaxRateResolverInterface**. The criteria describes tax rate object.

###### Learn more[¶](#learn-more "Permalink to this headline")

* [Taxation in the Sylius platform](index.html#document-book/orders/taxation) - concept documentation

##### Taxonomy[¶](#taxonomy "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

Basic taxonomies library for any PHP application. Taxonomies work similarly to the distinction of species in the fauna and flora

and their aim is to help the store owner manage products.

###### Installation[¶](#installation "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

You can install the component in 2 different ways:

* [Install it via Composer](index.html#document-components_and_bundles/components/general/using_components) (`sylius/taxonomy` on [Packagist](https://packagist.org/packages/sylius/taxonomy));

* Use the official Git repository (<https://github.com/Sylius/Taxonomy>).

Then, require the `vendor/autoload.php` file to enable the autoloading mechanism

provided by Composer. Otherwise, your application won’t be able to find the classes

of this Sylius component.

###### Basic Usage[¶](#basic-usage "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

```

<?php

use Sylius\Component\Taxonomy\Model\Taxon;

use Sylius\Component\Taxonomy\Model\Taxonomy;

// Let's assume we want to begin creating new taxonomy in our system

// therefore we think of a new taxon that will be a root for us.

$taxon = new Taxon();

// And later on we create a taxonomy with our taxon as a root.

$taxonomy = new Taxonomy($taxon);

// Before we can start using the newly created taxonomy, we have to define its locales.

$taxonomy->setFallbackLocale('en');

$taxonomy->setCurrentLocale('en');

$taxonomy->setName('Root');

$taxon->getName(); //will return 'Root'

```

###### Models[¶](#models "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

Taxonomy is a list constructed from individual Taxons. Taxonomy is a special case of Taxon itself (it has no parent).

All taxons can have many child taxons, you can define as many of them as you need.

Good examples of taxonomies are “Categories” and “Brands”. Below you can see exemplary trees.

```

| Categories

|\__T-Shirts

|   |\__Men

|    \__Women

|\__Stickers

|\__Mugs

 \__Books

| Brands

|\__SuperTees

|\__Stickypicky

|\__Mugland

 \__Bookmania

```

####### Taxon[¶](#taxon "Permalink to this headline")

| Property | Description |

| --- | --- |

| id | Unique id of the taxon |

| code | Unique code of the taxon |

| name | Name of the taxon taken form the `TaxonTranslation` |

| slug | Urlized name taken from the `TaxonTranslation` |

| description | Description of taxon taken from the `TaxonTranslation` |

| parent | Parent taxon |

| children | Sub taxons |

| left | Location within taxonomy |

| right | Location within taxonomy |

| level | How deep it is in the tree |

| position | Position of the taxon on its taxonomy |

Note

This model implements the [TaxonInterface](index.html#component-taxonomy-model-taxon-interface).

####### TaxonTranslation[¶](#taxontranslation "Permalink to this headline")

This model stores translations for the **Taxon** instances.

| Property | Description |

| --- | --- |

| id | Unique id of the taxon translation |

| name | Name of the taxon |

| slug | Urlized name |

| description | Description of taxon |

Note

This model implements the [TaxonTranslationInterface](index.html#component-taxonomy-model-taxon-translation-interface).

###### Interfaces[¶](#interfaces "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### Models Interfaces[¶](#models-interfaces "Permalink to this headline")

######## TaxonInterface[¶](#taxoninterface "Permalink to this headline")

The **TaxonInterface** gives an object an ability to have Taxons assigned as children.

Note

This interface extends the [CodeAwareInterface](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Component/Model/CodeAwareInterface.php),

[TranslatableInterface](https://github.com/Sylius/SyliusResourceBundle/blob/master/src/Component/Model/TranslatableInterface.php)

and the [TaxonTranslationInterface](#component-taxonomy-model-taxon-translation-interface).

######## TaxonsAwareInterface[¶](#taxonsawareinterface "Permalink to this headline")

The **TaxonsAwareInterface** should be implemented by models that can be classified with taxons.

######## TaxonTranslationInterface[¶](#taxontranslationinterface "Permalink to this headline")

This interface should be implemented by models that will store the **Taxon** translation data.

####### Services Interfaces[¶](#services-interfaces "Permalink to this headline")

######## TaxonRepositoryInterface[¶](#taxonrepositoryinterface "Permalink to this headline")

In order to have a possibility to get Taxons as a list you should create a repository class, that implements this interface.

###### Learn more[¶](#learn-more "Permalink to this headline")

* [Taxons in the Sylius platform](index.html#document-book/products/taxons) - concept documentation

##### User[¶](#user "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

Users management implementation in PHP.

###### Installation[¶](#installation "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

You can install the component in 2 different ways:

* [Install it via Composer](index.html#document-components_and_bundles/components/general/using_components) (`sylius/user` on [Packagist](https://packagist.org/packages/sylius/user));

* Use the official Git repository (<https://github.com/Sylius/User>).

Then, require the `vendor/autoload.php` file to enable the autoloading mechanism

provided by Composer. Otherwise, your application won’t be able to find the classes

of this Sylius component.

###### Models[¶](#models "Permalink to this headline")

####### Customer[¶](#customer "Permalink to this headline")

The customer is represented as a **Customer** instance. It should have everything

concerning personal data and as default has the following properties:

| Property | Description | Type |

| --- | --- | --- |

| id | Unique id of the customer | integer |

| email | Customer’s email | string |

| emailCanonical | Normalized representation of an email (lowercase) | string |

| firstName | Customer’s first name | string |

| lastName | Customer’s last name | string |

| birthday | Customer’s birthday | DateTime |

| gender | Customer’s gender | string |

| user | Corresponding user object | UserInterface |

| group | Customer’s groups | Collection |

| createdAt | Date of creation | DateTime |

| updatedAt | Date of update | DateTime |

Note

This model implements `CustomerInterface`

####### User[¶](#user "Permalink to this headline")

The registered user is represented as an **User** instance. It should have everything

concerning application user preferences and a corresponding **Customer** instance.

As default has the following properties:

| Property | Description | Type |

| --- | --- | --- |

| id | Unique id of the user | integer |

| customer | Customer which is associated to this user (required) | CustomerInterface |

| username | User’s username | string |

| usernameCanonical | Normalized representation of a username (lowercase) | string |

| enabled | Indicates whether user is enabled | bool |

| salt | Additional input to a function that hashes a password | string |

| password | Encrypted password, must be persisted | string |

| plainPassword | Password before encryption, must not be persisted | string |

| lastLogin | Last login date | DateTime |

| confirmationToken | Random string used to verify user | string |

| passwordRequestedAt | Date of password request | DateTime |

| locked | Indicates whether user is locked | bool |

| expiresAt | Date when user account will expire | DateTime |

| credentialExpiresAt | Date when user account credentials will expire | DateTime |

| roles | Security roles of a user | array |

| oauthAccounts | Associated OAuth accounts | Collection |

| createdAt | Date of creation | DateTime |

| updatedAt | Date of update | DateTime |

Note

This model implements `UserInterface`

####### CustomerGroup[¶](#customergroup "Permalink to this headline")

The customer group is represented as a **CustomerGroup** instance. It can be used to classify customers.

As default has the following properties:

| Property | Description | Type |

| --- | --- | --- |

| id | Unique id of the group | integer |

| name | Group name | string |

Note

This model implements `CustomerGroupInterface`

####### UserOAuth[¶](#useroauth "Permalink to this headline")

The user OAuth account is represented as an **UserOAuth** instance. It has all data

concerning OAuth account and as default has the following properties:

| Property | Description | Type |

| --- | --- | --- |

| id | Unique id of the customer | integer |

| provider | OAuth provider name | string |

| identifier | OAuth identifier | string |

| accessToken | OAuth access token | string |

| user | Corresponding user account | UserInterface |

Note

This model implements `UserOAuthInterface`

###### Basic Usage[¶](#basic-usage "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

####### Canonicalization[¶](#canonicalization "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

In order to be able to query or sort by some string, we should normalize it.

The most common use case for that is canonical email or username. We can

then allow for case insensitive users identification by email or username.

######## Canonicalizer[¶](#canonicalizer "Permalink to this headline")

User component offers simple canonicalizer which converts given string to lowercase

letters. Example usage:

```

<?php

// src/script.php

// update this to the path to the "vendor/"

// directory, relative to this file

require\_once \_\_DIR\_\_.'/../vendor/autoload.php';

use Sylius\Component\User\Model\User;

use Sylius\Component\Canonicalizer\Canonicalizer;

$canonicalizer = new Canonicalizer();

$user = new User();

$user->setEmail('MyEmail@eXample.Com');

$canonicalEmail = $canonicalizer->canonicalize($user->getEmail());

$user->setEmailCanonical($canonicalEmail);

$user->getEmail(); // returns 'MyEmail@eXample.Com'

$user->getEmailCanonical(); // returns 'myemail@example.com'

```

####### Updating password[¶](#updating-password "Permalink to this headline")

Danger

We’re sorry but **this documentation section is outdated**. Please have that in mind when trying to use it.

You can help us making documentation up to date via Sylius Github. Thank you!

In order to store user’s password safely you need to encode it and get rid of

the plain password.

######## PasswordUpdater[¶](#passwordupdater "Permalink to this headline")

User component offers simple password updater and encoder. All you need to do

is set the plain password on User entity and use updatePassword method on

PasswordUpdater. The plain password will be removed and the encoded password

will be set on User entity. Now you can safely store the encoded password.

Example usage:

```

<?php

// src/script.php

// update this to the path to the "vendor/"

// directory, relative to this file

require\_once \_\_DIR\_\_.'/../vendor/autoload.php';

use Sylius\Component\User\Model\User;

use Sylius\Component\User\Security\PasswordUpdater;

use Sylius\Component\User\Security\UserPbkdf2PasswordEncoder;

$user = new User();

$user->setPlainPassword('secretPassword');

$user->getPlainPassword(); // returns 'secretPassword'

$user->getPassword(); // returns null

// after you set user's password you need to encode it and get rid of unsafe plain text

$passwordUpdater = new PasswordUpdater(new UserPbkdf2PasswordEncoder());

$passwordUpdater->updatePassword($user);

// the plain password no longer exist

$user->getPlainPassword(); // returns null

// encoded password can be safely stored

$user->getPassword(); //returns 'notPredictableBecauseOfSaltHashedPassword'

```

Note

The password encoder takes user’s salt (random, autogenerated string in the

User constructor) as an additional input to a one-way function that hashes

a password. The primary function of salts is to defend against dictionary attacks

versus a list of password hashes and against pre-computed rainbow table attacks.

###### Learn more[¶](#learn-more "Permalink to this headline")

* [Customers & Users in the Sylius platform](index.html#document-book/customers/index) - concept documentation

* [Components General Guide](index.html#document-components_and_bundles/components/general/index)

* [Addressing](index.html#document-components_and_bundles/components/Addressing/index)

* [Attribute](index.html#document-components_and_bundles/components/Attribute/index)

* [Channel](index.html#document-components_and_bundles/components/Channel/index)

* [Currency](index.html#document-components_and_bundles/components/Currency/index)

* [Grid](https://github.com/Sylius/SyliusGridBundle/blob/master/docs/index.md)

* [Inventory](index.html#document-components_and_bundles/components/Inventory/index)

* [Locale](index.html#document-components_and_bundles/components/Locale/index)

* [Mailer](https://github.com/Sylius/SyliusMailerBundle/blob/master/docs/index.md)

* [Order](index.html#document-components_and_bundles/components/Order/index)

* [Payment](index.html#document-components_and_bundles/components/Payment/index)

* [Product](index.html#document-components_and_bundles/components/Product/index)

* [Promotion](index.html#document-components_and_bundles/components/Promotion/index)

* [Registry](https://github.com/Sylius/Registry/blob/master/docs/index.md)

* [Resource](https://github.com/Sylius/SyliusResourceBundle/blob/master/docs/index.md)

* [Shipping](index.html#document-components_and_bundles/components/Shipping/index)

* [Taxation](index.html#document-components_and_bundles/components/Taxation/index)

* [Taxonomy](index.html#document-components_and_bundles/components/Taxonomy/index)

* [User](index.html#document-components_and_bundles/components/User/index)

* [Sylius Components Documentation](index.html#document-components_and_bundles/components/index)

* [Sylius Bundles Documentation](index.html#document-components_and_bundles/bundles/index)

* [Sylius Components Documentation](index.html#document-components_and_bundles/components/index)

* [Sylius Bundles Documentation](index.html#document-components_and_bundles/bundles/index)

The Performance Guide[¶](#the-performance-guide "Permalink to this headline")

-----------------------------------------------------------------------------

With [The Performance Guide](index.html#document-performance/index) you can decrease page loading times.

### The Performance Guide[¶](#the-performance-guide "Permalink to this headline")

With huge databases some of you may experience performance issues, with this guide you can learn how to handle even millions of records.

#### Database indexes[¶](#database-indexes "Permalink to this headline")

Indexing tables allow you to decrease fetching time from the database.

As an example, let’s take a look at the customers’ list.

The default index page is sorted by registration date, to create a table index all you need to do is modify AppEntityCustomerCustomer entity and add the index using annotations.

In this file, add indexes attribute into the table configuration:

```

/\*\*

\* @ORM\Table(name="sylius\_customer", indexes={@ORM\Index(name="created\_at\_index", columns={"created\_at"})})

\*/

```

Your class should now look like this:

```

<?php

declare(strict\_types=1);

namespace App\Entity\Customer;

use Doctrine\ORM\Mapping as ORM;

use Sylius\Component\Core\Model\Customer as BaseCustomer;

/\*\*

 \* @ORM\Entity

 \* @ORM\Table(name="sylius\_customer",indexes={@ORM\Index(name="created\_at\_index", columns={"created\_at"})})

 \*/

class Customer extends BaseCustomer

{

}

```

Note

You can learn more here about [ORM annotations](https://www.doctrine-project.org/projects/doctrine-orm/en/latest/reference/annotations-reference.html#annref_haslifecyclecallbacks)

This should be considered for the most common sorting in your application.

Note

Using this solution you can increase speed of customer listing by around 10%.

Indexes should be used when working with huge tables, otherwise it doesnt really affect loading times.

#### Query optimization[¶](#query-optimization "Permalink to this headline")

A huge performance boost is achieved by optimizing queries.

For example, let’s take a look at the customers’ list.

By default, we don’t really need to fetch every single field from a customer entity to create a list of 10 customers.

All we need, is to load the id and the field we are sorting by (for pagination purposes).

Once these 10 customers are fetched, we can create 10 queries to fetch data for them.

As it can slow down small databases (a lot of unnecessary queries), this allows tables with millions of records to be loaded in less than a second.

It seems to be a bit complicated, but PagerfantaAdapterDoctrineORMAdapter allow us to achieve it easily.

The second argument of PagerfantaAdapterDoctrineORMAdapter is fetchJoinCollection, which is set to false by default.

Changing it to true, forces the database to fetch additional data once we get sorted results.

With 3 000 000 customers this method allows you to load page up to 70% faster.

Warning

This solution may slow down loading page with small tables as it will create additional database queries.

This solution is turned on by default on SyliusGridBundle since version 1.10.

For the earlier versions, the PagerfantaAdapterDoctrineORMAdapter has to be adjusted manually,

by overriding the SyliusBundleGridBundleDoctrineORMDataSource class.

* [Database indexes](index.html#document-performance/database-indexes)

* [Query optimization](index.html#document-performance/query-optimization)

* [Database indexes](index.html#document-performance/database-indexes)

* [Query optimization](index.html#document-performance/query-optimization)

