Content¶
...
SymfonyCMF and PHPCR¶
...
Static Content¶
...
Pages¶
...
Blocks¶
...
Final Thoughts¶
...
Learn more¶
- ...
Sylius is современный e-commerce фреймфорк на основе Symfony2 Framework.
Примечание
This documentation assumes you have a working knowledge of the Symfony2 Framework. If you haven’t, please start by reading the Quick Tour from the Symfony documentation.
The Developer’s guide to leveraging the flexibility of Sylius.
Sylius is a game-changing e-commerce solution for PHP, based on the Symfony2 framework.
Sylius is completely open source (MIT license) and free, maintained by diverse and creative community of developers and companies.
What are our core values and what makes us different from other solutions?
And much more, but we will let you discover it yourself.
Sylius is constructed from fully decoupled and flexible e-commerce components for PHP. It is also a set of Symfony2 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 Symfony2 app or built your application on top of Sylius platform.
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.
If you prefer to build your very custom system step by step and from scratch, you can integrate the standalone Symfony2 bundles. For the installation instructions, please refer to the appropriate bundle documentation.
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.
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.
The Sylius main application can serve as end-user app, as well as a foundation for your custom e-commerce application.
This article assumes you’re familiar with Composer, a dependency manager for PHP. It also assumes you have Composer installed globally.
Примечание
If you downloaded the Composer phar archive, you should use
php composer.phar
where this guide uses composer
.
It can be installed using two different approaches, depending on your use case.
To install Sylius main application from our main repository and contribute, run the following command:
$ composer create-project -s dev sylius/sylius
This will create a new sylius project in sylius
. When all the
dependencies are installed, you’ll be asked to fill the parameters.yml
file via interactive script. Please follow the steps. After everything is in
place, run the following commands:
# Move to the newly created directory
$ cd sylius
$ php app/console sylius:install
This package contains our main Sylius development repository, with all the components and bundles in the src/
folder.
For the contributing process questions, please refer to the [Contributing Guide].
To create a new project using Sylius Standard Edition, run this command:
$ composer create-project -s dev sylius/sylius-standard acme
This will create a new Symfony project in acme
directory. When all the
dependencies are installed, you’ll be asked to fill the parameters.yml
file via interactive script. Please follow the steps. After everything is in
place, run the following commands:
# Move to the newly created directory
$ cd acme
$ php app/console sylius:install
This package has the whole sylius/sylius
package in vendors, so you can easily updated it and focus on your custom development.
In order to see the shop, access the web/app_dev.php
file via your web
browser.
Совет
If you use PHP 5.4 or higher, you can also use the build-in webserver for
Symfony. Run the php app/console server:run
command and then access
http://localhost:8000
.
Before we dive separately into every Sylius concept, you need to have an overview of how our main application is structured. You already know that Sylius is built from components and Symfony2 bundles, which are integration layers with the framework.
All bundles share the conventions for naming things and the same way of data persistence. Sylius, by default, uses Doctrine ORM for managing all entities.
For deeper understanding of how Doctrine works, please refer to the [excellent documentation on their official website].
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...
Sylius resource management system lives in the SyliusResourceBundle and can be used in any Symfony2 project.
For every resource you have three very important services available:
Let us take the “product” resource as an example. By default, It is represented by Sylius\Component\Core\Model\Product
class and implement proper ProductInterface
.
The manager service is just an alias to appropriate Doctrine’s ObjectManager 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');
$manager->persist($product1);
$manager->remove($product2);
$manager->flush(); // Save changes in database.
}
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(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.
}
Every Sylius repository supports paginating resources. To create a Pagerfanta instance 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 returns 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(array('foo' => true), array('createdAt' => 'desc'));
$products->setMaxPerPage(3);
$products->setCurrentPage($request->query->get('page', 1));
}
To create a new object instance, you can simply call the createNew()
method on the repository.
Now let’s try something else than product, we’ll create a new TaxRate model.
<?php
public function myAction()
{
$repository = $this->container->get('sylius.repository.tax_rate');
$taxRate = $repository->createNew();
}
Примечание
Creating resources via this factory method makes the code more testable, and allows you to change the model class easily.
This service is the most important for every resource and provides a format agnostic CRUD controller with the following actions:
As you can see, these actions match the common operations in any REST API and yes, they are format agnostic. That means, all Sylius controllers can serve HTML, JSON or XML, depending on what do 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 you own resources as well. To give you some idea of what is possible, here are some examples!
Displaying a resource with custom template and repository methods:
# routing.yml
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.
method: findForStore # Use custom repository method.
arguments: [$slug] # Pass the slug from the url to the repository.
Creating a product using custom form and redirection method:
# routing.yml
app_product_create:
path: /my-stores/{store}/products/new
methods: [GET, POST]
defaults:
_controller: sylius.controller.product:createAction
_sylius:
form: app_user_product # Use this form type!
template: AppStoreBundle:Product:create.html.twig # Use a custom template.
factory:
method: createForStore # Use 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 his products.
parameters: [$store]
All other methods have the same level of flexibility and are documented in the [SyliusResourceBundle guide].
Main application is constructed from two main bundles:
SyliusCoreBundle, which is the glue for all other bundles. It is the integration layer of Core component - the heart of Sylius, providing the whole e-commerce framework. SyliusWebBundle, which contains the default web interface, assets, templates and menu builders.
Sylius uses a lot of libraries for various tasks:
...
...
...
...
...
...
...
In the modern world of e-commerce your website is no longer the only point of sale for your goods. Sylius supports multiple-channels and in this guide you will understand them from a technical point of view.
Channel model represents a single sales channel, which can be one of the following things:
Or pretty much any other channel type you can imagine.
The default model has the following basic properties:
Channel configuration also allows you to configure several important aspects:
...
Product model represents unique products in your Sylius store. Every product can have different variations or attributes and has following values:
In many cases, you will have product with different variations. The simplest example would be a T-Shirt available in different sizes and 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.
ProductVariant represents a unique combination of product options and can have its own pricing configuration, inventory tracking etc.
You are also able to use product variations system without the options at all.
Each product has a master variant, which tracks the same information as any other variant. It exists to simplify the internal Sylius logic. Whenever a product is created, a master variant for that product will be created too.
Attributes allow you to define product specific values.
...
...
...
Every address in Sylius is represented by Address model. Default structure has the following fields:
Every country to which you will be shipping your goods lives as Country entity. Country consists of name and isoName.
Province is a smaller area inside of a Country. You can use it to manage provinces or states and assign it to an address as well.
Attribute | Description |
---|---|
id | Unique id of the province |
name | |
country | Reference to a country |
createdAt | Date when province was created |
updatedAt | Date of last update |
This library allows you to define Zones, which represent a specific geographical area.
Every Zone is represented by the following model:
Attribute | Description |
---|---|
id | Unique id of the zone |
name | |
type | String type of zone |
members | Zone members |
createdAt | Date when zone was created |
updatedAt | Date of last update |
Three different types of zones are supported out-of-the-box.
Each zone type has different ZoneMember model, but they all expose the same API:
There are following models and each of them represents a different zone member:
Zones are not very useful by themselves, but they can be 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.
<?php
$zoneMatcher = $this->get('sylius.zone_matcher');
$zone = $zoneMatcher->match($user->getAddress());
Zone matcher can also return all matching zones. (not only the best one)
<?php
$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.
...
Sylius leverages a very simple approach to inventory management. The current stock of an item is stored on the ProductVariant entity.
It is always accessible via simple API:
<?php
echo $productVariant->getOnHand(); // Prints current inventory.
Every variant also has an unique SKU and can be available on demand, if you do not want to have strict inventory tracking.
<?php
$variant = $product->getMasterVariant();
$variant->setAvailableOnDemand(false);
if ($variant->isAvailableOnDemand()) {
// Order any amount you want!
}
Every item sold in the store is represented by InventoryUnit, which has many different states:
For example, if someone puts a product “Book” with quantity “4” in the cart, 4 inventory units are created. This allows us for very precise tracking of all sold/backordered/returned items.
Normally, inventory units are created automatically by Sylius and you do not need to bother. If you want to create some inventory units yourself, you should use the sylius.inventory_unit_factory
service.
<?php
use Sylius\Component\Inventory\Model\InventoryUnitInterface;
$variant = // Get variant from product.
$inventoryUnits = $this->get('sylius.inventory_unit_factory')->create($variant, 6, InventoryUnitInterface::STATE_BACKORDER);
$inventoryUnits
is now ArrayCollection with 6 instances of InventoryUnit, referencing the ProductVariant and with state backordered.
Inventory operator is the service responsible for managing the stock amounts of every ProductVariant with following methods:
...
...
Order model is one of the most important in Sylius, it represents the order placed via your store! It has a very consistent and clear API, which allows you to easily manipulate and process orders.
Order holds a reference to specific User, which is available through getUser()
method:
echo $order->getUser()->getEmail(); // john@example.com
When creating order programatically, you can define the user yourself:
$order = $this->get('sylius.repository.order')->createNew();
$john = $this->get('sylius.repository.user')->find(3);
$order->setUser($john);
Order may not have reference to User in case when Order was created by guest.
By default, every order has its own billing and shipping address, which are heavily used through whole process. Both of them are represented by Address model.
$shippingAddress = $order->getShippingAddress();
echo 'Shipped to: '.$shippingAddress->getCountry();
Order holds a collection of OrderItem instances.
OrderItem model has the attributes listed below:
Attribute | Description |
---|---|
id | Unique id of the item |
order | Reference to an Order |
variant | Reference to Variant |
product | Product loaded via Variant |
unitPrice | The price of a single unit |
quantity | Quantity of sold item |
adjustments | Collection of Adjustments |
adjustmentsTotal | Total value of adjustments |
total | Order grand total |
createdAt | Date when order was created |
updatedAt | Date of last change |
...
Order has also its general state, which can have the following values:
...
...
...
...
...
...
...
...
...
...
Sylius contains a very flexible payments management system with support for many gateways. (payment providers) We are using very powerful payment abstraction library, called [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.
Every payment in Sylius, successful or failed, is represented by Payment model, which contains basic information and reference to appropriate order.
Payment has following attributes:
All these properties are easily accessible through simple API:
<?php
echo $payment->getAmount() . $payment->getCurrency();
$order = $payment->getOrder(); // Get the order.
echo $payment->getMethod()->getName(); // Get the name of payment method used.
We are using [StateMachine] library to manage all payment states, here is the full list of defaults:
Of course, your can define your own states and transitions to create a workflow, that perfectly matches your business. Full configuration can be seen here.
Changes to payment happen mostly through applying appropriate transitions.
A [PaymentMethod] represents a way that your customer pays during the checkout process. It holds a reference to specific gateway with custom configuration. You can have different payment methods using the same gateway, like PayPal or Stripe. Default model of payment method contains the following fields:
...
...
...
...
Sylius has a very flexible taxation system, which allows you to apply appropriate taxes for different items, billing zones and use custom calculators.
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 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] chapter of this book.
A tax rate is essentially a percentage amount charged based on the sales price. Tax rates also contain other important information:
...
...
...
...
Pricing is the part of Sylius responsible for calculating the product prices for the cart. This functionality comes from the SyliusPricingBundle.
ProductVariant implements the PriceableInterface and has following attributes available:
Примечание
All prices in Sylius are represented by integers, so 14.99$ is stored as 1499 in the database.
First parameter holds the name of calculator type and second contains the configuration array for this particular calculator.
For example, if you have a product without any variations, its master variant can have a simple setup with:
A calculator is a very simple service implement PriceCalculatorInterface and has a very important calculate(priceable, configuration, context) method.
Every time product is added, removed to cart and generally on every cart modification event, this method is used to calculate the unit price. It takes the appropriate calculator configured on the product, together with the configuration and context. Context is taken from the product as well, but context is coming from the cart.
Currently the context is really simple and contains the following details:
You can easily [implement your own pricing calculator] and allow store managers to define complex pricing rules for any merchandise.
...
...
...
...
...
...
...
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 store all money values in the default currency and convert them to different currency with current rates or specific rates.
Every currency is represented by Currency entity and holds basic information:
The default currency has exchange rate of “1.000”.
By default, user can switch his current currency in the frontend of the store.
To manage the currently used currency, we use CurrencyContext. You can always access it through sylius.context.currency
id.
<?php
public function fooAction()
{
$currency = $this->get('sylius.context.currency')->getCurrency();
}
To change the currently used currency, you can simply use the setCurrency()
method of context service.
<?php
public function fooAction()
{
$this->get('sylius.context.currency')->setCurrency('PLN');
}
The currency context can be injected into your custom service and give you access to currently used currency.
...
The default menu for selecting currency is using a special service called sylius.currency_provider
, which returns all enabled currencies.
This is your entry point if you would like override this logic and return different currencies for various scenarios.
<?php
public function fooAction()
{
$currencies = $this->get('sylius.currency_provider')->getAvailableCurrencies();
foreach ($currencies as $currency) {
echo $currency->getCode();
}
}
...
To support multiple site languages, we use Locale model with the following set of fields:
With the default configuration, customers are able to change the store language in the frontend.
To manage the currently used language, we use LocaleContext. You can always access it through sylius.context.locale
id.
<?php
public function fooAction()
{
$locale = $this->get('sylius.context.locale')->getLocale();
echo $locale; // pl_PL
}
To change the locale, you can simply use the setLocale()
method of the context service.
<?php
public function fooAction()
{
$this->get('sylius.context.locale')->setLocale('de_DE'); // Store will be displayed in German.
}
The locale context can be injected into your custom service and give you access to currently used locale.
Service sylius.locale_provider
is responsible for returning all languages available to the current user. By default, it filters out all disabled locales.
You can easily modify this logic by overriding this component.
<?php
public function fooAction()
{
$locales = $this->get('sylius.locale_provider')->getAvailableLocales();
foreach ($locales as $locale) {
echo $locale->getCode();
}
}
To get all languages configured in the store, including the disabled ones, you can simply use the repository.
<?php
$locales = $this->get('sylius.repository.locale')->findAll();
...
...
...
...
...
...
...
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.
Every time new customer registers via registration form or checkout, this e-mail is sent to him.
SyliusWebBundle:Frontend/Email:customerWelcome.html.twig
You also have the following parameters available:
This e-mail is sent when order is paid. Template name is:
SyliusWebBundle:Frontend/Email:orderConfirmation.html.twig
You also have the following parameters available:
In the backend, you can comment orders and optionally notify the customer, this template is used:
SyliusWebBundle:Frontend/Email:orderComment.html.twig
You also have the following parameters available:
...
This chapter covers the REST API of Sylius platform.
This part of documentation is about RESTful JSON/XML API for Sylius platform.
Примечание
This documentation assumes you have at least basic experience with REST APIs.
Sylius channels API endpoint is /api/channels
.
To browse all channels available in the Sylius e-commerce platform you can call the following GET request:
GET /api/channels/
Response will contain a paginated list of channels.
STATUS: 200 OK
{
"page":1,
"limit":10,
"pages":1,
"total":3,
"_links":{
"self":{
"href":"\/api\/channels\/?page=1"
},
"first":{
"href":"\/api\/channels\/?page=1"
},
"last":{
"href":"\/api\/channels\/?page=12"
},
"next":{
"href":"\/api\/channels\/?page=2"
}
},
"_embedded":{
"items":[
{
"code": "WEB-UK",
"color": "Red",
"created_at": "2014-11-26T23:00:15+0000",
"currencies": [
],
"enabled": true,
"id": 91,
"locales": [
],
"name": "UK Webstore",
"payment_methods": [
],
"shipping_methods": [
],
"type": "web",
"updated_at": "2014-11-26T23:00:15+0000"
}
]
}
}
You can view a single channel by executing the following request:
GET /api/channels/91
STATUS: 200 OK
{
"code": "WEB-UK",
"color": "Red",
"created_at": "2014-11-26T23:00:15+0000",
"currencies": [
],
"enabled": true,
"id": 91,
"locales": [
],
"name": "UK Webstore",
"payment_methods": [
],
"shipping_methods": [
],
"type": "web",
"updated_at": "2014-11-26T23:00:15+0000"
}
To create a new channel, you can execute the following request:
POST /api/channels/
STATUS: 201 CREATED
{
"code": "WEB-US",
"color": "Blue",
"created_at": "2014-11-26T23:00:15+0000",
"currencies": [
],
"enabled": true,
"id": 92,
"locales": [
],
"name": "US Webstore",
"payment_methods": [
],
"shipping_methods": [
],
"type": "web",
"updated_at": "2014-11-26T23:00:15+0000"
}
You can update an existing channel using PUT or PATCH method:
PUT /api/channels/92
PATCH /api/channels/92
STATUS: 204 NO CONTENT
Sylius orders API endpoint is /api/orders.
You can retrieve the full list order by making the following request:
GET /api/orders/
The response will contain the newly created order information.
STATUS: 200 OK
{
"page":1,
"limit":10,
"pages":12,
"total":120,
"_links":{
"self":{
"href":"\/api\/orders\/?page=1"
},
"first":{
"href":"\/api\/orders\/?page=1"
},
"last":{
"href":"\/api\/orders\/?page=12"
},
"next":{
"href":"\/api\/orders\/?page=2"
}
},
"_embedded":{
"items":[
{
"id":301,
"completed_at":"2014-11-26T23:00:33+0000",
"number":"000000048",
"items":[
{
"id":1353,
"quantity":3,
"unit_price":9054,
"adjustments":[
],
"adjustments_total":0,
"total":27162,
"immutable":false,
"variant":{
"id":13099,
"master":false,
"object":{
"id":2107,
"name":"T-Shirt \"voluptas\"",
"description":"Non molestias voluptas quae nemo omnis totam. Impedit ad perferendis quaerat sint numquam voluptate eum. Facilis sed accusamus enim repellendus officiis rerum at.",
"created_at":"2014-11-26T23:00:17+0000",
"updated_at":"2014-11-26T23:00:17+0000",
"masterVariant":{
"id":13085,
"master":true,
"options":[
],
"created_at":"2014-11-26T23:00:17+0000",
"updated_at":"2014-11-26T23:00:17+0000",
"available_on":"2014-08-27T08:51:04+0000",
"sku":"43596"
},
"short_description":"Quos in dignissimos in fugit culpa vitae."
},
"created_at":"2014-11-26T23:00:17+0000",
"updated_at":"2014-11-26T23:00:34+0000",
"available_on":"2013-12-10T09:16:56+0000",
"sku":"8808"
},
"inventory_units":[
],
"_links":{
"product":{
"href":"\/api\/products\/2107"
},
"variant":{
"href":"\/api\/products\/2107\/variants\/13099"
}
}
}
],
"items_total":97783,
"adjustments":[
],
"comments":[
],
"adjustments_total":24240,
"total":122023,
"confirmed":true,
"created_at":"2014-04-30T10:41:14+0000",
"updated_at":"2014-11-26T23:00:34+0000",
"state":"pending",
"email":"ygrant@example.com",
"expires_at":"2014-11-27T02:00:33+0000",
"user":{
"id":476,
"username":"ygrant@example.com",
"username_canonical":"ygrant@example.com",
"email":"ygrant@example.com",
"email_canonical":"ygrant@example.com",
"enabled":false,
"groups":[
],
"locked":false,
"expired":false,
"roles":[
],
"credentials_expired":false
},
"channel":{
"id":91,
"code":"WEB-UK",
"name":"UK Webstore",
"type":"web",
"color":"Red",
"enabled":true,
"created_at":"2014-11-26T23:00:15+0000",
"updated_at":"2014-11-26T23:00:15+0000",
},
"shipping_address":{
},
"billing_address":{
},
"payments":[
],
"shipments":[
],
"currency":"GBP",
"checkout_state":"cart"
}
]
}
}
You can view a single order by executing the following request:
GET /api/orders/24/
STATUS: 200 OK
{
"id":301,
"completed_at":"2014-11-26T23:00:33+0000",
"number":"000000048",
"items":[
{
"id":1353,
"quantity":3,
"unit_price":9054,
"adjustments":[
],
"adjustments_total":0,
"total":27162,
"immutable":false,
"variant":{
"id":13099,
"master":false,
"object":{
"id":2107,
"name":"T-Shirt \"voluptas\"",
"description":"Non molestias voluptas quae nemo omnis totam. Impedit ad perferendis quaerat sint numquam voluptate eum. Facilis sed accusamus enim repellendus officiis rerum at.",
"created_at":"2014-11-26T23:00:17+0000",
"updated_at":"2014-11-26T23:00:17+0000",
"masterVariant":{
"id":13085,
"master":true,
"options":[
],
"created_at":"2014-11-26T23:00:17+0000",
"updated_at":"2014-11-26T23:00:17+0000",
"available_on":"2014-08-27T08:51:04+0000",
"sku":"43596"
},
"short_description":"Quos in dignissimos in fugit culpa vitae."
},
"created_at":"2014-11-26T23:00:17+0000",
"updated_at":"2014-11-26T23:00:34+0000",
"available_on":"2013-12-10T09:16:56+0000",
"sku":"8808"
},
"inventory_units":[
{
"id":4061,
"inventory_state":"onhold",
"created_at":"2014-11-26T23:00:34+0000",
"updated_at":"2014-11-26T23:00:34+0000",
"_links":{
"order":{
"href":"\/app_dev.php\/api\/orders\/301"
}
}
},
{
"id":4062,
"inventory_state":"onhold",
"created_at":"2014-11-26T23:00:34+0000",
"updated_at":"2014-11-26T23:00:34+0000",
"_links":{
"order":{
"href":"\/app_dev.php\/api\/orders\/301"
}
}
},
{
"id":4063,
"inventory_state":"onhold",
"created_at":"2014-11-26T23:00:34+0000",
"updated_at":"2014-11-26T23:00:34+0000",
"_links":{
"order":{
"href":"\/app_dev.php\/api\/orders\/301"
}
}
}
],
"_links":{
"product":{
"href":"\/app_dev.php\/api\/products\/2107"
},
"variant":{
"href":"\/app_dev.php\/api\/products\/2107\/variants\/13099"
}
}
}
],
"items_total":97783,
"adjustments":[
{
"id":1011,
"label":"tax",
"description":"EU VAT (23%)",
"amount":22490,
"neutral":false,
"locked":false,
"created_at":"2014-11-26T23:00:33+0000",
"updated_at":"2014-11-26T23:00:34+0000"
},
{
"id":1012,
"label":"shipping",
"description":"UPS Ground",
"amount":2500,
"neutral":false,
"locked":false,
"created_at":"2014-11-26T23:00:33+0000",
"updated_at":"2014-11-26T23:00:34+0000"
},
{
"id":1013,
"label":"promotion",
"description":"New Year Sale for 3 and more items.",
"amount":-500,
"neutral":false,
"locked":false,
"created_at":"2014-11-26T23:00:33+0000",
"updated_at":"2014-11-26T23:00:34+0000"
},
{
"id":1014,
"label":"promotion",
"description":"Christmas Sale for orders over 100 EUR.",
"amount":-250,
"neutral":false,
"locked":false,
"created_at":"2014-11-26T23:00:33+0000",
"updated_at":"2014-11-26T23:00:34+0000"
}
],
"comments":[
],
"adjustments_total":24240,
"total":122023,
"confirmed":true,
"created_at":"2014-04-30T10:41:14+0000",
"updated_at":"2014-11-26T23:00:34+0000",
"state":"pending",
"email":"ygrant@example.com",
"expires_at":"2014-11-27T02:00:33+0000",
"user":{
"id":476,
"username":"ygrant@example.com",
"username_canonical":"ygrant@example.com",
"email":"ygrant@example.com",
"email_canonical":"ygrant@example.com",
"enabled":false,
"groups":[
],
"locked":false,
"expired":false,
"roles":[
],
"credentials_expired":false
},
"channel":{
"id":91,
"code":"WEB-UK",
"name":"UK Webstore",
"type":"web",
"color":"Red",
"enabled":true,
"created_at":"2014-11-26T23:00:15+0000",
"updated_at":"2014-11-26T23:00:15+0000",
},
"shipping_address":{
},
"billing_address":{
},
"payments":[
],
"shipments":[
],
"currency":"GBP",
"checkout_state":"cart"
}
To create a new order (cart), you need to execute the following request:
POST /api/orders/
STATUS: 201 CREATED
{
"id":304,
"items":[
],
"items_total":0,
"adjustments":[
],
"comments":[
],
"adjustments_total":0,
"total":0,
"confirmed":true,
"created_at":"2014-11-29T12:29:07+0000",
"updated_at":"2014-11-29T12:29:08+0000",
"state":"cart",
"email":"chelsie.witting@example.com",
"expires_at":"2014-11-29T15:29:07+0000",
"user":{
"id":481,
"username":"chelsie.witting@example.com",
"username_canonical":"chelsie.witting@example.com",
"email":"chelsie.witting@example.com",
"email_canonical":"chelsie.witting@example.com",
"enabled":true,
"groups":[
],
"locked":false,
"expired":false,
"roles":[
],
"credentials_expired":false
},
"channel":{
"id":91,
"code":"WEB-UK",
"name":"UK Webstore",
"type":"web",
"color":"Red",
"enabled":true,
"created_at":"2014-11-26T23:00:15+0000",
"updated_at":"2014-11-26T23:00:15+0000",
},
"payments":[
],
"shipments":[
],
"currency":"USD",
"checkout_state":"cart"
}
You can delete (soft) an order from the system by making the following DELETE call:
DELETE /api/orders/24
STATUS: 204 NO CONTENT
To add an item to order, you simply need to do a POST request:
POST /api/orders/305/items/
Response will contain a representation of the newly created item.
STATUS: 201 CREATED
{
"_links": {
"product": {
"href": "/app_dev.php/api/products/101"
},
"variant": {
"href": "/app_dev.php/api/products/101/variants/779"
}
},
"adjustments": [],
"adjustments_total": 0,
"id": 277,
"immutable": false,
"inventory_units": [
{
"_links": {
"order": {
"href": "/app_dev.php/api/orders/52"
}
},
"created_at": "2014-12-15T13:18:48+0000",
"id": 828,
"inventory_state": "checkout",
"updated_at": "2014-12-15T13:18:48+0000"
},
{
"_links": {
"order": {
"href": "/app_dev.php/api/orders/52"
}
},
"created_at": "2014-12-15T13:18:48+0000",
"id": 829,
"inventory_state": "checkout",
"updated_at": "2014-12-15T13:18:48+0000"
},
{
"_links": {
"order": {
"href": "/app_dev.php/api/orders/52"
}
},
"created_at": "2014-12-15T13:18:48+0000",
"id": 830,
"inventory_state": "checkout",
"updated_at": "2014-12-15T13:18:48+0000"
}
],
"quantity": 3,
"total": 0,
"unit_price": 500000,
"variant": {
"available_on": "2014-04-01T06:43:02+0000",
"created_at": "2014-12-03T09:54:35+0000",
"id": 779,
"master": true,
"object": {
"attributes": [
{
"id": 238,
"name": "Book author",
"presentation": "Author",
"value": "Marlen Yost"
},
{
"id": 239,
"name": "Book ISBN",
"presentation": "ISBN",
"value": "326ccbc7-92d1-3aec-b3af-df8afdc5651d"
},
{
"id": 240,
"name": "Book pages",
"presentation": "Number of pages",
"value": "149"
}
],
"created_at": "2014-12-03T09:54:35+0000",
"description": "Et eveniet voluptas ut magni vero temporibus nihil. Omnis possimus accusantium quia corporis culpa. Et recusandae asperiores qui architecto culpa autem sint accusantium. Officiis iusto accusantium perferendis aliquid ducimus.",
"id": 101,
"name": "Book \"Quidem\" by \"Marlen Yost\"",
"options": [],
"short_description": "Distinctio quos est eaque fugit totam repellendus.",
"updated_at": "2014-12-03T09:54:35+0000"
},
"options": [],
"sku": "326ccbc7-92d1-3aec-b3af-df8afdc5651d",
"updated_at": "2014-12-03T09:54:35+0000"
}
}
After you create a cart (an empty order) and add some items to it, you can start the checkout via API. This basically means updating the order with concrete information, step by step, in a correct order.
Default Sylius checkout via API is constructed from the following steps:
Sylius API endpoint is /api/orders.
After you added some items to the cart, to start the checkout you simply need to provide a shipping address. You can also specify a different billing address if needed.
You need to pass order id in the following url and make a PUT call:
PUT /api/checkouts/44
If you do not specify the billing address block, shipping address will be used for that purpose.
The response will contain the updated order information.
STATUS: 200 OK
{
"adjustments": ,
"adjustments_total": -250,
"shipping_address": {
"_links": {
"country": {
"href": "/app_dev.php/api/countries/9"
}
},
"city": "New York",
"created_at": "2014-12-15T13:37:28+0000",
"first_name": "John",
"id": 105,
"last_name": "Doe",
"postcode": "12435",
"street": "Test",
"updated_at": "2014-12-15T13:37:29+0000"
},
"billing_address": {
"_links": {
"country": {
"href": "/app_dev.php/api/countries/9"
}
},
"city": "New York",
"created_at": "2014-12-15T13:37:28+0000",
"first_name": "John",
"id": 106,
"last_name": "Doe",
"postcode": "12435",
"street": "Test",
"updated_at": "2014-12-15T13:37:29+0000"
},
"channel": {
"_links": {
"self": {
"href": "/app_dev.php/api/channels/3"
}
},
"code": "WEB-US",
"color": "Pink",
"created_at": "2014-12-03T09:54:28+0000",
"enabled": true,
"id": 3,
"name": "United States Webstore",
"type": "web",
"updated_at": "2014-12-03T09:58:29+0000"
},
"checkout_state": "addressing",
"comments": [],
"confirmed": true,
"created_at": "2014-12-15T13:15:22+0000",
"currency": "USD",
"email": "xschaefer@example.com",
"expires_at": "2014-12-15T16:15:22+0000",
"id": 52,
"items": [],
"items_total": 1500000,
"payments": [],
"shipments": [],
"state": "cart",
"total": 1499750,
"updated_at": "2014-12-15T13:37:29+0000",
"user": {
"credentials_expired": false,
"email": "xschaefer@example.com",
"email_canonical": "xschaefer@example.com",
"enabled": true,
"expired": false,
"groups": [],
"id": 5,
"locked": false,
"roles": [],
"username": "xschaefer@example.com",
"username_canonical": "xschaefer@example.com"
}
}
When order contains the address information, we are able to determine the stock locations and available shipping methods. You can get these informations by first calling a GET request on the checkout unique URL.
GET /api/checkouts/44
STATUS: 200 OK
[
{
"methods": [
{
"_links": {
"self": {
"href": "/app_dev.php/api/shipping-methods/4"
},
"zone": {
"href": "/app_dev.php/api/zones/4"
}
},
"calculator": "flexible_rate",
"category_requirement": 1,
"configuration": {
"additional_item_cost": 500,
"additional_item_limit": 10,
"first_item_cost": 4000
},
"created_at": "2014-12-03T09:54:28+0000",
"enabled": true,
"id": 4,
"name": "FedEx World Shipping",
"updated_at": "2014-12-03T09:54:28+0000"
}
],
"shipment": {
"_links": {
"order": {
"href": "/app_dev.php/api/orders/52"
}
},
"created_at": "2014-12-15T14:11:32+0000",
"state": "checkout"
}
}
]
Response contains the proposed shipments and for each, it also has a list of shipping methods available.
Next step is updating the order with the types of shipping method that we have selected. To do so, you need to call another PUT request, but this time with different set of parameters.
You need to pass an id of shipping method for every id, you should obtain them in the previous request.
PUT /api/checkouts/44
Response will contain an updated order information.
STATUS: 200 OK
{
"adjustments": {
},
"adjustments_total": 4750,
"billing_address": {
},
"channel": {
},
"checkout_state": "shipping",
"comments": [],
"confirmed": true,
"created_at": "2014-12-15T13:15:22+0000",
"currency": "USD",
"email": "xschaefer@example.com",
"expires_at": "2014-12-15T16:15:22+0000",
"id": 52,
"items": [
],
"items_total": 1500000,
"payments": [],
"shipments": [
{
"_links": {
"method": {
"href": "/app_dev.php/api/shipping-methods/4"
},
"order": {
"href": "/app_dev.php/api/orders/52"
},
"self": {
"href": "/app_dev.php/api/shipments/51"
}
},
"created_at": "2014-12-15T14:30:40+0000",
"id": 51,
"method": {
"_links": {
"self": {
"href": "/app_dev.php/api/shipping-methods/4"
},
"zone": {
"href": "/app_dev.php/api/zones/4"
}
},
"calculator": "flexible_rate",
"category_requirement": 1,
"configuration": {
"additional_item_cost": 500,
"additional_item_limit": 10,
"first_item_cost": 4000
},
"created_at": "2014-12-03T09:54:28+0000",
"enabled": true,
"id": 4,
"name": "FedEx World Shipping",
"updated_at": "2014-12-03T09:54:28+0000"
},
"state": "checkout",
"updated_at": "2014-12-15T14:30:41+0000"
}
],
"shipping_address": {
},
"state": "cart",
"total": 1504750,
"updated_at": "2014-12-15T14:30:41+0000",
"user": {
}
}
When we are done with shipping choices and we know the final price of an order, we can select a payment method.
To obtain a list of available payment methods for this order, simply call a GET request again:
GET /api/checkouts/44
STATUS: 200 OK
{
"methods": {
"1": {
"_links": {
"self": {
"href": "/app_dev.php/api/payment-methods/1"
}
},
"created_at": "2014-12-03T09:54:28+0000",
"id": 1,
"name": "Dummy",
"updated_at": "2014-12-03T09:54:28+0000"
},
"2": {
"_links": {
"self": {
"href": "/app_dev.php/api/payment-methods/2"
}
},
"created_at": "2014-12-03T09:54:28+0000",
"id": 2,
"name": "Paypal Express Checkout",
"updated_at": "2014-12-03T09:54:28+0000"
},
"3": {
"_links": {
"self": {
"href": "/app_dev.php/api/payment-methods/3"
}
},
"created_at": "2014-12-03T09:54:28+0000",
"id": 3,
"name": "Stripe",
"updated_at": "2014-12-03T09:54:28+0000"
},
"4": {
"_links": {
"self": {
"href": "/app_dev.php/api/payment-methods/4"
}
},
"created_at": "2014-12-03T09:54:28+0000",
"id": 4,
"name": "Be2bill",
"updated_at": "2014-12-03T09:54:28+0000"
},
"5": {
"_links": {
"self": {
"href": "/app_dev.php/api/payment-methods/5"
}
},
"created_at": "2014-12-03T09:54:28+0000",
"id": 5,
"name": "Stripe Checkout",
"updated_at": "2014-12-03T09:54:28+0000"
}
},
"payment": {
"_links": {
"order": {
"href": "/app_dev.php/api/orders/52"
}
},
"amount": 1504750,
"created_at": "2014-12-15T14:57:28+0000",
"currency": "USD",
"state": "new"
}
}
With that information, another PUT request with the id of payment method is enough to proceed:
PUT /api/checkouts/44
Response will contain the updated order information.
STATUS: 200 OK
{
"adjustments": [
],
"adjustments_total": 4750,
"billing_address": {
},
"channel": {
},
"checkout_state": "payment",
"comments": [],
"confirmed": true,
"created_at": "2014-12-15T13:15:22+0000",
"currency": "USD",
"email": "xschaefer@example.com",
"expires_at": "2014-12-15T16:15:22+0000",
"id": 52,
"items": [
],
"items_total": 1500000,
"payments": [
{
"_links": {
"order": {
"href": "/app_dev.php/api/orders/52"
},
"payment-method": {
"href": "/app_dev.php/api/payment-methods/1"
},
"self": {
"href": "/app_dev.php/api/payments/51"
}
},
"amount": 1504750,
"created_at": "2014-12-15T15:02:54+0000",
"currency": "USD",
"id": 51,
"method": {
"_links": {
"self": {
"href": "/app_dev.php/api/payment-methods/1"
}
},
"created_at": "2014-12-03T09:54:28+0000",
"id": 1,
"name": "Dummy",
"updated_at": "2014-12-03T09:54:28+0000"
},
"state": "new",
"updated_at": "2014-12-15T15:02:55+0000"
}
],
"shipments": [
],
"shipping_address": {
},
"state": "cart",
"total": 1504750,
"updated_at": "2014-12-15T15:02:55+0000",
"user": {
}
}
Now your order is fully constructed, you can get its latest snapshot by calling your last GET request:
GET /api/checkouts/44
STATUS: 200 OK
{
"adjustments": [
{
"amount": 0,
"created_at": "2014-12-15T13:37:29+0000",
"description": "No tax (0%)",
"id": 205,
"label": "tax",
"locked": false,
"neutral": false,
"updated_at": "2014-12-15T13:37:29+0000"
},
{
"amount": 5000,
"created_at": "2014-12-15T14:30:41+0000",
"description": "FedEx World Shipping",
"id": 207,
"label": "shipping",
"locked": false,
"neutral": false,
"updated_at": "2014-12-15T14:30:41+0000"
},
{
"amount": -250,
"created_at": "2014-12-15T14:30:41+0000",
"description": "Christmas Sale for orders over 100 EUR.",
"id": 208,
"label": "promotion",
"locked": false,
"neutral": false,
"updated_at": "2014-12-15T14:30:41+0000"
}
],
"adjustments_total": 4750,
"billing_address": {
"_links": {
"country": {
"href": "/app_dev.php/api/countries/9"
}
},
"city": "New York",
"created_at": "2014-12-15T13:37:28+0000",
"first_name": "John",
"id": 106,
"last_name": "Doe",
"postcode": "12435",
"street": "Test",
"updated_at": "2014-12-15T13:37:29+0000"
},
"channel": {
"_links": {
"self": {
"href": "/app_dev.php/api/channels/3"
}
},
"code": "WEB-US",
"color": "Pink",
"created_at": "2014-12-03T09:54:28+0000",
"enabled": true,
"id": 3,
"name": "United States Webstore",
"type": "web",
"updated_at": "2014-12-03T09:58:29+0000"
},
"checkout_state": "payment",
"comments": [],
"confirmed": true,
"created_at": "2014-12-15T13:15:22+0000",
"currency": "USD",
"email": "xschaefer@example.com",
"expires_at": "2014-12-15T16:15:22+0000",
"id": 52,
"items": [
{
"_links": {
"product": {
"href": "/app_dev.php/api/products/101"
},
"variant": {
"href": "/app_dev.php/api/products/101/variants/779"
}
},
"adjustments": [],
"adjustments_total": 0,
"id": 277,
"immutable": false,
"inventory_units": [
{
"_links": {
"order": {
"href": "/app_dev.php/api/orders/52"
}
},
"created_at": "2014-12-15T13:18:48+0000",
"id": 828,
"inventory_state": "checkout",
"updated_at": "2014-12-15T14:30:41+0000"
},
{
"_links": {
"order": {
"href": "/app_dev.php/api/orders/52"
}
},
"created_at": "2014-12-15T13:18:48+0000",
"id": 829,
"inventory_state": "checkout",
"updated_at": "2014-12-15T14:30:41+0000"
},
{
"_links": {
"order": {
"href": "/app_dev.php/api/orders/52"
}
},
"created_at": "2014-12-15T13:18:48+0000",
"id": 830,
"inventory_state": "checkout",
"updated_at": "2014-12-15T14:30:41+0000"
}
],
"quantity": 3,
"total": 1500000,
"unit_price": 500000,
"variant": {
"available_on": "2014-04-01T06:43:02+0000",
"created_at": "2014-12-03T09:54:35+0000",
"id": 779,
"master": true,
"object": {
"attributes": [
{
"id": 238,
"name": "Book author",
"presentation": "Author",
"value": "Marlen Yost"
},
{
"id": 239,
"name": "Book ISBN",
"presentation": "ISBN",
"value": "326ccbc7-92d1-3aec-b3af-df8afdc5651d"
},
{
"id": 240,
"name": "Book pages",
"presentation": "Number of pages",
"value": "149"
}
],
"created_at": "2014-12-03T09:54:35+0000",
"description": "Et eveniet voluptas ut magni vero temporibus nihil. Omnis possimus accusantium quia corporis culpa. Et recusandae asperiores qui architecto culpa autem sint accusantium. Officiis iusto accusantium perferendis aliquid ducimus.",
"id": 101,
"name": "Book \"Quidem\" by \"Marlen Yost\"",
"options": [],
"short_description": "Distinctio quos est eaque fugit totam repellendus.",
"updated_at": "2014-12-03T09:54:35+0000"
},
"options": [],
"sku": "326ccbc7-92d1-3aec-b3af-df8afdc5651d",
"updated_at": "2014-12-03T09:54:35+0000"
}
}
],
"items_total": 1500000,
"payments": [
{
"_links": {
"order": {
"href": "/app_dev.php/api/orders/52"
},
"payment-method": {
"href": "/app_dev.php/api/payment-methods/1"
},
"self": {
"href": "/app_dev.php/api/payments/51"
}
},
"amount": 1504750,
"created_at": "2014-12-15T15:02:54+0000",
"currency": "USD",
"id": 51,
"method": {
"_links": {
"self": {
"href": "/app_dev.php/api/payment-methods/1"
}
},
"created_at": "2014-12-03T09:54:28+0000",
"id": 1,
"name": "Dummy",
"updated_at": "2014-12-03T09:54:28+0000"
},
"state": "new",
"updated_at": "2014-12-15T15:02:55+0000"
}
],
"shipments": [
{
"_links": {
"method": {
"href": "/app_dev.php/api/shipping-methods/4"
},
"order": {
"href": "/app_dev.php/api/orders/52"
},
"self": {
"href": "/app_dev.php/api/shipments/51"
}
},
"created_at": "2014-12-15T14:30:40+0000",
"id": 51,
"method": {
"_links": {
"self": {
"href": "/app_dev.php/api/shipping-methods/4"
},
"zone": {
"href": "/app_dev.php/api/zones/4"
}
},
"calculator": "flexible_rate",
"category_requirement": 1,
"configuration": {
"additional_item_cost": 500,
"additional_item_limit": 10,
"first_item_cost": 4000
},
"created_at": "2014-12-03T09:54:28+0000",
"enabled": true,
"id": 4,
"name": "FedEx World Shipping",
"updated_at": "2014-12-03T09:54:28+0000"
},
"state": "checkout",
"updated_at": "2014-12-15T14:30:41+0000"
}
],
"shipping_address": {
"_links": {
"country": {
"href": "/app_dev.php/api/countries/9"
}
},
"city": "New York",
"created_at": "2014-12-15T13:37:28+0000",
"first_name": "John",
"id": 105,
"last_name": "Doe",
"postcode": "12435",
"street": "Test",
"updated_at": "2014-12-15T13:37:29+0000"
},
"state": "cart",
"total": 1504750,
"updated_at": "2014-12-15T15:02:55+0000",
"user": {
"credentials_expired": false,
"email": "xschaefer@example.com",
"email_canonical": "xschaefer@example.com",
"enabled": true,
"expired": false,
"groups": [],
"id": 5,
"locked": false,
"roles": [],
"username": "xschaefer@example.com",
"username_canonical": "xschaefer@example.com"
}
}
This is how your final order looks, if you are happy with that response, simply call another PUT to confirm the checkout, which will became a real order and appear in the backend.
PUT /api/checkouts/44
Final response contains the full order information, now you can call the purchase action to actually pay for the order.
STATUS: 200 OK
{"to": "do"}
TODO.
PUT /api/checkouts/44
You can check the payment status in the payment lists on order response.
STATUS: 200 OK
{"to": "do"}
Sylius products catalogue API endpoint is /api/products and it allows for browsing, creating & editing product information.
To browse all products available in the store you should call the following GET request:
GET /api/products/
Response will contain a paginated list of products.
STATUS: 200 OK
{
"page":1,
"limit":10,
"pages":12,
"total":120,
"_links":{
"self":{
"href":"\/api\/products\/?page=1"
},
"first":{
"href":"\/api\/products\/?page=1"
},
"last":{
"href":"\/api\/products\/?page=12"
},
"next":{
"href":"\/api\/products\/?page=2"
}
},
"_embedded":{
"items":[
{
"created_at": "2014-11-26T23:00:20+0000",
"description": "Facere ipsum id eveniet rem omnis et. Totam vero eos eveniet nihil sint. Labore occaecati qui placeat fugit.",
"id": 2173,
"masterVariant": {
"available_on": "2014-03-29T01:30:04+0000",
"created_at": "2014-11-26T23:00:20+0000",
"id": 13403,
"master": true,
"options": [],
"sku": "68051",
"updated_at": "2014-11-26T23:00:20+0000"
},
"name": "T-Shirt \"ipsam\"",
"short_description": "Aut rerum quasi neque.",
"updated_at": "2014-11-26T23:00:20+0000"
}
]
}
}
You can view a single product by executing the following request:
GET /api/products/2173
STATUS: 200 OK
{
"created_at": "2014-11-26T23:00:20+0000",
"description": "Facere ipsum id eveniet rem omnis et. Totam vero eos eveniet nihil sint. Labore occaecati qui placeat fugit.",
"id": 2173,
"masterVariant": {
"available_on": "2014-03-29T01:30:04+0000",
"created_at": "2014-11-26T23:00:20+0000",
"id": 13403,
"master": true,
"options": [],
"sku": "68051",
"updated_at": "2014-11-26T23:00:20+0000"
},
"name": "T-Shirt \"ipsam\"",
"short_description": "Aut rerum quasi neque.",
"updated_at": "2014-11-26T23:00:20+0000"
}
To create a new product, you can execute the following request:
POST /api/products/
STATUS: 201 CREATED
{
"created_at": "2014-11-29T14:23:57+0000",
"description": "Bar",
"id": 2181,
"masterVariant": {
"available_on": "2014-11-29T14:23:57+0000",
"created_at": "2014-11-29T14:23:57+0000",
"id": 13468,
"master": true,
"options": [],
"updated_at": "2014-11-29T14:23:58+0000"
},
"name": "Foo",
"updated_at": "2014-11-29T14:23:58+0000"
}
You can update an existing product using PUT or PATCH method:
PUT /api/products/2181
PATCH /api/products/2181
STATUS: 204 NO CONTENT
Sylius users API endpoint is /api/users and it allows for browsing, creating & editing user data.
To browse all users available in the store you should call the following GET request:
GET /api/users/
Response will contain a paginated list of users.
STATUS: 200 OK
{
"page":1,
"limit":10,
"pages":10,
"total":100,
"_links":{
"self":{
"href":"\/api\/users\/?page=1"
},
"first":{
"href":"\/api\/users\/?page=1"
},
"last":{
"href":"\/api\/users\/?page=12"
},
"next":{
"href":"\/api\/users\/?page=2"
}
},
"_embedded":{
"items":[
{
"credentials_expired": false,
"email": "chelsie.witting@example.com",
"email_canonical": "chelsie.witting@example.com",
"enabled": true,
"expired": false,
"groups": [],
"id": 481,
"locked": false,
"password": "EbOLtGHYxJKotA+bdb9BElhXPd8qZsnlo8CjDdCk+qFR22EEZJoOTntBX/M5GUXw2vnEqOKIEVPaJr66yxXqqQ==",
"roles": [],
"salt": "h9ltmmawvdsk08oocogkws4sg040k04",
"username": "chelsie.witting@example.com",
"username_canonical": "chelsie.witting@example.com"
}
]
}
}
You can view a single user by executing the following request:
GET /api/users/481
STATUS: 200 OK
{
"credentials_expired": false,
"email": "chelsie.witting@example.com",
"email_canonical": "chelsie.witting@example.com",
"enabled": true,
"expired": false,
"groups": [],
"id": 481,
"locked": false,
"password": "EbOLtGHYxJKotA+bdb9BElhXPd8qZsnlo8CjDdCk+qFR22EEZJoOTntBX/M5GUXw2vnEqOKIEVPaJr66yxXqqQ==",
"roles": [],
"salt": "h9ltmmawvdsk08oocogkws4sg040k04",
"username": "chelsie.witting@example.com",
"username_canonical": "chelsie.witting@example.com"
}
To create a new user, you can execute the following request:
POST /api/users/
STATUS: 201 CREATED
{
"credentials_expired": false,
"email": "chelsie.witting@example.com",
"email_canonical": "chelsie.witting@example.com",
"enabled": true,
"expired": false,
"groups": [],
"id": 481,
"locked": false,
"password": "EbOLtGHYxJKotA+bdb9BElhXPd8qZsnlo8CjDdCk+qFR22EEZJoOTntBX/M5GUXw2vnEqOKIEVPaJr66yxXqqQ==",
"roles": [],
"salt": "h9ltmmawvdsk08oocogkws4sg040k04",
"username": "chelsie.witting@example.com",
"username_canonical": "chelsie.witting@example.com"
}
You can update an existing user using PUT or PATCH method:
PUT /api/users/481
PATCH /api/users/481
STATUS: 204 NO CONTENT
You can delete (soft) a user from the system by making the following DELETE call:
DELETE /api/users/24
STATUS: 204 NO CONTENT
You can create a new password resetting request by calling the following API endpoint:
POST /api/password-resetting-requests/
The successful response will contain the user object with a confirmation token and date of password request.
STATUS: 200 OK
{
"confirmation_token": "dzOeNrmdnn20IVHBW2Uaq-yAYsO2sY2hCXhfKdYl_xM",
"credentials_expired": false,
"email": "sylius@example.com",
"email_canonical": "sylius@example.com",
"enabled": true,
"expired": false,
"groups": [],
"id": 1,
"last_login": "2014-12-08T13:08:02+0000",
"locked": false,
"password_requested_at": "2014-12-08T14:19:26+0000",
"roles": [
"ROLE_SYLIUS_ADMIN"
],
"username": "sylius@example.com",
"username_canonical": "sylius@example.com"
}
Sylius shipments API endpoint is /api/shipments.
You can retrieve the full list shipment by making the following request:
GET /api/shipments/
STATUS: 200 OK
{
"page":1,
"limit":10,
"pages":8,
"total":80,
"_links":{
"self":{
"href":"\/api\/shipments\/?page=1"
},
"first":{
"href":"\/api\/shipments\/?page=1"
},
"last":{
"href":"\/api\/shipments\/?page=12"
},
"next":{
"href":"\/api\/shipments\/?page=2"
}
},
"_embedded":{
"items":[
{
"_links": {
"method": {
"href": "/api/shipping-methods/120"
},
"order": {
"href": "/api/orders/302"
},
"self": {
"href": "/api/shipments/251"
}
},
"created_at": "2014-11-26T23:00:34+0000",
"id": 251,
"method": {
"_links": {
"self": {
"href": "/api/shipping-methods/120"
},
"zone": {
"href": "/api/zones/120"
}
},
"calculator": "flexible_rate",
"category_requirement": 1,
"configuration": {
"additional_item_cost": 500,
"additional_item_limit": 10,
"first_item_cost": 4000
},
"created_at": "2014-11-26T23:00:15+0000",
"enabled": true,
"id": 120,
"name": "FedEx World Shipping",
"updated_at": "2014-11-26T23:00:15+0000"
},
"state": "backordered",
"updated_at": "2014-11-26T23:00:34+0000"
}
]
}
}
You can view a single shipment by executing the following request:
GET /api/shipments/251
STATUS: 200 OK
{
"_links": {
"method": {
"href": "/api/shipping-methods/120"
},
"order": {
"href": "/api/orders/302"
},
"self": {
"href": "/api/shipments/251"
}
},
"created_at": "2014-11-26T23:00:34+0000",
"id": 251,
"method": {
"_links": {
"self": {
"href": "/api/shipping-methods/120"
},
"zone": {
"href": "/api/zones/120"
}
},
"calculator": "flexible_rate",
"category_requirement": 1,
"configuration": {
"additional_item_cost": 500,
"additional_item_limit": 10,
"first_item_cost": 4000
},
"created_at": "2014-11-26T23:00:15+0000",
"enabled": true,
"id": 120,
"name": "FedEx World Shipping",
"updated_at": "2014-11-26T23:00:15+0000"
},
"state": "backordered",
"updated_at": "2014-11-26T23:00:34+0000"
}
Sylius payment API endpoint is /api/payments.
You can retrieve the full list payment by making the following request:
GET /api/payments/
STATUS: 200 OK
{
"page":1,
"limit":10,
"pages":8,
"total":80,
"_links":{
"self":{
"href":"\/api\/payments\/?page=1"
},
"first":{
"href":"\/api\/payments\/?page=1"
},
"last":{
"href":"\/api\/payments\/?page=12"
},
"next":{
"href":"\/api\/payments\/?page=2"
}
},
"_embedded":{
"items":[
{"to": "do"}
]
}
}
You can view a single payment by executing the following request:
GET /api/payments/251
Sylius promotion API endpoint is /api/promotions.
You can retrieve the full list of promotionns by making the following request:
GET /api/promotions
STATUS: 200 OK
{
"page":1,
"limit":10,
"pages":1,
"total":3,
"_links":{
"self":{
"href":"\/api\/promotions\/?page=1"
},
"first":{
"href":"\/api\/promotions\/?page=1"
},
"last":{
"href":"\/api\/promotions\/?page=12"
},
"next":{
"href":"\/api\/promotions\/?page=2"
}
},
"_embedded":{
"items":[
{
"_links": {
"coupons": {
"href": "/app_dev.php/api/promotions/1/coupons/"
},
"self": {
"href": "/app_dev.php/api/promotions/1"
}
},
"actions": [
{
"configuration": {
"amount": 500
},
"id": 1,
"type": "fixed_discount"
}
],
"coupon_based": false,
"created_at": "2014-12-03T09:54:28+0000",
"exclusive": false,
"id": 1,
"name": "New Year",
"priority": 0,
"rules": [
{
"configuration": {
"count": 3,
"equal": true
},
"id": 1,
"type": "item_count"
}
],
"updated_at": "2014-12-03T09:54:28+0000",
"used": 0
}
]
}
}
You can view a single promotion by executing the following request:
GET /api/promotions/1
STATUS: 200 OK
{
"_links": {
"coupons": {
"href": "/app_dev.php/api/promotions/1/coupons/"
},
"self": {
"href": "/app_dev.php/api/promotions/1"
}
},
"actions": [
{
"configuration": {
"amount": 500
},
"id": 1,
"type": "fixed_discount"
}
],
"coupon_based": false,
"created_at": "2014-12-03T09:54:28+0000",
"exclusive": false,
"id": 1,
"name": "New Year",
"priority": 0,
"rules": [
{
"configuration": {
"count": 3,
"equal": true
},
"id": 1,
"type": "item_count"
}
],
"updated_at": "2014-12-03T09:54:28+0000",
"used": 0
}
You can delete a promotion from the system by making the following DELETE call:
DELETE /api/promotions/1
STATUS: 204 NO CONTENT
You can get the coupons associated with given promotion by performing the following request:
GET /api/promotions/1/coupons
STATUS: 200 OK
{
"_embedded": {
"items": [
{
"_links": {
"promotion": {
"href": "/api/promotions/1"
},
"self": {
"href": "/api/promotions/1/coupons/1"
}
},
"code": "XAETWESF",
"id": 1,
"usage_limit": 1,
"used": 0
}
]
},
"_links": {
"first": {
"href": "/api/promotions/1/coupons/?page=1&limit=10"
},
"last": {
"href": "/api/promotions/1/coupons/?page=1&limit=10"
},
"self": {
"href": "/api/promotions/1/coupons/?page=1&limit=10"
}
},
"limit": 10,
"page": 1,
"pages": 1,
"total": 1
}
To create a new coupon for given promotion, you can execute the following request:
POST /api/promotion/1/coupons/
STATUS: 201 CREATED
{
"_links": {
"promotion": {
"href": "/api/promotions/1"
},
"self": {
"href": "/api/promotions/1/coupons/2"
}
},
"code": "SUPER-AWESOME-SALE",
"id": 1,
"usage_limit": 3,
"used": 0
}
Sylius stock locations API endpoint is /api/stock-locations
.
To browse all stock locations configured, use the following request:
GET /api/stock-locations/
Response will contain a paginated list of stock locations.
STATUS: 200 OK
{
"_embedded": {
"items": [
{
"_links": {
"self": {
"href": "/api/stock-locations/57"
}
},
"address": {
"_links": {
"country": {
"href": "/api/countries/7517"
}
},
"city": "Naderton",
"created_at": "2014-11-26T23:00:21+0000",
"first_name": "Sabrina",
"id": 519,
"last_name": "Roberts",
"postcode": "45449-6358",
"street": "75382 Larkin Junctions",
"updated_at": "2014-11-26T23:00:21+0000"
},
"code": "LONDON-1",
"created_at": "2014-11-26T23:00:21+0000",
"enabled": true,
"id": 57,
"name": "London Werehouse",
"updated_at": "2014-11-26T23:00:21+0000"
}
]
},
"_links": {
"first": {
"href": "/api/stock-locations/?page=1&limit=10"
},
"last": {
"href": "/api/stock-locations/?page=1&limit=10"
},
"self": {
"href": "/api/stock-locations/?page=1&limit=10"
}
},
"limit": 10,
"page": 1,
"pages": 1,
"total": 4
}
You can view a single stock location by executing the following request:
GET /api/stock-locations/519
STATUS: 200 OK
{
"_links": {
"self": {
"href": "/api/stock-locations/57"
}
},
"address": {
"_links": {
"country": {
"href": "/api/countries/7517"
}
},
"city": "Naderton",
"created_at": "2014-11-26T23:00:21+0000",
"first_name": "Sabrina",
"id": 519,
"last_name": "Roberts",
"postcode": "45449-6358",
"street": "75382 Larkin Junctions",
"updated_at": "2014-11-26T23:00:21+0000"
},
"code": "LONDON-1",
"created_at": "2014-11-26T23:00:21+0000",
"enabled": true,
"id": 57,
"name": "London Werehouse",
"updated_at": "2014-11-26T23:00:21+0000"
}
To create a new stock location, you must execute the following request:
POST /api/stock-locations/
STATUS: 201 CREATED
{
"_links": {
"self": {
"href": "/api/stock-locations/58"
}
},
"code": "LONDON-2",
"created_at": "2014-11-26T23:00:21+0000",
"enabled": true,
"id": 58,
"name": "London Werehouse II",
"updated_at": "2014-11-26T23:00:21+0000"
}
The User’s guide around the Sylius interface and configuration.
There are several ways to install Sylius.
Either you’re installing it to contribute, in which case you may prefer Sylius/Sylius, or you’re bootstrapping a new e-commerce project, and you’d prefer using Sylius/Sylius-Standard.
Предупреждение
Why two versions ? The reason is simple: Sylius/Sylius is the central repository, where all code and commits are contributed to. All the other repositories are splitted from this main repository.
Sylius-Standard is just a distribution including these splitted repositories.
We assume you’re familiar with Composer, a dependency manager for PHP. Otherwise, check how to install Composer.
$ composer create-project -s dev sylius/sylius # or sylius/sylius-standard
$ cd sylius # or sylius-standard
$ php app/console sylius:install
$ git clone git@github.com:Sylius/Sylius.git # or Sylius-Standard
$ cd Sylius # or Sylius-Standard
$ composer install
$ php app/console sylius:install
Specific solutions for specific needs.
We will to show you how to set up the sylius registry. In this example, we will register two price calculators.
<!-- Resources/config/services.xml -->
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="app.price.calculator.registry.class">App\Component\Registry\ServiceRegistry</parameter>
<parameter key="app.price.calculator.interface">App\Bundle\MyBundle\PriceCalculatorInterface</parameter>
</parameters>
<services>
<!-- You need to declare the registry as a service, it expect as argument the type of object that you want to register -->
<service id="app.price.calculator.registry" class="%app.price.calculator.registry.class%">
<argument>%app.price.calculator.interface%</argument>
</service>
<!-- Don't forget that a registry don't create object. You need to create them before and register them into the registry after -->
<service id="app.price.calculator.default" class="...">
<service id="app.price.calculator.custom" class="...">
</services>
</container>
namespace App\Bundle\MyBundle\DependencyInjection\Compiler;
class RegisterCalculatorServicePass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
// You can check if your registry is defined
if (!$container->hasDefinition('app.price.calculator.registry')) {
return;
}
$registryDefinition = $container->getDefinition('app.price.calculator.registry');
// You can register your services like this
$registryDefinition->addMethodCall(
'register',
array(
'default',
new Reference('app.price.calculator.default'),
)
);
$registryDefinition->addMethodCall(
'register',
array(
'custom',
new Reference('app.price.calculator.default'),
)
);
}
}
Finally, you need to register your custom pass with the container
namespace App\Bundle\MyBundle;
class AppMyBundleBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new RegisterServicePass());
}
}
Learn about how Sylius integrates with third-party applications.
Welcome to Sylius Integrations, where you can learn about all available connectors, bridges and integrations with other applications.
Migrating to Sylius from other e-commerce platforms.
Documentation of all Sylius bundles.
All Sylius bundles share the same architecture and this guide will introduce you these conventions.
You’ll learn how to perform basic CRUD operations on the data and how to override models, controllers, repositories, validation mapping and forms.
Sylius is using the Doctrine Common persistence interfaces. This means that every model within Sylius bundles has its own repository and object manager.
Some interfaces extend the Timestampable and SoftDeletable interfaces. Those interfaces are defined in the SyliusResourceBundle to not create a dependency on Doctrine ORM. They are however compatible with the GedmoDoctrineExtensions when using Doctrine ORM.
Retrieving any resource from database always happens via the repository, which implements Sylius\Bundle\ResourceBundle\Model\RepositoryInterface
.
If you have been using Doctrine, you should already be familiar with this concept, as it extends the default Doctrine ObjectRepository
interface.
Let’s assume you want to load a product from database. Your product repository is always accessible via the sylius.repository.product
service.
<?php
public function myAction()
{
$repository = $this->container->get('sylius.repository.product');
}
Retrieving many resources is as simple as calling the proper methods on the repository.
<?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(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.
}
Every Sylius repository supports paginating products. To create a Pagerfanta instance 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 returns 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(array('foo' => true), array('createdAt' => 'desc'));
$products->setMaxPerPage(3);
$products->setCurrentPage($request->query->get('page', 1));
}
To create a new resource instance, you can simply call the createNew()
method on the repository.
Now let’s try something else than product, we’ll create a new TaxRate model.
<?php
public function myAction()
{
$repository = $this->container->get('sylius.repository.tax_rate');
$taxRate = $repository->createNew();
}
Примечание
Creating resources via this factory method makes the code more testable, and allows you to change the model class easily.
To save or remove a resource, you can use any ObjectManager
which is capable of managing the class.
Every model has its own manager alias, for example the sylius.manager.address
is an alias to the ORM EntityManager.
Of course, it is also perfectly fine if you use the doctrine.orm.entity_manager
service name or any other appropriate manager service.
<?php
public function myAction()
{
$repository = $this->container->get('sylius.repository.address');
$manager = $this->container->get('sylius.manager.address'); // Alias to the appropriate doctrine manager service.
$address = $repository->createNew();
$address
->setFirstname('John')
->setLastname('Doe')
;
$manager->persist($address);
$manager->flush(); // Save changes in database.
}
To remove a resource, you also use the manager.
<?php
public function myAction()
{
$repository = $this->container->get('sylius.repository.shipping_method');
$manager = $this->container->get('sylius.manager.shipping_method');
$shippingMethod = $repository->findOneBy(array('name' => 'DHL Express'));
$manager->remove($shippingMethod);
$manager->flush(); // Save changes in database.
}
...
All Sylius models live in Sylius\Component\Xyz\Model
namespace together with the interfaces.
As an example, for Sylius Taxation Component it’s TaxCategory and TaxRate.
Let’s assume you want to add “zone” field to the Sylius tax rates.
Firstly, you need to create your own TaxRate
class, which will extend the base model.
namespace Acme\Bundle\ShopBundle\Entity;
use Sylius\Component\Addressing\Model\ZoneInterface;
use Sylius\Component\Taxation\Model\TaxRate as BaseTaxRate;
class TaxRate extends BaseTaxRate
{
private $zone;
public function getZone()
{
return $this->zone;
}
public function setZone(ZoneInterface $zone)
{
$this->zone = $zone;
return $this;
}
}
Secondly, define the entity mapping inside Resources/config/doctrine/TaxRate.orm.xml
of your bundle.
<?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="Acme\ShopBundle\Entity\TaxRate" table="sylius_tax_rate">
<many-to-one field="zone" target-entity="Sylius\Component\Addressing\Model\ZoneInterface">
<join-column name="zone_id" referenced-column-name="id" nullable="false" />
</many-to-one>
</entity>
</doctrine-mapping>
Finally, you configure your class in app/config/config.yml
file.
sylius_taxation:
driver: doctrine/orm
classes:
tax_rate:
model: Acme\ShopBundle\Entity\TaxRate # Your tax rate entity.
Done! Sylius will now use your TaxRate model!
What has happened?
sylius.model.tax_rate.class
contains Acme\\Bundle\\ShopBundle\\Entity\\TaxRate
.sylius.repository.tax_rate
represents Doctrine repository for your new class.sylius.manager.tax_rate
represents Doctrine object manager for your new class.sylius.controller.tax_rate
represents the controller for your new class.Sylius\\Component\\Taxation\\Model\\TaxRateInterface
are using your new class as target-entity, you do not need to update any mappings.TaxRateType
form type is using your model as data_class
.Sylius\\Component\\Taxation\\Model\\TaxRate
is automatically turned into Doctrine Mapped Superclass.All Sylius bundles are using SyliusResourceBundle as a foundation for database storage.
If you want to modify the controller or add your custom actions, you can do so by defining a new controller class. By extending resource controller, you also get access to several handy methods. Let’s add to our custom controller a new method to recommend a product:
<?php
// src/Acme/ShopBundle/Controller/ProductController.php
namespace Acme\ShopBundle\Controller;
use Sylius\Bundle\ResourceBundle\Controller\ResourceController;
use Symfony\Component\HttpFoundation\Request;
class ProductController extends ResourceController
{
public function recommendAction(Request $request, $id)
{
$product = $this->findOr404(array('id' => $id)); // Find product with given id or return 404!
$product->incrementRecommendations(); // Add +1!
$this->persistAndFlush($product); // Save product.
return $this->redirect($this->generateUrl('acme_shop_homepage'));
}
}
You also need to configure your controller class in app/config/config.yml
.
# app/config/config.yml
sylius_product:
driver: doctrine/orm
classes:
product:
controller: Acme\ShopBundle\Controller\ProductController
That’s it! Now sylius.controller.product:recommendAction
is available. You can use it by defining a new route.
# app/config/routing.yml
acme_shop_product_recommend:
path: /products/{id}/recommend
defaults:
_controller: sylius.controller.product:recommendAction
What has happened?
sylius.controller.product.class
contains Acme\\Bundle\\ShopBundle\\Controller\\ProductController
.sylius.controller.product
is using your new controller class.Overriding a Sylius model repository involves extending the base class and configuring it inside the bundle configuration.
Sylius is using both custom and default Doctrine repositories and often you’ll need to add your own methods. Let’s assume you want to find all orders for a given customer.
Firstly, you need to create your own repository class
<?php
// src/Acme/ShopBundle/Repository/OrderRepository.php
namespace Acme\ShopBundle\Repository;
use Sylius\Bundle\ResourceBundle\Doctrine\ORM\EntityRepository;
class OrderRepository extends EntityRepository
{
public function findByCustomer(Customer $customer)
{
return $this
->createQueryBuilder('o')
->join('o.billingAddress', 'billingAddress')
->join('o.shippingAddress', 'shippingAddress')
->join('o.customer', 'customer')
->where('o.customer = :customer')
->setParameter('customer', $customer)
->getQuery()
->getResult()
;
}
}
Secondly, need to configure your repository class in app/config/config.yml
.
# app/config/config.yml
sylius_order:
driver: doctrine/orm
classes:
order:
repository: Acme\ShopBundle\Repository\OrderRepository
That’s it! Now sylius.repository.order
is using your new class.
<?php
public function ordersAction()
{
$customer = // Obtain customer instance.
$repository = $this->container->get('sylius.repository.order');
$orders = $repository->findByCustomer($customer);
}
What has happened?
sylius.repository.order.class
contains Acme\\ShopBundle\\Repository\\OrderRepository
.sylius.repository.order
is using your new class.All Sylius validation mappings and forms are using sylius
as the default group.
You can configure your own validation for Sylius models. If the defaults do not fit your needs, create validation.xml
inside your bundle.
<?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\TaxationBundle\Model\TaxCategory">
<property name="name">
<constraint name="NotBlank">
<option name="message">Fill me in!</option>
<option name="groups">acme</option>
</constraint>
<constraint name="Length">
<option name="min">5</option>
<option name="max">255</option>
<option name="minMessage">Looonger!</option>
<option name="maxMessage">Shooorter!</option>
<option name="groups">acme</option>
</constraint>
</property>
</class>
</constraint-mapping>
You also need to configure the new validation group in app/config/config.yml
.
sylius_taxation:
driver: doctrine/orm # Configure the doctrine orm driver used in documentation.
validation_groups:
tax_category: [acme]
Done! Now all Sylius forms will use acme
validation group on all forms of tax category.
Every form type in Sylius holds its class name in a specific parameter. This allows you to easily add or remove fields by extending the base form class.
All Sylius form types live in Sylius\Bundle\XyzBundle\Form\Type
namespace.
Let’s assume you want to add “phone” and remove “company” fields to the Sylius address form.
You have to create your own AddressType
class, which will extend the base form type.
namespace Acme\Bundle\ShopBundle\Form\Type;
use Sylius\Bundle\AddressingBundle\Form\Type\AddressType as BaseAddressType;
use Symfony\Component\Form\FormBuilderInterface;
class AddressType extends BaseAddressType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
parent::buildForm($builder, $options); // Add default fields.
$builder->remove('company');
$builder->add('phone', 'text', array('required' => false));
}
}
Now, define the new form class in the app/config/config.yml
.
# app/config/config.yml
sylius_addressing:
driver: doctrine/orm
classes:
address:
form: Acme\ShopBundle\Form\Type\AddressType
Done! Sylius will now use your custom address form everywhere!
What has happened?
sylius.form.type.address.class
contains Acme\\Bundle\\ShopBundle\\Form\\Type\\AddressType
.sylius.form.type.address
form type service uses your custom class.sylius_address
form type uses your new form everywhere.All Sylius bundles use the Doctrine RTEL functionality, which allows to map the relations using interfaces, not implementations.
When defining relation to Sylius model, use the interface name instead of concrete class. It will be automatically replaced with the configured model, enabling much greater flexibility.
<?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="Acme\ShopBundle\Entity\Product" table="sylius_product">
<many-to-one field="taxCategory" target-entity="Sylius\Bundle\TaxationBundle\Model\TaxCategoryInterface">
<join-column name="tax_category_id" referenced-column-name="id" nullable="true" />
</many-to-one>
</entity>
</doctrine-mapping>
Thanks to this approach, if the TaxCategory model has changed, you do not need to worry about remapping other entities.
All Sylius bundles are using SyliusResourceBundle, which has some built-in events.
Event | Description |
---|---|
sylius.<resource>.pre_create | Before persist |
sylius.<resource>.create | After persist |
sylius.<resource>.post_create | After flush |
sylius.<resource>.pre_update | Before persist |
sylius.<resource>.update | After persist |
sylius.<resource>.post_update | After flush |
sylius.<resource>.pre_delete | Before remove |
sylius.<resource>.delete | After remove |
sylius.<resource>.post_delete | After flush |
Let’s take the Product model as an example. As you should already know, the product controller is represented by the sylius.controller.product
service.
Several useful events are dispatched during execution of every default action of this controller.
When creating a new resource via the createAction
method, 3 events occur.
First, before the persist()
is called on the product, the sylius.product.pre_create
event is dispatched.
Secondly, just before the database flush is performed, Sylius dispatches the sylius.product.create
event.
Finally, after the data storage is updated, sylius.product.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 Product object by the getSubject
method.
We’ll stay with the Product model and create a listener which updates Solr (search engine) document every time a product is updated.
namespace Acme\ShopBundle\EventListener;
use Symfony\Component\EventDispatcher\GenericEvent;
class SolrListener
{
// ... Constructor with indexer injection code.
public function onProductUpdate(GenericEvent $event)
{
$this->indexer->updateProductDocument($event->getSubject());
}
}
Now you need to register the listener in services configuration.
<?xml version="1.0" encoding="UTF-8"?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="acme.listener.solr" class="Acme\ShopBundle\EventListener\SolrListener">
<tag name="kernel.event_listener" event="sylius.product.post_update" method="onProductUpdate" />
</service>
</services>
</container>
Done! Every time a product is edited and the database updated, this listener will also use the indexer to update Solr index accordingly.
Easy CRUD and persistence for Symfony2 apps.
During our work on Sylius, we noticed a lot of duplicated code across all controllers. We started looking for a good solution to the problem. We’re not big fans of administration generators (they’re cool, but not for our use case!) - we wanted something simpler and more flexible.
Another idea was to not limit ourselves to one persistence backend. Initial implementation included custom manager classes, which was quite of an overhead, so we decided to simply stick with Doctrine Common Persistence interfaces. If you are using Doctrine ORM or any of the ODM’s, you’re already familiar with those concepts. Resource bundle relies mainly on the ObjectManager and ObjectRepository interfaces.
The last annoying problem this bundle tries to solve, is having separate “backend” and “frontend” controllers, or any other duplication for displaying the same resource, with different presentation (view). We also wanted an easy way to filter some resources from list, sort them or display them by id, slug or any other criteria - without having to define another super simple action for that purpose.
If these are issues you’re struggling with, this bundle may be helpful!
Please note that this bundle is not an admin generator. It won’t create forms, filters and grids for you. It only provides format agnostic controllers as foundation to build on, with some basic sorting and filter mechanisms.
We assume you’re familiar with Composer, 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.
$ composer require "sylius/resource-bundle"
Otherwise you have to download .phar file.
$ curl -sS https://getcomposer.org/installer | php
$ php composer.phar require "sylius/resource-bundle"
You just need to enable proper bundles inside the kernel.
<?php
// app/AppKernel.php
public function registerBundles()
{
$bundles = array(
new FOS\RestBundle\FOSRestBundle(),
new JMS\SerializerBundle\JMSSerializerBundle($this),
new Sylius\Bundle\ResourceBundle\SyliusResourceBundle(),
new WhiteOctober\PagerfantaBundle\WhiteOctoberPagerfantaBundle(),
);
}
To benefit from bundle services, you have to first register your bundle class as a resource.
You also need to enable HTTP method parameter override, by calling the following method on the Request object.
<?php
// web/app{_dev|_test}.php
use Symfony\Component\HttpFoundation\Request;
Request::enableHttpMethodParameterOverride();
Now you need to configure your resources! It means that you will tell to this bundle what model, controller, repository, etc. is used for each configured resource. It exists two ways for doing that, we will call them Basic configuration and Advanced configuration. The first one is pretty easy because your just need to write configuration (in your config.yml, for example). The second one allows you to embed configuration into your bundles but you will need to write some lines of code. We will explain the both ways in the next chapters.
In your app/config.yml
(or in an imported configuration file), you need to define what resources you want to use :
sylius_resource:
resources:
my_app.entity_key:
driver: doctrine/orm
manager: default
templates: App:User
classes:
model: MyApp\Entity\EntityName
interface: MyApp\Entity\EntityKeyInterface
controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController
repository: Sylius\Bundle\ResourceBundle\Doctrine\ORM\EntityRepository
my_app.other_entity_key:
driver: doctrine/odm
manager: other_manager
templates: App:User
classes:
model: MyApp\Entity\OtherEntityKey
interface: MyApp\Document\OtherEntityKeyInterface
controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController
repository: Sylius\Bundle\ResourceBundle\Doctrine\ODM\DocumentRepository
At this step:
$ php app/console container:debug | grep entity_key
my_app.repository.entity_key container MyApp\Entity\EntityName
my_app.controller.entity_key container Sylius\Bundle\ResourceBundle\Controller\ResourceController
my_app.repository.entity_key container Sylius\Bundle\ResourceBundle\Doctrine\ORM\EntityRepository
//...
$ php app/console container:debug --parameters | grep entity_key
sylius.config.classes {"my_app.entity_key": {"driver":"...", "manager": "...", "classes":{"model":"...", "controller":"...", "repository":"...", "interface":"..."}}}
//...
First you must list the supported doctrine driver by your bundle, the available drivers are:
SyliusResourceBundle::DRIVER_DOCTRINE_ORM
SyliusResourceBundle::DRIVER_DOCTRINE_MONGODB_ODM
SyliusResourceBundle::DRIVER_DOCTRINE_PHPCR_ODM
class MyBundle extends AbstractResourceBundle
{
public static function getSupportedDrivers()
{
return array(
SyliusResourceBundle::DRIVER_DOCTRINE_ORM
);
}
}
Примечание
Since the 0.11
your bundle class must implement ResourceBundleInterface
.
You need to expose a semantic configuration for your bundle. The following example show you a basic Configuration
that the resource bundle needs to work.
class Configuration implements ConfigurationInterface
{
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('bundle_name');
$rootNode
->children()
// Driver used by the resource bundle
->scalarNode('driver')->isRequired()->cannotBeEmpty()->end()
// Object manager used by the resource bundle, if not specified "default" will used
->scalarNode('manager')->defaultValue('default')->end()
// Validation groups used by the form component
->arrayNode('validation_groups')
->addDefaultsIfNotSet()
->children()
->arrayNode('MyEntity')
->prototype('scalar')->end()
->defaultValue(array('your_group'))
->end()
->end()
->end()
// Configure the template namespace used by each resource
->arrayNode('templates')
->addDefaultsIfNotSet()
->children()
->scalarNode('my_entity')->defaultValue('MyCoreBundle:Entity')->end()
->scalarNode('my_other_entity')->defaultValue('MyOtherCoreBundle:Entity')->end()
->end()
->end()
// The resources
->arrayNode('classes')
->addDefaultsIfNotSet()
->children()
->arrayNode('my_entity')
->addDefaultsIfNotSet()
->children()
->scalarNode('model')->defaultValue('MyApp\MyCustomBundle\Model\MyEntity')->end()
->scalarNode('controller')->defaultValue('Sylius\Bundle\ResourceBundle\Controller\ResourceController')->end()
->scalarNode('repository')->end()
->scalarNode('form')->defaultValue('MyApp\MyCustomBundle\Form\Type\MyformType')->end()
->end()
->end()
->arrayNode('my_other_entity')
->addDefaultsIfNotSet()
->children()
->scalarNode('model')->defaultValue('MyApp\MyCustomBundle\Model\MyOtherEntity')->end()
->scalarNode('controller')->defaultValue('Sylius\Bundle\ResourceBundle\Controller\ResourceController')->end()
->scalarNode('form')->defaultValue('MyApp\MyCustomBundle\Form\Type\MyformType')->end()
->end()
->end()
->end()
->end()
->end()
;
return $treeBuilder;
}
}
The resource bundle provide you AbstractResourceExtension
, your bundle extension have to extends it.
use Sylius\Bundle\ResourceBundle\DependencyInjection\AbstractResourceExtension;
class MyBundleExtension extends AbstractResourceExtension
{
// You can choose your application name, it will use to prefix the configuration keys in the container (the default value is sylius).
protected $applicationName = 'my_app';
// You can define where yours service definitions are
protected $configDirectory = '/../Resources/config';
// You can define what service definitions you want to load
protected $configFiles = array(
'services',
'forms',
);
public function load(array $config, ContainerBuilder $container)
{
$this->configure(
$config,
new Configuration(),
$container,
self::CONFIGURE_LOADER | self::CONFIGURE_DATABASE | self::CONFIGURE_PARAMETERS | self::CONFIGURE_VALIDATORS
);
}
}
The last parameter of the AbstractResourceExtension::configure()
allows you to define what functionalities you want to use :
- CONFIGURE_LOADER : load yours service definitions located in
$applicationName
- CONFIGURE_PARAMETERS : set to the container the configured resource classes using the pattern
my_app.serviceType.resourceName.class
For example :sylius.controller.product.class
. For a form, it is a bit different : ‘sylius.form.type.product.class’- CONFIGURE_VALIDATORS : set to the container the configured validation groups using the pattern
my_app.validation_group.modelName
For examplesylius.validation_group.product
- CONFIGURE_DATABASE : Load the database driver, available drivers are
doctrine/orm
,doctrine/mongodb-odm
anddoctrine/phpcr-odm
At this step:
$ php app/console container:debug | grep my_entity
my_app.controller.my_entity container Sylius\Bundle\ResourceBundle\Controller\ResourceController
my_app.form.type.my_entity container MyApp\MyCustomBundle\Form\Type\TaxonomyType
my_app.manager.my_entity n/a alias for doctrine.orm.default_entity_manager
my_app.repository.my_entity container MyApp\MyCustomer\ModelRepository
//...
$ php app/console container:debug --parameters | grep my_entity
my_app.config.classes {...}
my_app.controller.my_entity.class MyApp\MyCustomBundle\ModelController
my_app.form.type.my_entity.class MyApp\MyCustomBundle\FormType
my_app.model.my_entity.class MyApp\MyCustomBundle\Model
my_app.repository.my_entity.class MyApp\MyCustomBundle\ModelRepository
my_app.validation_group.my_entity ["my_app"]
my_app_my_entity.driver doctrine/orm
my_app_my_entity.driver.doctrine/orm true
//...
You can overwrite the configuration of your bundle like that :
bundle_name:
driver: doctrine/orm
manager: my_custom_manager
validation_groups:
product: [myCustomGroup]
classes:
my_entity:
model: MyApp\MyOtherCustomBundle\Model
controller: MyApp\MyOtherCustomBundle\Entity\ModelController
repository: MyApp\MyOtherCustomBundle\Repository\ModelRepository
form: MyApp\MyOtherCustomBundle\Form\Type\FormType
Примечание
Caution: Your form is not declared as a service for now.
For now, with the advanced configuration you can not use several drivers but they can be overwritten. Example, you want to use
doctrine/odm
for my_other_entity
(see previous chapter), you just need to add this extra configuration to the app/config.yml
.
sylius_resource:
resources:
my_app.other_entity_key:
driver: doctrine/odm
manager: my_custom_manager
classes:
model: %my_app.model.my_entity.class%
And your manager will be overwrite:
$ php app/console container:debug | grep my_app.manager.other_entity_key
my_app.manager.other_entity_key n/a alias for doctrine.odm.my_custom_manager_document_manager
And... we’re done!
Примечание
ResourceController class is built using FOSRestBundle, thus it’s format agnostic, and can serve resources in many formats, like html, json or xml.
Your newly created controller service has a few basic crud actions and is configurable via routing, which allows you to do some really tedious tasks - easily.
The most basic action is showAction. It is used to display a single resource. To use it, the only thing you need to do is register a proper route.
# routing.yml
app_user_show:
path: /users/{id}
methods: [GET]
defaults:
_controller: app.controller.user:showAction
Done! Now when you go to /users/3
, ResourceController will use the repository (app.repository.user
) to find user with given id.
If the requested user resource does not exist, it will throw a 404 exception.
When a user is found, the default template will be rendered - App:User:show.html.twig
(like you configured it in config.yml) with the User result as the user
variable.
That’s the most basic usage of the simple showAction
action.
Okay, but what if you want now to display same User resource, but with a different representation?
# routing.yml
app_backend_user_show:
path: /backend/users/{id}
methods: [GET]
defaults:
_controller: app.controller.user:showAction
_sylius:
template: App:Backend/User:show.html.twig
Nothing more to do here, when you go to /backend/users/3
, the controller will try to find the user and render it using the custom template you specified under the route configuration.
Simple, isn’t it?
Displaying the user by id can be boring... and let’s say we do not want to allow viewing disabled users? There is a solution for that!
# routing.yml
app_user_show:
path: /users/{username}
methods: [GET]
defaults:
_controller: app.controller.user:showAction
_sylius:
criteria:
username: $username
enabled: true
With this configuration, the controller will look for a user with the given username and exclude disabled users.
Internally, it simply uses the $repository->findOneBy(array $criteria)
method to look for the resource.
By default, resource repository uses findOneBy(array $criteria), but in some cases it’s not enough - for example - you want to do proper joins or use a custom query. Creating yet another action to change the method called could be a solution but there is a better way. The configuration below will use a custom repository method to get the resource.
# routing.yml
app_user_show:
path: /users/{username}
methods: [GET]
defaults:
_controller: app.controller.user:showAction
_sylius:
method: findOneWithFriends
arguments: [$username]
Internally, it simply uses the $repository->findOneWithFriends($username)
method, where username
is taken from the current request.
To get a paginated list of users, we will use indexAction of our controller! In the default scenario, it will return an instance of paginator, with a list of Users.
# routing.yml
app_user_index:
path: /users
methods: [GET]
defaults:
_controller: app.controller.user:indexAction
When you go to /users
, ResourceController will use the repository (app.repository.user
) to create a paginator.
The default template will be rendered - App:User:index.html.twig
with the paginator as the users
variable.
A paginator can be a simple array if you disable the pagination otherwise it is a instance of Pagerfanta\Pagerfanta
which is the library used to manage the pagination.
Just like for the showAction, you can override the default template and criteria.
# routing.yml
app_user_index_inactive:
path: /users/inactive
methods: [GET]
defaults:
_controller: app.controller.user:indexAction
_sylius:
criteria:
enabled: false
template: App:User:inactive.html.twig
This action will render a custom template with a paginator only for disabled users.
Except filtering, you can also sort users.
# routing.yml
app_user_index_top:
path: /users/top
methods: [GET]
defaults:
_controller: app.controller.user:indexAction
_sylius:
sorting:
score: desc
template: App:User:top.html.twig
Under that route, you can paginate over the users by their score.
You can define your own repository method too, you can use the same way explained in <show_resource>.
Примечание
If you want to paginate your resources you need to use EntityReposiory::getPaginator($queryBuilder)
.
It will transform your doctrine query builder into Pagerfanta\Pagerfanta
object.
You can also control the “max per page” for paginator, using paginate
parameter.
# routing.yml
app_user_index_top:
path: /users/top
methods: [GET]
defaults:
_controller: app.controller.user:indexAction
_sylius:
paginate: 5
sorting:
score: desc
template: App:User:top.html.twig
This will paginate users by 5 per page, where 10 is the default.
Pagination is handy, but you do not always want to do it, you can disable pagination and simply request a collection of resources.
# routing.yml
app_user_index_top3:
path: /users/top
methods: [GET]
defaults:
_controller: app.controller.user:indexAction
_sylius:
paginate: false
limit: 3
sorting:
score: desc
template: App:User:top3.html.twig
That action will return the top 3 users by score, as the users
variable.
You need to define two routes, they will use to update the position of the resource.
# routing.yml
my_route_move_up:
pattern: /{id}/move-up
methods: [PUT]
defaults:
_controller: sylius.controller.resource:moveUpAction
_sylius:
redirect: referer
sortable_position: priority # the default value is position
my_route_move_down:
pattern: /{id}/move-down
methods: [PUT]
defaults:
_controller: sylius.controller.resource:moveDownAction
_sylius:
redirect: referer
sortable_position: priority # the default value is position
You need to update your doctrine mapping :
<!-- resource.orm.xml -->
<field name="priority" type="integer">
<gedmo:sortable-position/>
</field>
In your template, you can use the macro move to print the move up and move down buttons:
{# index.html.twig #}
{% import 'SyliusResourceBundle:Macros:buttons.html.twig' as buttons %}
{{ buttons.move(path('my_route_move_up', {'id': resource.id}), 'up', loop.first and not resources.hasPreviousPage, loop.last and not resources.hasNextPage) }}
{{ buttons.move(path('my_route_move_down', {'id': resource.id}), 'down', loop.first and not resources.hasPreviousPage, loop.last and not resources.hasNextPage) }}
This TWIG extension renders the title of your columns (in your table), it created the link used to sort your resources. You will need to enable it per route
# routing.yml
app_user_index:
path: /users
methods: [GET]
defaults:
_controller: app.controller.user:indexAction
sortable: true
or globally
# config.yml
sylius_resource:
settings:
sortable: true
Parameter | Mandatory | Type | Description |
---|---|---|---|
property | YES | string | Name of the property (attribute defined in your classes) |
label | NO | string | Default order, it can be asc or desc (default : asc) |
order | NO | string | Unique id of the address |
options | NO | array | Additional options : template (string) : Path to the template route (string) : Key of the new route route_params (array) : Additional route parameters |
This extension renders the following template : SyliusResourceBundle:Twig:sorting.html.twig. You will need to enable it per route
# routing.yml
app_user_index:
path: /users
methods: [GET]
defaults:
_controller: app.controller.user:indexAction
paginate: $paginate
or globally
# config.yml
sylius_resource:
settings:
paginate: $paginate
<table>
<tr>
<td>
{{ sylius_resource_sort('productId', 'product.id'|trans) }}
</td>
<td>
{{ sylius_resource_sort('productName', 'product.name'|trans, 'desc', {'route': 'my_custom_route'}) }}
</td>
</tr>
<table>
This TWIG extension renders a HTML select which allows the user to choose how many items he wants to display in the page.
Parameter | Mandatory | Type | Description |
---|---|---|---|
paginator | YES | string | An instance of PagerFanta |
limits | YES | string | An array of paginate value |
options | NO | array | Additional options : template (string) : Path to the template route (string) : Key of the new route route_params (array) : Additional route parameters |
This extension renders the following template : SyliusResourceBundle:Twig:paginate.html.twig
{{ sylius_resource_paginate(paginator, [10, 30, 50]) }}
<table>
<!-- ... -->
</table>
{{ sylius_resource_paginate(paginator, [10, 30, 50]) }}
For now, you need to create your own macro, it could look like :
{% macro pagination(paginator, options) %}
{% if paginator.haveToPaginate()|default(false) %}
{{ pagerfanta(paginator, 'twitter_bootstrap3_translated', options|default({})) }}
{% endif %}
{% endmacro %}
To display a form, handle submit or create a new resource via API, you should use createAction of your app.controller.user service.
# routing.yml
app_user_create:
path: /users/new
methods: [GET, POST]
defaults:
_controller: app.controller.user:createAction
Done! Now when you go to /users/new
, ResourceController will use the repository (app.repository.user
) to create new user instance.
Then it will try to create an app_user
form, and set the newly created user as data.
Примечание
Currently, this bundle does not generate a form for you, the right form type has to be created and registered in the container manually.
As a response, it will render the App:User:create.html.twig
template with form view as the form
variable.
As said previously, you need to declare your form as a service. Its name must be contructed with the following pattern application_resource
(Example: sylius_product
).
(see the Configuration chapter to see how configure your resources and your application name)
<parameters>
<!-- If you use the basic configuration this paramter is not available in the container. You need to add it manually. -->
<parameter key="app.form.type.user.class">App\Bundle\Form\UserType</parameter>
</parameters>
<services>
<service id="app.form.type.user" class="%app.form.type.user.class%">
<tag name="form.type" alias="app_user" />
</service>
</services>
You can use exactly the same route to handle the submit of the form and create the user.
<form method="post" action="{{ path('app_user_create') }}">
On submit, the create action with method POST, will bind the request on the form, and if it is valid it will use the right manager to persist the resource.
Then, by default it redirects to app_user_show
to display the created user, but you can easily change that behavior - you’ll see this in later sections.
When validation fails, it will render the form just like previously with the errors to display.
Just like for the show and index actions, you can customize the template per route.
# routing.yml
app_user_create:
path: /users/new
methods: [GET, POST]
defaults:
_controller: app.controller.user:createAction
_sylius:
template: App:Backend/User:create.html.twig
You can also use custom form type on per route basis. By default it generates the form type name following the simple convention bundle prefix + _ + resource logical name
.
Below you can see the usage for specifying a custom form.
# routing.yml
app_user_create:
path: /users/new
methods: [GET, POST]
defaults:
_controller: app.controller.user:createAction
_sylius:
template: App:Backend/User:create.html.twig
form: app_user_custom
or use use directly a class.
# routing.yml
app_user_create:
path: /users/new
methods: [GET, POST]
defaults:
_controller: app.controller.user:createAction
_sylius:
template: App:Backend/User:create.html.twig
form: App\Byndle\Form\UserType
By default, ResourceController
will use the createNew
method with no arguments to create a new instance of your object. However, this behavior can be modified.
To use different method of your repository, you can simply configure the factory
option.
# routing.yml
app_user_create:
path: /users/new
methods: [GET, POST]
defaults:
_controller: app.controller.user:createAction
_sylius:
factory: createNewWithGroups
Additionally, if you want to provide your custom method with arguments from the request, you can do so by adding more parameters.
# routing.yml
app_user_create:
path: /users/{groupId}/new
methods: [GET, POST]
defaults:
_controller: app.controller.user:createAction
_sylius:
factory:
method: createNewWithGroups
arguments: [$groupId]
With this configuration, $repository->createNewWithGroups($request->get('groupId'))
will be called to create new resource within createAction
.
By default the controller will try to get the id of the newly created resource and redirect to the “show” route. You can easily change that. For example, to redirect user to list after successfully creating a new resource - you can use the following configuration.
# routing.yml
app_user_create:
path: /users/new
methods: [GET, POST]
defaults:
_controller: app.controller.user:createAction
_sylius:
redirect: app_user_index
You can also perform more complex redirects, with parameters. For example...
# routing.yml
app_user_create:
path: /competition/{competitionId}/users/new
methods: [GET, POST]
defaults:
_controller: app.controller.user:createAction
_sylius:
redirect:
route: app_competition_show
parameters: { id: $competitionId }
In addition to the request parameters, you can access some of the newly created objects properties, using the resource.
prefix.
# routing.yml
app_user_create:
path: /users/new
methods: [GET, POST]
defaults:
_controller: app.controller.user:createAction
_sylius:
redirect:
route: app_user_show
parameters: { email: resource.email }
With this configuration, the email
parameter for route app_user_show
will be obtained from your newly created user.
To display an edit form of a particular resource, change it or update it via API, you should use the updateAction action of your app.controller.user service.
# routing.yml
app_user_update:
path: /users/{id}/edit
methods: [GET, PUT]
defaults:
_controller: app.controller.user:updateAction
Done! Now when you go to /users/5/edit
, ResourceController will use the repository (app.repository.user
) to find the user with ID === 5.
If found it will create the app_user
form, and set the existing user as data.
Примечание
Currently, this bundle does not generate a form for you, the right form type has to be updated and registered in the container manually.
As a response, it will render the App:User:update.html.twig
template with form view as the form
variable and the existing User as the user
variable.
You can use exactly the same route to handle the submit of the form and update the user.
<form method="post" action="{{ path('app_user_update', {'id': user.id}) }}">
<input type="hidden" name="_method" value="PUT" />
On submit, the update action with method PUT, will bind the request on the form, and if it is valid it will use the right manager to persist the resource.
Then, by default it redirects to app_user_show
to display the updated user, but like for creation of the resource - it’s customizable.
When validation fails, it will simply render the form again.
Just like for other actions, you can customize the template.
# routing.yml
app_user_update:
path: /users/{id}/edit
methods: [GET, PUT]
defaults:
_controller: app.controller.user:updateAction
_sylius:
template: App:Backend/User:update.html.twig
Same way like for createAction you can override the default form.
# routing.yml
app_user_update:
path: /users/{id}/edit
methods: [GET, PUT]
defaults:
_controller: app.controller.user:updateAction
_sylius:
template: App:Backend/User:update.html.twig
form: app_user_custom
By default, the updateAction will look for the resource by id. You can easily change that criteria.
# routing.yml
app_user_update:
path: /users/{username}/edit
methods: [GET, PUT]
defaults:
_controller: app.controller.user:updateAction
_sylius:
criteria: { username: $username }
By default the controller will try to get the id of resource and redirect to the “show” route. To change that, use the following configuration.
# routing.yml
app_user_update:
path: /users/{id}/edit
methods: [GET, PUT]
defaults:
_controller: app.controller.user:updateAction
_sylius:
redirect: app_user_index
You can also perform more complex redirects, with parameters. For example...
# routing.yml
app_user_update:
path: /competition/{competitionId}/users/{id}/edit
methods: [GET, PUT]
defaults:
_controller: app.controller.user:updateAction
_sylius:
redirect:
route: app_competition_show
parameters: { id: $competitionId }
Deleting a resource is simple.
# routing.yml
app_user_delete:
path: /users/{id}
methods: [DELETE]
defaults:
_controller: app.controller.user:deleteAction
Currently browsers do not support the “DELETE” http method. Fortunately, Symfony has a very useful feature. You can make a POST call with override parameter, which will force the framework to treat the request as specified method.
<form method="post" action="{{ path('app_user_delete', {'id': user.id}) }}">
<input type="hidden" name="_method" value="DELETE" />
<button type="submit">
Delete
</button>
</form>
On submit, the delete action with the method DELETE, will remove and flush the resource.
Then, by default it redirects to app_user_index
to display the users index, but like for other actions - it’s customizable.
By default, the deleteAction will look for the resource by id. However, you can easily change that. For example, you want to delete the user who belongs to particular company, not only by his id.
# routing.yml
app_user_delete:
path: /companies/{companyId}/users/{id}
methods: [DELETE]
defaults:
_controller: app.controller.user:deleteAction
_sylius:
criteria:
id: $id
company: $companyId
There are no magic hacks behind that, it simply takes parameters from request and builds the criteria array for the findOneBy
repository method.
By default the controller will try to get the id of the resource and redirect to the “index” route. To change that, use the following configuration.
# routing.yml
app_user_delete:
path: /competition/{competitionId}/users/{id}
methods: [DELETE]
defaults:
_controller: app.controller.user:deleteAction
_sylius:
redirect:
route: app_competition_show
parameters: { id: $competitionId }
By default the resource bundle generate default messages for each action. They use the following translation key pattern
sylius.resource.actionName (actionName
can be create
, update
and delete
).
You can manage flash messages by resource, the following pattern appName.resource.actionName is used to translate them.
create
, update
or delete
)Example:
sylius_resource:
resources:
my_app.entity_key:
driver: doctrine/orm
templates: App:User
classes:
model: MyApp\Entity\EntityName
interface: MyApp\Entity\EntityKeyInterface
controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController
repository: Sylius\Bundle\ResourceBundle\Doctrine\ORM\EntityRepository
For the current this resource, you can use my_app.entity_key.create
, my_app.entity_key.update
and my_app.entity_key.delete
in your translation files.
Примечание
Caution: The domain used for translating flash messages is flashes. You need to create your own flashes.locale.yml (Exemple: flashes.en.yml).
This bundle allow you easy usage of two extra doctrine tools: XmlMappingDriver and ResolveDoctrineTargetEntitiesPass. The first one allow you to put your models (entities, document, etc) and their mappings in specific directories. The second one define relationships between different entities without making them hard dependencies. We will explain how you can enable them in the next chapters.
Примечание
Caution : these tools are facultatives!
XmlMappingDriver
¶class MyBundle extends AbstractResourceBundle
{
// You need to specify a prefix for your bundle
protected function getBundlePrefix()
{
return 'app_bundle_name';
}
// You need specify the namespace where are stored your models
protected function getModelNamespace()
{
return 'MyApp\MyBundle\Model';
}
// You can specify the path where are stored the doctrine mapping, by default this method will returns
// model. This path is relative to the Resources/config/doctrine/.
protected function getDoctrineMappingDirectory()
{
return 'model';
}
}
ResolveDoctrineTargetEntitiesPass
¶class MyBundle extends AbstractResourceBundle
{
// You need to specify a prefix for your bundle
protected function getBundlePrefix()
{
return 'app_bundle_name';
}
// You need to specify the mapping between your interfaces and your models. Like the following example you can
// get the classname of your model in the container (See the following chapter for more informations).
protected function getModelInterfaces()
{
return array(
'MyApp\MyBundle\ModelInterface' => 'sylius.model.resource.class',
);
}
}
This bundle use the loadClassMetadata
doctrine event which occurs after the mapping metadata for a class has been loaded from
a mapping source (annotations/xml/yaml). Every models can be declared as mapped superclass, this listener will transform
them in an entity or document if they have not child.
With this following mapping, Doctrine will create the table my_table
with the column name
.
<!-- Resource/config/Model.orm.xml-->
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:gedmo="http://gediminasm.org/schemas/orm/doctrine-extensions-mapping">
<mapped-superclass name="My/Bundle/Model" table="my_table">
<id name="id" column="id" type="integer">
<generator strategy="AUTO" />
</id>
<field name="name" column="name" type="string" />
</mapped-superclass>
</doctrine-mapping>
If you want to add an extra field, you can create a new model which extends My/Bundle/Model
and its doctrine mapping
like that :
<!-- Resource/config/NewModel.orm.xml-->
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:gedmo="http://gediminasm.org/schemas/orm/doctrine-extensions-mapping">
<entity name="My/OtherBundle/NewModel" table="my_new_table">
<field name="description" column="name" type="string" />
</entity>
</doctrine-mapping>
Примечание
This functionality works for Doctrine ORM and ODM.
For each route defined the parameter parser allow you to find extra data in the current request, in the resource previously created by using the PropertyAccess component or by using the Expression Language component
You need use the following syntax $var_name. It will try to find in the request the key named var_name $request->get(‘var_name’).
# routing.yml
app_user_index:
path: /products
methods: [GET]
defaults:
_controller: app.controller.user:indexAction
_sylius:
criteria: {name: $name}
You need use the following syntax resource.attribute_name. It will try to find the value of the attribute name named attribute_name $accessor->getValue($resource, ‘attribute_name’)).
# routing.yml
app_user_index:
path: /products
methods: [POST]
defaults:
_controller: app.controller.user:indexAction
_sylius:
redirect:
route: my_route
parameters: {name: resource.name}
You need use the following syntax expr:resource.my_expression.
# routing.yml
app_user_index:
path: /customer/orders
methods: [POST]
defaults:
_controller: app.controller.user:indexAction
_sylius:
method: findOrderByCustomer
arguments: ['expr:service("security.context").getToken().getUser()']
Примечание
service
: returns a given service (see the example above);parameter
: returns a specific parameter value, aka: expr:parameter("sylius.locale")
The methods ResourceController:createAction
, ResourceController:updateAction
and ResourceController:deleteAction
throw events before and after executing any actions on the current resource. The name of the events used the following pattern
app_name.resource.(pre|post)_(create|update|delete)
.
First, you need to register event listeners, the following example show you how you can do that.
# services.xml
<service id="my_listener" class="MyBundle\MyEventListener">
<tag name="kernel.event_listener" event="sylius.order.pre_create" method="onOrderPreCreate"/>
</service>
# services.yml
services:
my_listener:
class: MyBundle\MyEventListener
tags:
- { name: kernel.event_listener, event: sylius.order.pre_create, method: onOrderPreCreate }
After that, you need to create your listener
class MyEventListener
{
public function onOrderPreCreate(ResourceEvent $event)
{
// You can get your resource like that
$resource = $event->getSubject();
// You can stop propagation too.
$event->stop('my.message', array('%amount%' => $resource->getAmount()));
}
}
Примечание
Caution: you can use subscribers too, you can get more informations there.
Примечание
To be written.
The container element needs to have data-form-type=”collection” as html attribute. It is used to enable the form collection plugin. All data attributes beginning by data-form-collection are used by the plugin and should not be removed.
<div data-form-type="collection" data-prototype='...'>
<div data-form-collection="list" class="row collection-list">
<input type="hidden" data-form-prototype="prototypeName" value="..." />
<div data-form-collection="item"
data-form-collection-index="XX"
class="col-md-{{ boxWidth }} collection-item">
<!-- Put here your sub form -->
<a href="#" data-form-collection="delete">
Delete
</a>
</div>
</div>
<a href="#" data-form-collection="add">
Add
</a>
</div>
When a HTML element which has data-form-prototype=”update” as HTML attribute fired change event, the plugin will find the new prototype from the server if the data-form-url=”Url” is specified. The value of the input/select and the position of the collection item will be submitted to the server.
<div data-form-collection="item" data-form-collection-index="1">
<select data-form-url="example.com/update">
<option value="rule">Rule</option>
<option value="action">Action</option>
</select>
</div>
In this example, the value of the select and the position will be sent to the server.
Another way exists, hidden inputs can be used too if you don’t want to generate the prototype by the server. You need to insert as many hidden inputs as select options in the page. They need to have attribute like data-form-prototype=”prototypeName”. “prototypeName” needs to match to one of all the select options.
<div data-form-collection="item" data-form-collection-index="1">
<input type="hidden" data-form-prototype="rule" value="..." />
<input type="hidden" data-form-prototype="action" value="..." />
<select>
<option value="rule">Rule</option>
<option value="action">Action</option>
</select>
</div>
In this example, when you select Rule, the plugin will replace the current form prototype by the value of the hidden input which has data-form-prototype=”rule”.
sylius_resource:
settings:
# Enable pagination
paginate: true
# If the pagination is disabled, you can specify a limit
limit: false
# If the pagination is enabled, you can specify the allowed page size
allowed_paginate: [10, 20, 30]
# Default page size
default_page_size: 10
# Enable sorting
sortable: false
# Default sorting parameters
sorting: []
# Enable filtering
filterable: false
# Default filtering parameters
criteria: []
resources:
app.user:
driver: doctrine/orm # Also supported - doctrine/mongodb-odm.
templates: AppBundle:User
classes:
model: App\Entity\User
controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController
repository: Sylius\Bundle\ResourceBundle\Doctrine\ORM\EntityRepository
route_name:
defaults:
_sylius:
# Name of the form, by default it is built with the prefix of the bundle and the name of the resource
form: bundlePrefix_resource # string
# Name of the route where the user will be redirected
redirect: my_route # string
# If your route has extra parameters you can use the following syntax:
route: my_route # string
parameters: [] # array
# Number of item in the list (should be set to false if you want to disable it)
limit: 10 # integer or boolean
# Number of item by page (should be set to false if you want to disable it)
paginate 10 # integer or boolean
# Enabling the filter
filterable: true # boolean
# Parameter used for filtering resources when filterable is enabled, using a $ to find the parameter in the request
criteria: $paginate # string or array
# Enabling the sorting
sortable: false # boolean
# Parameter used for sorting resources when sortable is enabled, using a $ to find the parameter in the request
sorting: $sorting # string or array
# The method of the repository used to retrieve resources
method: findActiveProduct # string
# Arguments gave to the 'method'
arguments: [] # array
factory:
# The method of the repository used to create the new resource
method: createNew # string
# Arguments gave to the 'method'
arguments: [] # array
# Key used by the translator for managing flash messages, by default it is built with the prefix of the bundle, the name of the resource and the name of the action (create, update, delete and move)
flash: sylius.product.create # string
# Name of the property used to manage the position of the resource
sortable_position: position # string
# API request, version used by the serializer
serialization_version: null
# API request, groups used by the serializer
serialization_groups: []
This bundle uses GitHub issues. If you have found bug, please create an issue.
The Sylius product catalog is made available as set of 2 standalone bundles. This component contains the most basic product model with properties (attributes) support. It also includes the product prototyping system, which serves as a template for creating similar products.
If you need product options support, please see SyliusVariationBundle which allows to manage product variations with a very flexible architecture.
We assume you’re familiar with Composer, 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.
$ 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"
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($this),
new Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle(),
new WhiteOctober\PagerfantaBundle\WhiteOctoberPagerfantaBundle(),
new Sylius\Bundle\ProductBundle\SyliusProductBundle(),
new Sylius\Bundle\ResourceBundle\SyliusResourceBundle(),
// Other bundles...
new Doctrine\Bundle\DoctrineBundle\DoctrineBundle(),
);
}
Примечание
Please register the bundle before DoctrineBundle. This is important as we use listeners which have to be processed first.
Put this configuration inside your app/config/config.yml
.
sylius_product:
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:
sluggable: true
timestampable: true
Add the following to your app/config/routing.yml
.
sylius_product:
resource: @SyliusProductBundle/Resources/config/routing.yml
Run the following command.
$ php app/console doctrine:schema:update --force
Предупреждение
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.
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 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));
}
To create new product instance, you can simply call createNew()
method on the repository.
<?php
public function myAction(Request $request)
{
$repository = $this->container->get('sylius.repository.product');
$product = $repository->createNew();
}
Примечание
Creating a product via this factory method makes the code more testable, and allows you to change the product class easily.
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)
{
$repository = $this->container->get('sylius.repository.product');
$manager = $this->container->get('sylius.manager.product'); // Alias for appropriate doctrine manager service.
$product = $repository->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.
}
A product can also have a set of defined Properties (think Attributes), you’ll learn about them in next chapter of this documentation.
Managing properties happens exactly the same way like products, you have sylius.repository.property
and sylius.manager.property
at your disposal.
Value of specific Property for one of Products, happens through ProductProperty model, which holds the references to Product, Property pair and the value. If you want to programatically set a property value on product, use the following code.
<?php
public function myAction(Request $request)
{
$propertyRepository = $this->container->get('sylius.repository.property');
$productPropertyRepository = $this->container->get('sylius.repository.product_property');
$property = $propertyRepository->findOneBy(array('name' => 'T-Shirt Collection'));
$productProperty = $productPropertyRepository->createNew();
$productProperty
->setProperty($property)
->setValue('Summer 2013')
;
$product->addProperty($productProperty);
$manager = $this->container->get('sylius.manager.product');
$manager->persist($product);
$manager->flush(); // Save changes in database.
}
This looks a bit tedious, doesn’t it? There is a ProductBuilder service which simplifies the creation of products dramatically, you can learn about it in appropriate chapter.
The bundle ships with a set of useful form types for all models. You can use the defaults or override them
with your own forms.
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 |
availableOn | datetime |
metaDescription | text |
metaKeywords | text |
You can render each of these using the usual Symfony way {{ form_row(form.description) }}
.
Default form for the Property model has name sylius_property
and contains several basic fields.
Field | Type |
---|---|
name | text |
presentation | text |
type | choice |
The default form for the Prototype model has name sylius_prototype
and is built from the following fields.
Field | Type |
---|---|
name | text |
properties | sylius_property_choice |
There are a few more form types, which can become useful when integrating the bundle into your app.
sylius_product_property
is a form which is used to set the product properties (and their values). It has 2 fields, the property choice field and a value input.
sylius_property_choice
is a ready-to-use select field, with a list of all Properties from database.
sylius_product_to_identifier
can be used to render a text field, which will transform the value into a product.
If you need to customize existing fields or add your own, please read the overriding forms chapter
.
...
Used to build product based on given prototype.
Here is an example:
<?php
$prototype = $this->findOr404(array('id' => $prototypeId));
$product = $this->get('sylius.repository.product')->createNew();
$this
->get('sylius.builder.prototype')
->build($prototype, $product)
;
It will add appropriate options and variants to given product based on prototype.
This service provides a fluent interface for easy product creation.
The example shown below is self explanatory:
<?php
$product = $this->get('sylius.builder.product')
->create('Github mug')
->setDescription("Coffee. Let's face it — humans need to drink liquids!")
->addAttribute('collection', 2013)
->addAttribute('color', 'Red')
->addAttribute('material', 'Stone')
->save()
;
sylius_product:
driver: ~ # The driver used for persistence layer.
engine: twig # Templating engine to use by default.
classes:
product:
model: Sylius\Component\Product\Model\Product
controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController
repository: ~
form: Sylius\Bundle\AssortmentBundle\Form\Type\ProductType
product_prototype:
model: Sylius\Component\Product\Model\Prototype
controller: Sylius\Bundle\ProductBundle\Controller\PrototypeController
repository: ~
form: Sylius\Bundle\AssortmentBundle\Form\Type\PrototypeType
validation_groups:
product: [sylius] # Product validation groups.
product_prototype: [sylius] # Product prototype validation groups.
$ composer install --dev --prefer-dist
$ bin/phpspec run -fpretty --verbose
This bundle uses GitHub issues. If you have found bug, please create an issue.
A generic solution for a cart system inside a Symfony2 application.
It doesn’t matter if you are starting a new project or you need to implement this feature for an existing system - this bundle should be helpful. Currently only the Doctrine ORM driver is implemented, so we’ll use it here as an example.
There are two main models inside the bundle, Cart and CartItem.
There are also 2 main services, Provider and ItemResolver. You’ll get familiar with them in further parts of the documentation.
We assume you’re familiar with Composer, 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.
$ composer require "sylius/cart-bundle"
Otherwise you have to download .phar file.
$ curl -sS https://getcomposer.org/installer | php
$ php composer.phar require "sylius/cart-bundle"
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 the following bundles and their dependencies to the kernel:
- SyliusResourceBundle
- SyliusMoneyBundle
- SyliusOrderBundle
Don’t worry, everything was automatically installed via Composer.
Примечание
Please register the bundle before DoctrineBundle. This is important as we use listeners which have to be processed first. It is generally a good idea to place all of the Sylius bundles at the beginning of the bundles list, as it is done in the Sylius-Standard project.
<?php
// app/AppKernel.php
public function registerBundles()
{
$bundles = array(
new Sylius\Bundle\ResourceBundle\SyliusResourceBundle(),
new Sylius\Bundle\MoneyBundle\SyliusMoneyBundle(),
new Sylius\Bundle\OrderBundle\SyliusOrderBundle(),
new Sylius\Bundle\CartBundle\SyliusCartBundle(),
// Other bundles...
new Doctrine\Bundle\DoctrineBundle\DoctrineBundle(),
new FOS\RestBundle\FOSRestBundle(),
new JMS\SerializerBundle\JMSSerializerBundle($this),
);
}
This is no longer a required step in the latest version of the
SyliusCartBundle, and if you are happy with the default implementation (which
is Sylius\Bundle\CartBundle\Model\CartItem
), you can just skip to the next
section.
You can create your CartItem entity, living inside your application code.
We think that keeping the application-specific and simple bundle structure is a good practice, so
let’s assume you have your AppBundle
registered under App\AppBundle
namespace.
<?php
// src/App/AppBundle/Entity/CartItem.php
namespace App\AppBundle\Entity;
use Sylius\Bundle\CartBundle\Model\CartItem as BaseCartItem;
class CartItem extends BaseCartItem
{
}
Now we need to define a simple mapping for this entity to map its fields.
You should create a mapping file in your AppBundle
, put it inside the doctrine mapping directory src/App/AppBundle/Resources/config/doctrine/CartItem.orm.xml
.
<?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\AppBundle\Entity\CartItem" table="app_cart_item">
</entity>
</doctrine-mapping>
You do not have to map the ID field because it is already mapped in the
Sylius\Bundle\CartBundle\Model\CartItem
class, together with the relation
between Cart and CartItem.
Let’s assume you have a Product entity, which represents your main merchandise within your webshop.
Примечание
Please remember that you can use anything else, Product here is just an obvious example, but it will work in a similar way with other entities.
We need to modify the CartItem entity and its mapping a bit, so it allows us to put a product inside the cart item.
<?php
// src/App/AppBundle/Entity/CartItem.php
namespace App\AppBundle\Entity;
use Sylius\Bundle\CartBundle\Model\CartItem as BaseCartItem;
class CartItem extends BaseCartItem
{
private $product;
public function getProduct()
{
return $this->product;
}
public function setProduct(Product $product)
{
$this->product = $product;
}
}
We added a “product” property, and a simple getter and setter. We have to also map the Product to CartItem, let’s create this relation in mapping files.
<?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\AppBundle\Entity\CartItem" table="app_cart_item">
<many-to-one field="product" target-entity="App\AppBundle\Entity\Product">
<join-column name="product_id" referenced-column-name="id" />
</many-to-one>
</entity>
</doctrine-mapping>
Similarly, you can create a custom entity for orders. The class that you need
to extend is Sylius\Bundle\CartBundle\Model\Cart
. Carts and Orders in
Sylius are in fact the same thing. Do not forget to create the mapping file.
But, again, do not put a mapping for the ID field — it is already mapped in
the parent class.
And that would be all about entities. Now we need to create a really simple service.
The ItemResolver will be used by the controller to resolve the new cart item - based on a user request information.
Its only requirement is to implement Sylius\Bundle\CartBundle\Resolver\ItemResolverInterface
.
<?php
// src/App/AppBundle/Cart/ItemResolver.php
namespace App\AppBundle\Cart;
use Sylius\Bundle\CartBundle\Model\CartItemInterface;
use Sylius\Bundle\CartBundle\Resolver\ItemResolverInterface;
use Symfony\Component\HttpFoundation\Request;
class ItemResolver implements ItemResolverInterface
{
public function resolve(CartItemInterface $item, Request $request)
{
}
}
The class is in place, well done.
We need to do some more coding, so the service is actually doing its job. In our example we want to put Product in our cart, so we should inject the entity manager into our resolver service.
<?php
// src/App/AppBundle/Cart/ItemResolver.php
namespace App\AppBundle\Cart;
use Sylius\Bundle\CartBundle\Model\CartItemInterface;
use Sylius\Bundle\CartBundle\Resolver\ItemResolverInterface;
use Symfony\Component\HttpFoundation\Request;
use Doctrine\ORM\EntityManager;
class ItemResolver implements ItemResolverInterface
{
private $entityManager;
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
public function resolve(CartItemInterface $item, Request $request)
{
}
private function getProductRepository()
{
return $this->entityManager->getRepository('AppBundle:Product');
}
}
We also added a simple method getProductRepository()
to keep the resolving code cleaner.
We must use this repository to find a product with id, given by the user via the request. This can be done in various ways, but to keep the example simple - we’ll use a query parameter.
<?php
// src/App/AppBundle/Cart/ItemResolver.php
namespace App\AppBundle\Cart;
use Sylius\Bundle\CartBundle\Model\CartItemInterface;
use Sylius\Bundle\CartBundle\Resolver\ItemResolverInterface;
use Sylius\Bundle\CartBundle\Resolver\ItemResolvingException;
use Symfony\Component\HttpFoundation\Request;
use Doctrine\ORM\EntityManager;
class ItemResolver implements ItemResolverInterface
{
private $entityManager;
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
public function resolve(CartItemInterface $item, Request $request)
{
$productId = $request->query->get('productId');
// If no product id given, or product not found, we throw exception with nice message.
if (!$productId || !$product = $this->getProductRepository()->find($productId)) {
throw new ItemResolvingException('Requested product was not found');
}
// Assign the product to the item and define the unit price.
$item->setVariant($product);
$item->setUnitPrice($product->getPrice());
// Everything went fine, return the item.
return $item;
}
private function getProductRepository()
{
return $this->entityManager->getRepository('AppBundle:Product');
}
}
Примечание
Please remember that item accepts only integers as price and quantity.
Register our brand new service in the container. We’ll use XML as an example, but you are free to pick any other format.
<?xml version="1.0" encoding="UTF-8"?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="app.cart_item_resolver" class="App\AppBundle\Cart\ItemResolver">
<argument type="service" id="doctrine.orm.entity_manager" />
</service>
</services>
</container>
The bundle requires also a simple configuration...
Put this minimal configuration inside your app/config/config.yml
.
sylius_cart:
resolver: app.cart_item_resolver # The id of our newly created service.
classes: ~ # This key can be empty but it must be present in the configuration.
sylius_order:
driver: doctrine/orm # Configure the doctrine orm driver used in documentation.
sylius_money:
driver: doctrine/orm # Configure the doctrine orm driver used in documentation.
Or, if you have created any custom entities, use this:
sylius_cart:
resolver: app.cart_item_resolver # The id of our newly created service.
classes: ~ # This key can be empty but it must be present in the configuration.
sylius_order:
driver: doctrine/orm # Configure the doctrine orm driver used in documentation.
classes:
order:
model: App\AppBundle\Entity\Cart # If you have created a custom Cart entity.
order_item:
model: App\AppBundle\Entity\CartItem # If you have created a custom CartItem entity.
sylius_money:
driver: doctrine/orm # Configure the doctrine orm driver used in documentation.
Import the default routing from your app/config/routing.yml
.
sylius_cart:
resource: @SyliusCartBundle/Resources/config/routing.yml
prefix: /cart
Remember to update your database schema.
For “doctrine/orm” driver run the following command.
$ php app/console doctrine:schema:update --force
Предупреждение
This should be done only in dev environment! We recommend using Doctrine migrations, to safely update your schema.
We think that providing a sensible default template is really difficult, especially when a cart summary is not the simplest page. This is the reason why we do not currently include any, but if you have an idea for a good starter template, let us know!
The bundle requires only the summary.html.twig
template for cart summary page.
The easiest way to override the view is by placing it here app/Resources/SyliusCartBundle/views/Cart/summary.html.twig
.
Примечание
You can use the templates from our Sylius app as inspiration.
Here is a quick reference of what the default models can do for you.
You can access the cart total value using the ->getTotal()
method. The denormalized number of cart items is available via ->getTotalItems()
method.
Recalculation of totals can happen by calling ->calculateTotal()
method, using the simplest possible math. It will also update the item totals.
The carts have their expiration time - ->getExpiresAt()
returns that time and ->incrementExpiresAt()
sets it to +3 hours from now by default.
The collection of items (Implementing the Doctrine\Common\Collections\Collection
interface) can be obtained using the ->getItems()
.
Just like for the cart, the total is available via the same method (->getTotal()
), but the unit price is accessible using the ->getUnitPrice()
Each item also can calculate its total, using the quantity (->getQuantity()
) and the unit price.
It also has a very important method called ->equals(CartItemInterface $item)
, which decides whether the items are “same” or not.
If they are, it should return true, false otherwise. This is taken into account when adding an item to the cart.
If the added item is equal to an existing one, their quantities are summed, but no new item is added to the cart.
By default, it compares the ids, but for our example we would prefer to check the products. We can easily modify our CartItem entity to do that correctly.
<?php
// src/App/AppBundle/Entity/CartItem.php
namespace App/AppBundle/Entity;
use Sylius\Bundle\CartBundle\Model\CartItem as BaseCartItem;
use Sylius\Bundle\CartBundle\Model\CartItemInterface;
class CartItem extends BaseCartItem
{
private $product;
public function getProduct()
{
return $this->product;
}
public function setProduct(Product $product)
{
$this->product = $product;
}
public function equals(CartItemInterface $item)
{
return $this->product === $item->getProduct();
}
}
If the user tries to add the same product twice or more, it will just sum the quantities, instead of adding duplicates to the cart.
This bundle provides a quite simple default routing with several handy and common actions. You can see the usage guide below.
To point user to the cart summary page, you can use the sylius_cart_summary
route.
It will render the page with the cart and form variables by default.
The cart is the current cart and form is the view of the cart form.
In our simple example, we only need to add the following link in the places where we need the “add to cart button”.
<a href="{{ path('sylius_cart_item_add', {'productId': product.id}) }}">Add product to cart</a>
Clicking this link will add the selected product to the cart.
On the cart summary page you have access to all the cart items, so another simple link will allow a user to remove items from the cart.
<a href="{{ path('sylius_cart_item_remove', {'id': item.id}) }}">Remove from cart</a>
Where item variable represents one of the cart.items collection items.
Clearing the cart is simple as clicking the following link.
<a href="{{ path('sylius_cart_clear')}}">Clear cart</a>
On the cart summary page, you have access to the cart form, if you want to save it, simply submit the form with the following action.
<form action="{{ path('sylius_cart_save') }}" method="post">Save cart</a>
You cart will be validated and saved if everything is alright.
When using the bundle, you have access to several handy services. You can use them to manipulate and manage the cart.
Примечание
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
// ...
public function saveAction(Request $request)
{
// ObjectManager which is capable of managing the Cart resource.
// For *doctrine/orm* driver it will be EntityManager.
$this->get('sylius.manager.cart');
// ObjectRepository for the Cart resource, it extends the base EntityRepository.
// You can use it like usual entity repository in project.
$this->get('sylius.repository.cart');
// Same pair for CartItem resource.
$this->get('sylius.manager.cart_item');
$this->get('sylius.repository.cart_item');
// Those repositories have some handy default methods, for example...
$item = $this->get('sylius.repository.cart')->createNew();
}
There are also 3 more services for you.
You use the provider to obtain the current user cart, if there is none, a new one is created and saved.
The ->setCart()
method also allows you to replace the current cart.
->abandonCart()
is resetting the current cart, a new one will be created on the next ->getCart()
call.
This is useful, for example, when after completing an order you want to start with a brand new and clean cart.
<?php
// ...
public function saveAction(Request $request)
{
$provider = $this->get('sylius.cart_provider'); // Implements the CartProviderInterface.
$currentCart = $provider->getCart();
$provider->setCart($customCart);
$provider->abandonCart();
}
The resolver is used to create a new item based on the user request.
<?php
// ...
public function addItemAction(Request $request)
{
$resolver = $this->get('sylius.cart_resolver');
$item = $resolver->resolve($this->createNew(), $request);
}
Примечание
A more advanced example of a resolver implementation is available in Sylius Sandbox application on GitHub.
When using Twig as your template engine, you have access to 2 handy functions.
The sylius_cart_get
function uses the provider to get the current cart.
{% set cart = sylius_cart_get() %}
Current cart totals: {{ cart.total }} for {{ cart.totalItems }} items!
The sylius_cart_form
returns the form view for the CartItem form. It allows you to easily build more complex actions for
adding items to cart. In this simple example we allow to provide the quantity of item. You’ll need to process this form in your resolver.
{% set form = sylius_cart_form({'product': product}) %} {# You can pass options as an argument. #}
<form action="{{ path('sylius_cart_item_add', {'productId': product.id}) }}" method="post">
{{ form_row(form.quantity)}}
<input type="submit" value="Add to cart">
</form>
Примечание
An example with multiple variants of this form can be found in Sylius Sandbox app. It allows for selecting a variation/options/quantity of the product. It also adapts to the product type.
sylius_cart:
# The driver used for persistence layer.
driver: ~
# Service id of cart item resolver.
resolver: ~
# Cart provider service id.
provider: sylius.cart_provider.default
# The id of cart storage for default provider.
storage: sylius.cart_storage.session
classes:
cart:
controller: Sylius\Bundle\CartBundle\Controller\CartController
form: Sylius\Bundle\CartBundle\Form\Type\CartType
item:
controller: Sylius\Bundle\CartBundle\Controller\CartItemController
form: Sylius\Bundle\CartBundle\Form\Type\CartItemType
validation_groups:
cart: [sylius]
item: [sylius]
This bundle uses GitHub issues. If you have found bug, please create an issue.
This bundle is a foundation for sales order handling for Symfony2 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.
We assume you’re familiar with Composer, 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.
$ 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:*
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 JMS\SerializerBundle\JMSSerializerBundle($this),
new Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle(),
new WhiteOctober\PagerfantaBundle\WhiteOctoberPagerfantaBundle(),
new Sylius\Bundle\ResourceBundle\SyliusResourceBundle(),
new Sylius\Bundle\MoneyBundle\SyliusMoneyBundle(),
new Sylius\Bundle\OrderBundle\SyliusOrderBundle(),
// Other bundles...
new Doctrine\Bundle\DoctrineBundle\DoctrineBundle(),
);
}
Примечание
Please register the bundle before DoctrineBundle. This is important as we use listeners which have to be processed first.
You have to create your own Order 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 AppBundle
registered under App\Bundle\AppBundle
namespace.
<?php
// src/App/AppBundle/Entity/Order.php
namespace App\AppBundle\Entity;
use Sylius\Component\Order\Model\Order as BaseOrder;
class Order extends BaseOrder
{
}
Now we need to define simple mapping for this entity, because it only extends the Doctrine mapped superclass.
You should create a mapping file in your AppBundle
, put it inside the doctrine mapping directory src/App/AppBundle/Resources/config/doctrine/Order.orm.xml
.
<?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\AppBundle\Entity\Order" table="app_order">
<id name="id" column="id" type="integer">
<generator strategy="AUTO" />
</id>
<one-to-many field="items" target-entity="Sylius\Component\Order\Model\OrderItemInterface" mapped-by="order" orphan-removal="true">
<cascade>
<cascade-all/>
</cascade>
</one-to-many>
<one-to-many field="adjustments" target-entity="Sylius\Component\Order\Model\AdjustmentInterface" mapped-by="order" orphan-removal="true">
<cascade>
<cascade-all/>
</cascade>
</one-to-many>
</entity>
</doctrine-mapping>
Примечание
You might wonder why are we putting interface inside mapping, you can read about this Doctrine feature here.
Now let’s assume you have a Product entity, which represents your main merchandise in your webshop.
Примечание
Please remember that you can use anything else, Product here is just an obvious example, but it will work in similar way with other entities.
All you need to do is making your Product entity to implement ProductInterface
and configure it inside Symfony settings.
<?php
// src/App/AppBundle/Entity/Product.php
namespace App\AppBundle\Entity;
use Sylius\Component\Product\Model\ProductInterface;
class Product implements ProductInterface
{
// Your code...
public function getName()
{
// Here you just have to return the nice display name of your merchandise.
return $this->name;
}
}
Now, you do not even have to map your Product model to the order items. It is all done automatically. And that would be all about entities.
Put this configuration inside your app/config/config.yml
.
sylius_order:
driver: doctrine/orm # Configure the doctrine orm driver used in documentation.
classes:
order:
model: App\AppBundle\Entity\Order # The order entity.
order_item:
model: App\AppBundle\Entity\OrderItem # Your order item entity, if you need to override it
sylius_product:
driver: doctrine/orm # Configure the doctrine orm driver used in documentation.
classes:
product:
model: App\AppBundle\Entity\Product
Remember to update your database schema.
For “doctrine/orm” driver run the following command.
$ php app/console doctrine:schema:update --force
Предупреждение
This should be done only in dev environment! We recommend using Doctrine migrations, to safely update your schema.
Here is a quick reference of what the default models can do for you.
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');
To check whether the order is confirmed or not, you can use the isConfirmed()
method, which returns a true/false value.
To change that status, you can use the confirmation setter, setConfirmed(false)
. All orders are confirmed by default, unless you enabled the e-mail confirmation feature.
Order also can contain a confirmation token, accessible by the appropriate getter and setter.
<?php
if ($order->isConfirmed()) {
echo 'This one is confirmed, great!';
}
Примечание
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(); // 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.
Recalculation of totals can happen by calling ->calculateTotal()
method, using the simplest possible math. It will also update the item totals.
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)
;
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);
Примечание
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.
<?php
$item = $itemRepository->createNew();
$item
->setVariant($book)
->setUnitPrice(2000)
->setQuantity(4)
->calculateTotal()
;
echo $item->getTotal(); // 8000.
An OrderItem can also hold adjustments.
Adjustments are based on simple but powerful idea inspired by Spree adjustments. They serve as foundation for any tax, shipping and discounts systems.
Примечание
To be written.
Примечание
To be written.
When using the bundle, you have access to several handy services. You can use them to retrieve and persist orders.
Примечание
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.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.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.
Примечание
To be written.
sylius_order:
# The driver used for persistence layer.
driver: ~
classes:
sellable:
# The class name of the entity you want to put inside orders.
model: ~
order:
model: Sylius\Component\Order\Model\Order
controller: Sylius\Bundle\OrderBundle\Controller\OrderController
repository: ~
form: Sylius\Bundle\OrderBundle\Form\Type\OrderType
order_item:
model: Sylius\Component\Order\Model\OrderItem
controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController
repository: ~
form: Sylius\Bundle\OrderBundle\Form\Type\OrderItemType
adjustment:
model: Sylius\Component\Order\Model\Adjustment
controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController
repository: ~
form: Sylius\Bundle\OrderBundle\Form\Type\AdjustmentType
validation_groups:
order: [sylius] # Order validation groups.
order_item: [sylius]
adjustment: [sylius]
This bundle uses GitHub issues. If you have found bug, please create an issue.
This bundle integrates the Addressing into Symfony2 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.
We assume you’re familiar with Composer, 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.
$ 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:*
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($this),
new Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle(),
new WhiteOctober\PagerfantaBundle\WhiteOctoberPagerfantaBundle(),
new Sylius\Bundle\AddressingBundle\SyliusAddressingBundle(),
new Sylius\Bundle\ResourceBundle\SyliusResourceBundle(),
// Other bundles...
new Doctrine\Bundle\DoctrineBundle\DoctrineBundle(),
);
}
Примечание
Please register the bundle before DoctrineBundle. This is important as we use listeners which have to be processed first.
Put this configuration inside your app/config/config.yml
.
sylius_addressing:
driver: doctrine/orm # Configure the doctrine orm driver used in documentation.
And configure doctrine extensions which are used in assortment bundle:
stof_doctrine_extensions:
orm:
default:
timestampable: true
Import the routing configuration by adding the following to your app/config/routing.yml
.
sylius_addressing:
resource: @SyliusAddressingBundle/Resources/config/routing.yml
Run the following command.
$ php app/console doctrine:schema:update --force
Предупреждение
This should be done only in dev environment! We recommend using Doctrine migrations, to safely update your schema.
This bundle provides some default bootstrap templates.
Примечание
You can check Sylius application to see how to integrate it in your application.
This bundle exposes the ZoneMatcher as sylius.zone_matcher
service.
<?php
$zoneMatcher = $this->get('sylius.zone_matcher');
$zone = $zoneMatcher->match($user->getBillingAddress);
The bundle ships with a set of useful form types for all models. You can use the defaults or override them with your own types.
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')
;
}
}
Flexible inventory management for Symfony2 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.
We assume you’re familiar with Composer, 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.
$ 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"
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(),
);
}
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 AppBundle
registered under App\Bundle\AppBundle
namespace.
We will create Book entity.
<?php
// src/App/AppBundle/Entity/Book.php
namespace App\AppBundle\Entity;
use Sylius\Bundle\InventoryBundle\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;
/**
* @ORM\Column(type="boolean")
*/
protected $availableOnDemand;
public function __construct()
{
$this->onHand = 1;
$this->availableOnDemand = true;
}
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 isAvailableOnDemand()
{
return $this->availableOnDemand;
}
public function setAvailableOnDemand($availableOnDemand)
{
$this->availableOnDemand = (Boolean) $availableOnDemand;
}
public function getOnHand()
{
return $this->onHand;
}
public function setOnHand($onHand)
{
$this->onHand = $onHand;
}
}
Примечание
This example shows the full power of StockableInterface. The bundle also provides an Stockable entity which implements StockableInterface for you. By extending the Stockable entity, the example above can be dramatically simplified.
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/App/AppBundle/Entity/InventoryUnit.php
namespace App\AppBundle\Entity;
use Sylius\Bundle\InventoryBundle\Entity\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 entity from Sylius bundle, which means inheriting some functionality inventory bundle 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.
Put this configuration inside your app/config/config.yml
.
sylius_inventory:
driver: doctrine/orm
backorders: true
classes:
unit:
model: App\AppBundle\Entity\InventoryUnit
stockable:
model: App\AppBundle\Entity\Book
Import the routing configuration by adding the following to your app/config/routing.yml`.
sylius_inventory:
resource: @SyliusInventoryBundle/Resources/config/routing.yml
Remember to update your database schema.
For “doctrine/orm” driver run the following command.
$ php app/console doctrine:schema:update --force
Предупреждение
This should be done only in dev environment! We recommend using Doctrine migrations, to safely update your schema.
The bundle provides some default bootstrap templates.
Примечание
You can check our Sandbox app to see how to integrate it in your application.
Here is a quick reference for the default models.
Each unit holds a reference to a stockable object and its state, which can be sold or backordered. It also provides some handy shortcut methods like isSold, isBackordered and getSku.
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, need to provide display name and to check if stockable is available on demand. It can get/set current stock level with getOnHand and setOnHand methods.
When using the bundle, you have access to several handy services.
The name speaks for itself, this service checks availability for given stockable object. It takes backorders setting into account, so if backorders are enabled, stockable will always be available. Backorders can be enabled per stockable if it is available on demand. If none of this is the case, it means that backorders are not enabled for the given stockable and AvailabilityChecker will rely 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.
Inventory operator is the heart of this bundle. It can be used to manage stock levels and inventory units. It can also fill backorders for the given stockable, this is a very powerful feature in combination with InventoryChangeListener. Creating/destroying inventory units with a given state is also the operators job.
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 %}
sylius_inventory:
# The driver used for persistence layer.
driver: ~
# Enable/disable backorders.
backorders: true
# Array of events for InventoryChangeListener
events: ~
# 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
classes:
inventory_unit:
model: Sylius\Component\Inventory\Model\InventoryUnit
controller: Sylius\Bundle\InventoryBundle\Controller\InventoryUnitController
repository: ~ # You can override the repository class here.
stockable:
model: ~ # The stockable model class.
controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController
If you want to see working implementation, try out the Sylius sandbox application.
This bundle uses GitHub issues. If you have found bug, please create an issue.
SyliusShippingBundle is the shipment management component for Symfony2 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.
We assume you’re familiar with Composer, 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.
$ 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"
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 FOS\RestBundle\FOSRestBundle(),
new JMS\SerializerBundle\JMSSerializerBundle($this),
new Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle(),
new WhiteOctober\PagerfantaBundle\WhiteOctoberPagerfantaBundle(),
new Sylius\Bundle\ShippingBundle\SyliusShippingBundle(),
new Sylius\Bundle\ResourceBundle\SyliusResourceBundle(),
// Other bundles...
new Doctrine\Bundle\DoctrineBundle\DoctrineBundle(),
);
}
Примечание
Please register the bundle before DoctrineBundle. This is important as we use listeners which have to be processed first.
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
Add the following to your app/config/routing.yml
.
sylius_shipping:
resource: @SyliusShipping/Resources/config/routing.yml
Run the following command.
$ php app/console doctrine:schema:update --force
Предупреждение
This should be done only in dev environment! We recommend using Doctrine migrations, to safely update your schema.
In order to handle your merchandise through the Sylius shipping engine, your models need to implement ShippableInterface.
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\Bundle\ShippingBundle\Model\ShippableInterface;
use Sylius\Bundle\ShippingBundle\Model\ShippingCategoryInterface;
class Book
{
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="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="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.
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 find available shipping methods or calculate shipping cost you need to use object implementing ShippingSubjectInterface
.
The default Shipment model is already implementing ShippingSubjectInterface
.
getShippingMethod
returns a ShippingMethodInterface
instance, representing the method.getShippingItemCount
provides you with the count of items to ship.getShippingItemTotal
returns the total value of shipment, if applicable. The default Shipment model returns 0.getShippingWeight
returns the total shipment weight.getShippables
returns a collection of unique ShippableInterface
instances.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 |
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?
calculate(ShippingSubjectInterface, array $configuration)
is called, where configuration is taken from ShippingMethod.configuration attribute.Default calculators can be sufficient solution for many use cases.
The flat_rate
calculator, charges concrete amount per shipment.
The per_item_rate
calculator, charges concrete amount per shipment item.
The flexible_rate
calculator, charges one price for the first item, and another price for every other item.
The weight_rate
calculator, charges one price for certain weight of shipment. So if the shipment weights 5 kg, and calculator is configured to charge $4 per kg, the final price is $20.
Depending on community contributions and Sylius resources, more default calculators can be implemented, for example weight_range_rate
.
Sylius ships with several default calculators, but you can easily register your own.
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/Acme/ShopBundle\Shipping/DHLCalculator.php
namespace Acme\ShopBundle\Shipping;
use Acme\ShopBundle\Shipping\DHLService;
use Sylius\Bundle\ShippingBundle\Calculator\Calculator;
use Sylius\Bundle\ShippingBundle\Model\ShippingSubjectInterface;
class DHLCalculator extends Calculator
{
private $dhlService;
public function __construct(DHLService $dhlService)
{
$this->dhlService = $dhlService;
}
public function calculate(ShippingSubjectInterface $subject, array $configuration)
{
return $this->dhlService->getShippingCostForWeight($subject->getShippingWeight());
}
}
Now, you need to register your new service in container and tag it with sylius.shipping_calculator
.
<?xml version="1.0" encoding="UTF-8"?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="acme.shipping_calculator.dhl" class="Acme\ShopBundle\Shipping\DHLCalculator">
<argument type="service" id="acme.dhl_service" />
<tag name="sylius.shipping_calculator" calculator="dhl" label="DHL" />
</service>
</services>
</container>
That would be all. This new option (“DHL”) will appear on the ShippingMethod creation form, in the “calculator” field.
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 define the configuration options, using the Symfony OptionsResolver component.
<?php
// src/Acme/ShopBundle\Shipping/DHLCalculator.php
namespace Acme\ShopBundle\Shipping;
use Acme\ShopBundle\Shipping\DHLService;
use Sylius\Bundle\ShippingBundle\Calculator\Calculator;
use Sylius\Bundle\ShippingBundle\Model\ShippingSubjectInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class DHLCalculator extends Calculator
{
private $dhlService;
public function __construct(DHLService $dhlService)
{
$this->dhlService = $dhlService;
}
public function calculate(ShippingSubjectInterface $subject, array $configuration)
{
return $this->dhlService->getShippingCostForWeight($subject->getShippingWeight());
}
/**
* {@inheritdoc}
*/
public function isConfigurable()
{
return true;
}
public function setConfiguration(OptionsResolverInterface $resolver)
{
$resolver
->setDefaults(array(
'limit' => 10
))
->setAllowedTypes(array(
'limit' => array('integer'),
))
;
}
}
Done, we’ve set the default item limit to 10. Now we have to create a form type which will be displayed if our calculator is selected.
<?php
// src/Acme/ShopBundle/Form/Type/Shipping/DHLConfigurationType.php
namespace Acme\ShopBundle\Form\Type\Shipping;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\Type;
class DHLConfigurationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('limit', 'integer', array(
'label' => 'Free shipping above total items',
'constraints' => array(
new NotBlank(),
new Type(array('type' => 'integer')),
)
))
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver
->setDefaults(array(
'data_class' => null
))
;
}
public function getName()
{
return 'acme_shipping_calculator_dhl';
}
}
We also need to register the form type and the calculator in the container.
<?xml version="1.0" encoding="UTF-8"?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="acme.shipping_calculator.dhl" class="Acme\ShopBundle\Shipping\DHLCalculator">
<argument type="service" id="acme.dhl_service" />
<tag name="sylius.shipping_calculator" calculator="dhl" label="DHL" />
</service>
<service id="acme.form.type.shipping_calculator.dhl" class="Acme\ShopBundle\Form\Type\Shipping\DHLConfigurationType">
<tag name="form.type" alias="acme_shipping_calculator_dhl" />
</service>
</services>
</container>
Finally, configure the calculator to use the form, by implementing simple getConfigurationFormType
method.
<?php
// src/Acme/ShopBundle\Shipping/DHLCalculator.php
namespace Acme\ShopBundle\Shipping;
use Acme\ShopBundle\Shipping\DHLService;
use Sylius\Bundle\ShippingBundle\Calculator\Calculator;
use Sylius\Bundle\ShippingBundle\Model\ShippingSubjectInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class DHLCalculator extends Calculator
{
private $dhlService;
public function __construct(DHLService $dhlService)
{
$this->dhlService = $dhlService;
}
public function calculate(ShippingSubjectInterface $subject, array $configuration)
{
return $this->dhlService->getShippingCostForWeight($subject->getShippingWeight());
}
/**
* {@inheritdoc}
*/
public function isConfigurable()
{
return true;
}
public function setConfiguration(OptionsResolverInterface $resolver)
{
$resolver
->setDefaults(array(
'limit' => 10
))
->setAllowedTypes(array(
'limit' => array('integer'),
))
;
}
public function getConfigurationFormType()
{
return 'acme_shipping_calculator_dhl';
}
}
Perfect, now we’re able to use the configuration inside the calculate
method.
<?php
// src/Acme/ShopBundle\Shipping/DHLCalculator.php
namespace Acme\ShopBundle\Shipping;
use Acme\ShopBundle\Shipping\DHLService;
use Sylius\Bundle\ShippingBundle\Calculator\Calculator;
use Sylius\Bundle\ShippingBundle\Model\ShippingSubjectInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class DHLCalculator extends Calculator
{
private $dhlService;
public function __construct(DHLService $dhlService)
{
$this->dhlService = $dhlService;
}
public function calculate(ShippingSubjectInterface $subject, array $configuration)
{
if ($subject->getShippingItemCount() > $configuration['limit']) {
return 0;
}
return $this->dhlService->getShippingCostForWeight($subject->getShippingWeight());
}
/**
* {@inheritdoc}
*/
public function isConfigurable()
{
return true;
}
public function setConfiguration(OptionsResolverInterface $resolver)
{
$resolver
->setDefaults(array(
'limit' => 10
))
->setAllowedTypes(array(
'limit' => array('integer'),
))
;
}
public function getConfigurationFormType()
{
return 'acme_shipping_calculator_dhl';
}
}
Your new configurable calculator is ready to use. When you select the “DHL” calculator in ShippingMethod form, configuration fields will appear automatically.
Sylius has a very flexible system for displaying only the right shipping methods to the user.
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.
With this requirement, the shipping method will support any shipment (or shipping subject) which contains at least one shippable with the same category.
All shippables have to reference the same category as the ShippingMethod.
None of the shippables can have the same shipping category.
The categories system is sufficient for basic use cases, for more advanced requirements, Sylius has the shipping rules system.
Every ShippingMethod can have a collection of ShippingRule instances. Each shipping rule, has a checker and configuration assigned. The ShippingRule.checker attribute holds the alias name of appropriate service implementing RuleCheckerInterface. Just like with cost calculators, Sylius ships with default checkers, but you can easily implement your own.
The ShippingMethodEligibilityChecker service can check if given subject, satisfies the category and shipping rules.
Sylius ships with several shipping rule checker types, so you can easily decide whether the shipping method is applicable to given shipment.
The item_count
checker, accepts the subject only if the items count fits into min and max range.
You can configure a method, which will be available only if the shipment contains more than 5 items, but less than 20.
The item_total
checker, is testing if the shipping subject total value is more than configured minimum, or eventually, less than maximum.
The weight
checker, allows to ship the shipment using the particular method, only if the shipment weight falls into the configure min and max range.
Depending on community contributions and Sylius resources, more default checkers can be implemented.
Implementing a custom rule checker is really simple, just like calculators, you can use simple services, or more complex - configurable checkers.
Примечание
To be written.
Примечание
To be written.
sylius_shipping:
# The driver used for persistence layer.
driver: ~
classes:
shipment:
model: Sylius\Component\Shipping\Model\Shipment
controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController
repository: ~
form: Sylius\Bundle\ShippingBundle\Form\Type\ShipmentType
shipment_item:
model: Sylius\Component\Shipping\Model\ShipmentItem
controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController
repository: ~
form: Sylius\Bundle\ShippingBundle\Form\Type\ShipmentItemType
shipping_method:
model: Sylius\Component\Shipping\Model\ShippingMethod
controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController
repository: ~
form: Sylius\Bundle\ShippingBundle\Form\Type\ShippingMethodType
shipping_method_rule:
model: Sylius\Component\Shipping\Model\ShippingMethodRule
controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController
repository: ~
form: Sylius\Bundle\ShippingBundle\Form\Type\ShippingMethodRuleType
shipping_method_rule:
model: Sylius\Component\Shipping\Model\Rule
controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController
repository: ~
form: Sylius\Bundle\ShippingBundle\Form\Type\RuleType
validation_groups:
shipping_category: [sylius]
shipping_method: [sylius]
shipping_rule_item_count_configuration: [sylius]
shipping_calculator_flat_rate_configuration: [sylius]
shipping_calculator_per_item_rate_configuration: [sylius]
shipping_calculator_flexible_rate_configuration: [sylius]
shipping_calculator_weight_rate_configuration: [sylius]
$ composer install --dev --prefer-dist
$ bin/phpspec run -fpretty --verbose
This bundle uses GitHub issues. If you have found bug, please create an issue.
Calculating and applying taxes is a common task for most of ecommerce applications. SyliusTaxationBundle is a reusable taxation component for Symfony2.
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.
We assume you’re familiar with Composer, 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.
$ 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"
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 FOS\RestBundle\FOSRestBundle(),
new JMS\SerializerBundle\JMSSerializerBundle($this),
new Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle(),
new WhiteOctober\PagerfantaBundle\WhiteOctoberPagerfantaBundle(),
new Sylius\Bundle\TaxationBundle\SyliusTaxationBundle(),
new Sylius\Bundle\ResourceBundle\SyliusResourceBundle(),
// Other bundles...
new Doctrine\Bundle\DoctrineBundle\DoctrineBundle(),
);
}
Примечание
Please register the bundle before DoctrineBundle. This is important as we use listeners which have to be processed first.
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
Add the following to your app/config/routing.yml
.
sylius_taxation:
resource: @SyliusTaxationBundle/Resources/config/routing.yml
Run the following command.
$ php app/console doctrine:schema:update --force
Предупреждение
This should be done only in dev environment! We recommend using Doctrine migrations, to safely update your schema.
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.
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 Every server has it’s price and other parameters, but a 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 Acme\Bundle\ShopBundle\Entity;
use Sylius\Bundle\TaxationBundle\Model\TaxCategoryInterface;
use Sylius\Bundle\TaxationBundle\Model\TaxableInterface;
class Server
{
private $taxCategory;
public function getTaxCategory()
{
return $this->taxCategory;
}
public function setTaxCategory(TaxCategoryInterface $taxCategory) // This method is not required.
{
$this->taxCategory = $taxCategory;
return $this;
}
}
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="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="Acme\ShopBundle\Entity\Server" table="acme_server">
<!-- your mappings... -->
<many-to-one field="taxCategory" target-entity="Sylius\Bundle\TaxationBundle\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.
If you want to add a tax category selection field to your model form, simply use the sylius_tax_category_choice
type.
namespace Acme\ShopBundle\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')
;
}
}
Tax rate model holds the configuration for particular tax category.
Attribute | Description |
---|---|
id | Unique id of the tax rate |
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 |
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.
First step is to create a new tax category.
<?php
public function configureAction()
{
$repository = $this->container->get('sylius.repository.tax_category');
$manager = $this->container->get('sylius.manager.tax_category');
$clothing = $repository
->createNew()
->setName('Clothing')
->setDescription('T-Shirts and this kind of stuff.')
;
$food = $repository
->createNew()
->setName('Food')
->setDescription('Yummy!')
;
$manager->persist($clothing);
$manager->persist($food);
$manager->flush();
}
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()
{
$tshirt = // ...
$banana = // ... Some logic behind loading entities.
$repository = $this->container->get('sylius.repository.tax_category');
$clothing = $repository->findOneBy(array('name' => 'Clothing'));
$food = $repository->findOneBy(array('name' => 'Food'));
$tshirt->setTaxCategory($clothing);
$food->setTaxCategory($food);
// Save the product entities.
}
Finally, you have to create appropriate tax rates for each of categories.
<?php
public function configureAction()
{
$taxCategoryRepository = $this->container->get('sylius.repository.tax_category');
$clothing = $taxCategoryRepository->findOneBy(array('name' => 'Clothing'));
$food = $taxCategoryRepository->findOneBy(array('name' => 'Food'));
$repository = $this->container->get('sylius.repository.tax_rate');
$manager = $this->container->get('sylius.repository.tax_rate');
$clothingTax = $repository
->createNew()
->setName('Clothing Tax')
->setAmount(0,08)
;
$foodTax = $repository
->createNew()
->setName('Food')
->setAmount(0,12)
;
$manager->persist($clothingTax);
$manager->persist($foodTax);
$manager->flush();
}
Done! See the “Calculating Taxes” chapter to see how to apply these rates.
Предупреждение
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.
<?php
namespace Acme\ShopBundle\Taxation
use Acme\ShopBundle\Entity\Order;
use Sylius\Bundle\TaxationBundle\Calculator\CalculatorInterface;
use Sylius\Bundle\TaxationBundle\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->getProduct();
$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.
}
}
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.
All calculators implement the TaxCalculatorInterface. First, you need to create a new class.
namespace Acme\Bundle\ShopBundle\TaxCalculator;
use Sylius\Bundle\TaxationBundle\Calculator\TaxCalculatorInterface;
use Sylius\Bundle\TaxationBundle\Model\TaxRateInterface;
class FeeCalculator implements TaxCalculatorInterface
{
public function calculate($amount, TaxRate $rate)
{
return $amount * ($rate->getAmount() + 0,15 * 0,30);
}
}
sylius_taxation:
# The driver used for persistence layer.
driver: ~
classes:
tax_category:
model: Sylius\Component\Taxation\Model\TaxCategory
controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController
repository: ~
form: Sylius\Bundle\TaxationBundle\Form\Type\TaxCategoryType
tax_rate:
model: Sylius\Component\Taxation\Model\TaxRate
controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController
repository: ~
form: Sylius\Bundle\TaxationBundle\Form\Type\TaxRateType
validation_groups:
tax_category: [sylius]
tax_rate: [sylius]
$ composer install --dev --prefer-dist
$ bin/phpspec run -fpretty --verbose
This bundle uses GitHub issues. If you have found bug, please create an issue.
Promotions system for Symfony2 applications.
With minimal configuration you can introduce promotions and coupons into your project. The following types of promotions are available and totally mixable:
This means you can for instance create the following promotions :
We assume you’re familiar with Composer, 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.
$ composer require "sylius/promotions-bundle"
Otherwise you have to download .phar file.
$ curl -sS https://getcomposer.org/installer | php
$ php composer.phar require "sylius/promotions-bundle"
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($this),
new Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle(),
new WhiteOctober\PagerfantaBundle\WhiteOctoberPagerfantaBundle(),
new Sylius\Bundle\PromotionsBundle\SyliusPromotionsBundle(),
new Sylius\Bundle\ResourceBundle\SyliusResourceBundle(),
// Other bundles...
new Doctrine\Bundle\DoctrineBundle\DoctrineBundle(),
);
}
Примечание
Please register the bundle before DoctrineBundle. This is important as we use listeners which have to be processed first.
Put this configuration inside your app/config/config.yml
.
sylius_promotions:
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
Add the following to your app/config/routing.yml
.
sylius_promotion:
resource: @SyliusPromotionsBundle/Resources/config/routing.yml
Run the following command.
$ php app/console doctrine:schema:update --force
Предупреждение
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.
All the models of this bundle are defined in Sylius\Bundle\PromotionsBundle\Model
.
A Rule
is used to check if your order is eligible to the promotion. A promotion can have none, one or several rules. SyliusPromotionsBundle
comes with 2 types of rules :
- item count rule : the number of items 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 item count rules, you have to configure the count
key, whereas the amount
key is used for item total rules. For both types of rules, you can configure the equal
key to false
or true
depending is you want the rule to be strict or not.
For instance, for an item count rule configured with equal
to false
and count
to 4, orders with only more than 4 items will be eligible.
An Action
defines the the nature of the discount. Common actions are :
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.
A Coupon
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.
A PromotionSubjectInterface
is the object you want to apply the promotion on. For instance, in Sylius Standard, a Sylius\Bundle\CoreBundle\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
.
The Promotion
is the main model of this bundle. A promotion has a name
, a description
and :
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.startsAt
and endsAt
Everything related to this subject is located in Sylius\Bundle\PromotionsBundle\Checker
.
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 SyliusPromotionsBundle
ships with 2 types of rules : item count rule and item total rule.
Item count rule is defined via the service sylius.promotion_rule_checker.item_count
which uses the class ItemCountRuleChecker
. The method isEligible
checks here if the promotion subject has the minimum number of items (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.
To be eligible to a promotion, a subject must :
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 PromotionEligibilityChecker
.
Everything related to this subject is located in Sylius\Bundle\PromotionsBundle\Action
.
Actions can be created by implementing PromotionActionInterface
. 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 SyliusPromotionsBundle
is totally independent, it does not provide some actions out of the box. Great examples of actions are provided by Sylius/Standard-Edition
.
Примечание
Sylius\Bundle\CoreBundle\Promotion\Action\FixedDiscountAction
from Sylius/Standard-Edition
is an example of action for a fixed amount discount. The related service is called sylius.promotion_action.fixed_discount
.
Примечание
Sylius\Bundle\CoreBundle\Promotion\Action\PercentageDiscountAction
from Sylius/Standard-Edition
is an example of action for a discount based on percentage. The related service is called sylius.promotion_action.percentage_discount
.
All actions that you have defined as services will be automatically registered thanks to Sylius\Bundle\PromotionsBundle\Action\Registry\PromotionActionRegistry
.
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.
By using the promotion eligibility checker
and the promotion applicator checker
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\Bundle\PromotionsBundle\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 require special needs that are covered by this documentation.
SyliusPromotionsBundle
provides a way of generating coupons for a promotion : the coupon generator. Provided as a service sylius.generator.promotion_coupon
via the class Sylius\Bundle\PromotionsBundle\Generator\CouponGenerator
, its goal is to generate unique coupon codes.
SyliusPromotionsBundle
provides a way to transform a simple string code to a real Coupon
object (and vice versa). This is done via the Sylius\Bundle\PromotionsBundle\Form\DataTransformer\CouponToCodeTransformer
class.
This data transformer is used by default with the Sylius\Bundle\PromotionsBundle\Form\Type\CouponToCodeType
form, provided as the service sylius.form.type.promotion_coupon_to_code
.
Примечание
An example of integration of this form can be found in the Sylius\Bundle\CoreBundle\Form\Type\CartType
class of Sylius/Standard-Edition
.
The Sylius\Bundle\PromotionsBundle\Controller\CouponController
provides an interface for easily generating new coupons.
sylius_promotions:
# The driver used for persistence layer.
driver: ~
classes:
promotion:
model: Sylius\Component\Promotion\Model\Promotion
controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController
repository: ~
form: Sylius\Bundle\PromotionsBundle\Form\Type\PromotionType
promotion_rule:
model: Sylius\Component\Promotion\Model\Rule
controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController
repository: ~
form: Sylius\Bundle\PromotionsBundle\Form\Type\RuleType
promotion_action:
model: Sylius\Component\Promotion\Model\Action
controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController
repository: ~
form: Sylius\Bundle\PromotionsBundle\Form\Type\ActionType
promotion_coupon:
model: Sylius\Component\Promotion\Model\Coupon
controller: Sylius\Bundle\PromotionsBundle\Controller\CouponController
repository: ~
form: Sylius\Bundle\PromotionsBundle\Form\Type\CouponType
promotion_subject:
model: ~
controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController
validation_groups:
promotion: [sylius]
promotion_rule: [sylius]
promotion_coupon: [sylius]
promotion_action: [sylius]
promotion_rule_item_total_configuration: [sylius]
promotion_rule_item_count_configuration: [sylius]
promotion_rule_user_loyality_configuration: [sylius]
promotion_rule_shipping_country_configuration: [sylius]
promotion_rule_taxonomy_configuration: [sylius]
promotion_rule_nth_order_configuration: [sylius]
promotion_action_fixed_discount_configuration: [sylius]
promotion_action_percentage_discount_configuration: [sylius]
promotion_action_add_product_configuration: [sylius]
promotion_coupon_generate_instruction: [sylius]
promotion_action_shipping_discount_configuration: [sylius]
This bundle uses GitHub issues. If you have found bug, please create an issue.
Symfony2 integration for Omnipay library, PHP abstraction for payment gateways.
We assume you’re familiar with Composer, 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.
$ composer require "sylius/omnipay-bundle"
Otherwise you have to download .phar file.
$ curl -sS https://getcomposer.org/installer | php
$ php composer.phar require "sylius/omnipay-bundle"
First, you need to enable the bundle inside the kernel.
<?php
// app/AppKernel.php
public function registerBundles()
{
$bundles = array(
// ...
new FOS\RestBundle\FOSRestBundle(),
new JMS\SerializerBundle\JMSSerializerBundle($this),
new Sylius\Bundle\OmnipayBundle\SyliusOmnipayBundle()
);
}
Put this configuration inside your app/config/config.yml
.
sylius_omnipay:
gateways:
AuthorizeNet_AIM: # gateway name, use anyone
type: AuthorizeNet_AIM # predefined list of types, read father for explanation
label: Authorize.Net AIM # how is gateway will be displayed in a form, etc.
AuthorizeNet_SIM:
type: AuthorizeNet_SIM
label: Authorize.Net SIM
Stripe:
type: Stripe
label: Stripe
mode: true # optional, default: false, activate test mode
active: false # optional, default: true, does this gateway is active
options: # optional, predefine list of options to get work with gateway
apikey: secretapikey
PayPal_Express:
type: PayPal_Express
label: PayPal Express
PayPal_Pro:
type: PayPal_Pro
label: PayPal Pro
The following gateways are already implemented:
The list above is always growing. The full list of supported gateways can be found at the Omnipay github repository.
The bundle provide handy services, which help to manage the activated gateways and options.
Here are a couple of services which are available to use out of the box.
To be written.
Flexible categorization system for Symfony2 applications.
We assume you’re familiar with Composer, 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.
$ 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"
Примечание
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.
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 JMS\SerializerBundle\JMSSerializerBundle($this),
new Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle(),
new WhiteOctober\PagerfantaBundle\WhiteOctoberPagerfantaBundle(),
new Sylius\Bundle\TaxonomyBundle\SyliusTaxonomyBundle(),
new Sylius\Bundle\ResourceBundle\SyliusResourceBundle(),
// Other bundles...
new Doctrine\Bundle\DoctrineBundle\DoctrineBundle(),
);
}
Примечание
Please register the bundle before DoctrineBundle. This is important as we use listeners which have to be processed first.
Put this configuration inside your app/config/config.yml
.
sylius_taxonomies:
driver: doctrine/orm # Configure the doctrine orm driver used in documentation.
And configure doctrine extensions which are used in the taxonomy bundle:
stof_doctrine_extensions:
orm:
default:
tree: true
sluggable: true
Add the following lines to your app/config/routing.yml
.
sylius_taxonomy:
resource: @SyliusTaxonomyBundle/Resources/config/routing.yml
prefix: /taxonomy
Run the following command.
$ php app/console doctrine:schema:update --force
Предупреждение
This should be done only in dev environment! We recommend using Doctrine migrations, to safely update your schema.
Retrieving taxonomy 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 taxonomy repository is always accessible via sylius.repository.taxonomy
service.
Of course, sylius.repository.taxon
is also available for use, but usually you obtains taxons directly from Taxonomy model.
You’ll see that in further parts of this document.
<?php
public function myAction(Request $request)
{
$repository = $this->container->get('sylius.repository.taxonomy');
}
Retrieving taxonomies is simpleas calling proper methods on the repository.
<?php
public function myAction(Request $request)
{
$repository = $this->container->get('sylius.repository.taxonomy');
$taxonomy = $repository->find(2); // Get taxonomy with id 2, returns null if not found.
$taxonomy = $repository->findOneBy(array('name' => 'Specials')); // Get one taxonomy by defined criteria.
$taxonomies = $repository->findAll(); // Load all the taxonomies!
$taxonomies = $repository->findBy(array('hidden' => true)); // Find taxonomies matching some custom criteria.
}
Taxonomy repository also supports paginating taxonomies. To create a Pagerfanta instance use the createPaginator
method.
<?php
public function myAction(Request $request)
{
$repository = $this->container->get('sylius.repository.taxonomy');
$taxonomies = $repository->createPaginator();
$taxonomies->setMaxPerPage(5);
$taxonomies->setCurrentPage($request->query->get('page', 1));
// Now you can return taxonomies to template and iterate over it to get taxonomies from current page.
}
Paginator also can be created for specific criteria and with desired sorting.
<?php
public function myAction(Request $request)
{
$repository = $this->container->get('sylius.repository.taxonomy');
$taxonomies = $repository->createPaginator(array('foo' => true), array('createdAt' => 'desc'));
$taxonomies->setMaxPerPage(3);
$taxonomies->setCurrentPage($request->query->get('page', 1));
}
To create new taxonomy instance, you can simply call createNew()
method on the repository.
<?php
public function myAction(Request $request)
{
$repository = $this->container->get('sylius.repository.taxonomy');
$taxonomy = $repository->createNew();
}
Примечание
Creating taxonomy via this factory method makes the code more testable, and allows you to change taxonomy class easily.
To save or remove a taxonomy, you can use any ObjectManager
which manages Taxonomy. You can always access it via alias sylius.manager.taxonomy
.
But it’s also perfectly fine if you use doctrine.orm.entity_manager
or other appropriate manager service.
<?php
public function myAction(Request $request)
{
$repository = $this->container->get('sylius.repository.taxonomy');
$manager = $this->container->get('sylius.manager.taxonomy'); // Alias for appropriate doctrine manager service.
$taxonomy = $repository->createNew();
$taxonomy
->setName('Foo')
;
$manager->persist($taxonomy);
$manager->flush(); // Save changes in database.
}
To remove a taxonomy, you also use the manager.
<?php
public function myAction(Request $request)
{
$repository = $this->container->get('sylius.repository.taxonomy');
$manager = $this->container->get('sylius.manager.taxonomy');
$taxonomy = $repository->find(5);
$manager->remove($taxonomy);
$manager->flush(); // Save changes in database.
}
Taxons can be handled exactly the same way, but with usage of sylius.repository.taxon
.
Taxonomy contains methods which allow you to retrieve the child taxons. Let’s look again at our example tree.
| Categories
|-- T-Shirts
| |-- Men
| `-- Women
|-- Stickers
|-- Mugs
`-- Books
To get a flat list of taxons under taxonomy, use the getTaxonsAsList
method.
<?php
public function myAction(Request $request)
{
// Find the taxonomy
$taxonomyRepository = $this->container->get('sylius.repository.taxonomy');
$taxonomy = $taxonomyRepository->findOneByName('Categories');
// Get the taxons as a list
$taxonRepository = $this->container->get('sylius.repository.taxon');
$taxons = $taxonRepository->getTaxonsAsList($taxonomy);
}
$taxons
variable will now contain flat list (ArrayCollection) of taxons in following order: T-Shirts, Men, Women, Stickers, Mugs, Books.
If, for example, you want to render them as tree.
<?php
public function myAction(Request $request)
{
$repository = $this->container->get('sylius.repository.taxonomy');
$taxonomy = $repository->findOneByName('Categories');
$taxons = $taxonomy->getTaxons();
}
Now $taxons
contains only the 4 main items, and you can access their children by calling $taxon->getChildren()
.
sylius_taxonomies:
# The driver used for persistence layer.
driver: ~
classes:
taxonomy:
model: Sylius\Component\Taxonomy\Model\Taxonomy
controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController
repository: ~
form: Sylius\Bundle\TaxonomiesBundle\Form\Type\TaxonomyType
taxon:
model: Sylius\Component\Taxonomy\Model\Taxon
controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController
repository: ~
form: Sylius\Bundle\TaxonomiesBundle\Form\Type\TaxonType
validation_groups:
taxonomy: [sylius]
taxon: [sylius]
$ composer install --dev --prefer-dist
$ bin/phpspec run -fpretty --verbose
This bundle uses GitHub issues. If you have found bug, please create an issue.
Settings system with web editing interface for Symfony2 applications.
Allowing application users to edit some global configuration and storing it in database is a common need in a variety of projects. This bundle provides exactly this feature - you only need to define the configuration.
We assume you’re familiar with Composer, 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.
$ composer require "sylius/settings-bundle"
Otherwise you have to download .phar file.
$ curl -sS https://getcomposer.org/installer | php
$ php composer.phar require "sylius/settings-bundle"
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. This bundle also uses DoctrineCacheBundle. 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($this),
new Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle(),
new Doctrine\Bundle\DoctrineCacheBundle\DoctrineCacheBundle(),
new WhiteOctober\PagerfantaBundle\WhiteOctoberPagerfantaBundle(),
new Sylius\Bundle\SettingsBundle\SyliusSettingsBundle(),
new Sylius\Bundle\ResourceBundle\SyliusResourceBundle(),
// Other bundles...
new Doctrine\Bundle\DoctrineBundle\DoctrineBundle(),
);
}
Please register the bundle before *DoctrineBundle*. This is important as we use listeners which have to be processed first.
Put this configuration inside your app/config/config.yml
.
sylius_settings:
driver: doctrine/orm
doctrine_cache:
providers:
sylius_settings:
type: file_system
Import default routing from your app/config/routing.yml
.
sylius_settings:
resource: @SyliusSettingsBundle/Resources/config/routing.yml
prefix: /settings
Примечание
We used default
namespace in this example. If you want to use other namespaces for saving your settings, routing config should
be updated as it contains the namespace parameter.
Run the following command.
$ php app/console doctrine:schema:update --force
Предупреждение
This should be done only in dev environment! We recommend using Doctrine migrations, to safely update your schema.
You have to create a new class implementing SchemaInterface, which will represent the structure of your configuration. For purpose of this tutorial, let’s define the page metadata settings.
<?php
// src/Acme/ShopBundle/Settings/MetaSettingsSchema.php
namespace Acme\ShopBundle\Settings;
use Sylius\Bundle\SettingsBundle\Schema\SchemaInterface;
use Sylius\Bundle\SettingsBundle\Schema\SettingsBuilderInterface;
use Symfony\Component\Form\FormBuilderInterface;
class MetaSettingsSchema implements SchemaInterface
{
public function buildSettings(SettingsBuilderInterface $builder)
{
$builder
->setDefaults(array(
'title' => 'Sylius - Modern ecommerce for Symfony2',
'meta_keywords' => 'symfony, sylius, ecommerce, webshop, shopping cart',
'meta_description' => 'Sylius is modern ecommerce solution for PHP. Based on the Symfony2 framework.',
))
->setAllowedTypes(array(
'title' => array('string'),
'meta_keywords' => array('string'),
'meta_description' => array('string'),
))
;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder
->add('title')
->add('meta_keywords')
->add('meta_description', 'textarea')
;
}
}
Примечание
SettingsBuilderInterface is extended version of Symfony’s OptionsResolver component.
As you can see there are two methods in our schema and both are very simple. First one, the ->buildSettings()
defines default values and allowed data types. Second, ->buildForm()
creates the form to be used in the web interface to update the settings.
Now, lets register our MetaSettingsSchema service. Remember that we are tagging it as sylius.settings_schema:
<service id="acme.settings_schema.meta" class="Acme\ShopBundle\Settings\MetaSettingsSchema">
<tag name="sylius.settings_schema" namespace="meta" />
</service>
Your new settings schema is available for use.
To edit the settings via the web interface, simply point users to the sylius_settings_update
route with proper parameters.
In order to update our meta settings, generate the following link.
<a href="{{ path('sylius_settings_update', {'namespace': 'meta'}) }}">Edit SEO</a>
A proper form will be generated, with a submit action, which updates the settings in database.
Bundle provides handy SyliusSettingsExtension which you can use in your templates.
In our example, it can be something like:
{% set meta = sylius_settings_all('meta') %}
<head>
<title>{{ meta.title }}</title>
<meta name="keywords" content="{{ meta.meta_keywords }}">
<meta name="description" content="{{ meta.meta_description }}">
</head>
There is also sylius_settings_get() to get particular setting directly.
{% set title = sylius_settings_get('meta.title') %}
<head>
<title>{{ title }}</title>
</head>
You can also load and save the settings in any service. Simply use the SettingsManager service, available under the sylius.settings.manager
id.
<?php
// src/Acme/ShopBundle/Taxation/TaxApplicator.php
namespace Acme\ShopBundle\Taxation;
use Sylius\Bundle\SettingsBundle\Manager\SettingsManagerInterface;
class TaxApplicator
{
private $settingsManager;
public function __construct(SettingsManagerInterface $settingsManager)
{
$this->settingsManager = $settingsManager;
}
public function applyTaxes(Order $order)
{
$taxationSettings = $this->settingsManager->loadSettings('taxation');
$itemsTotal = $order->getItemsTotal();
$order->setTaxTotal($taxationSettings->get('rate') * $itemsTotal);
}
}
Injecting the settings manager is as simple as using any other service.
<service id="acme.tax_applicator" class="Acme\ShopBundle\Taxation\TaxApplicator">
<argument type="service" id="sylius.settings.manager" />
</service>
Примечание
To be written.
sylius_settings:
# The driver used for persistence layer.
driver: ~
classes:
parameter:
model: Sylius\Bundle\SettingsBundle\Model\Parameter
controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController
repository: ~
form: Sylius\Bundle\SettingsBundle\Form\Type\ParameterType
$ composer install --dev --prefer-dist
$ bin/phpspec run -fpretty --verbose
This bundle uses GitHub issues. If you have found bug, please create an issue.
Wizards with reusable steps for Symfony2 applications. Suitable for building checkouts or installations.
We assume you’re familiar with Composer, 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.
$ composer require "sylius/flow-bundle"
Otherwise you have to download .phar file.
$ curl -sS https://getcomposer.org/installer | php
$ php composer.phar require "sylius/flow-bundle"
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 Sylius\Bundle\FlowBundle\SyliusFlowBundle(),
// Other bundles...
);
}
We will create a very simple wizard now, without forms, storage, to keep things simple and get started fast.
Lets create a few simple steps:
<?php
namespace Acme\DemoBundle\Process\Step;
use Sylius\Bundle\FlowBundle\Process\Context\ProcessContextInterface;
use Sylius\Bundle\FlowBundle\Process\Step\ControllerStep;
class FirstStep extends ControllerStep
{
public function displayAction(ProcessContextInterface $context)
{
return $this->render('AcmeDemoBundle:Process/Step:first.html.twig');
}
public function forwardAction(ProcessContextInterface $context)
{
return $this->complete();
}
}
<?php
namespace Acme\DemoBundle\Process\Step;
use Sylius\Bundle\FlowBundle\Process\Context\ProcessContextInterface;
use Sylius\Bundle\FlowBundle\Process\Step\ControllerStep;
class SecondStep extends ControllerStep
{
public function displayAction(ProcessContextInterface $context)
{
return $this->render('AcmeDemoBundle:Process/Step:second.html.twig');
}
public function forwardAction(ProcessContextInterface $context)
{
return $this->complete();
}
}
And so on, one class for each step in the wizard.
As you can see, there are two actions in each step, display and forward.
Usually, there is a form in a forward action where you can pick some data.
When you do return $this->complete()
the wizard will take you to the next step.
To group steps into the wizard, we will implement ProcessScenarioInterface:
<?php
namespace Acme\DemoBundle\Process;
use Sylius\Bundle\FlowBundle\Process\Builder\ProcessBuilderInterface;
use Sylius\Bundle\FlowBundle\Process\Scenario\ProcessScenarioInterface;
use Symfony\Component\DependencyInjection\ContainerAware;
use Acme\DemoBundle\Process\Step;
class SyliusScenario extends ContainerAware implements ProcessScenarioInterface
{
public function build(ProcessBuilderInterface $builder)
{
$builder
->add('first', new Step\FirstStep())
->add('second', new Step\SecondStep())
// ...
;
}
}
As you can see, we just add each step to process builder with a desired name. The name will be used in the routes to navigate to particular step.
In order for this to work, we need to register SyliusScenario and tag it as sylius.process.scenario
:
<service id="sylius.scenario.flow" class="Acme\DemoBundle\Process\SyliusScenario">
<call method="setContainer">
<argument type="service" id="service_container" />
</call>
<tag name="sylius.process.scenario" alias="sylius_flow" />
</service>
The configured alias will be used later in the route parameters to identify the scenario as you can have more then one.
Import routing configuration:
sylius_flow:
resource: @SyliusFlowBundle/Resources/config/routing.yml
prefix: /flow
If you take a look into imported routing configuration, you will see that sylius_flow_start
is a wizard entry point.
sylius_flow_display
displays the step with the given name, sylius_flow_forward
forwards to the next step from the step with the given name.
All routes have an scenarioAlias as a required parameter to identify the scenario.
Step templates are like any other action template, usually due to the nature of multi-step wizards, they have back and forward buttons:
<h1>Welcome to second step</h1>
<a href="{{ path('sylius_flow_display', {'scenarioAlias': 'sylius_flow', 'stepName': 'first'}) }}" class="btn btn-success"><i class="icon-backward icon-white"></i> back</a>
<a href="{{ path('sylius_flow_forward', {'scenarioAlias': 'sylius_flow', 'stepName': 'second'}) }}" class="btn btn-success">forward <i class="icon-forward icon-white"></i></a>
By default, flow bundle will use session for data storage. Here is simple example how to use it in your steps:
<?php
namespace Acme\DemoBundle\Process\Step;
use Sylius\Bundle\FlowBundle\Process\Context\ProcessContextInterface;
use Sylius\Bundle\FlowBundle\Process\Step\ControllerStep;
class FirstStep extends ControllerStep
{
// ...
public function forwardAction(ProcessContextInterface $context)
{
$request = $this->getRequest();
$form = $this->createForm('my_form');
if ($request->isMethod('POST') && $form->bind($request)->isValid()) {
$context->getStorage()->set('my_data', $form->getData());
return $this->complete();
}
return $this->render('AcmeDemoBundle:Process/Step:first.html.twig', array(
'form' => $form->createView(),
));
}
}
You can later get data with $context->getStorage()->get('my_data')
.
For more details about storage, check SyliusBundleFlowBundleStorageStorageInterface class.
sylius_flow:
storage: sylius.process_storage.session # The storage used to save flow data.
$ composer install --dev --prefer-dist
$ phpunit
If you want to see working implementation, try out the Sylius application.
There is also an example that shows how to integrate this bundle into Symfony Standard Edition.
This bundle uses GitHub issues. If you have found bug, please create an issue.
This bundle allows you to manage and generate variations of any compatible object with a very flexible architecture.
Sylius uses this bundle internally for its product catalog to manage variations of the same product based on its options and their values, but can be used with almost any object.
We assume you’re familiar with Composer, 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.
$ composer require "sylius/variation-bundle"
Otherwise you have to download .phar file.
$ curl -sS https://getcomposer.org/installer | php
$ php composer.phar require "sylius/variation-bundle"
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($this),
new Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle(),
new WhiteOctober\PagerfantaBundle\WhiteOctoberPagerfantaBundle(),
new Sylius\Bundle\VariationBundle\SyliusVariationBundle(),
new Sylius\Bundle\ResourceBundle\SyliusResourceBundle(),
// Other bundles...
new Doctrine\Bundle\DoctrineBundle\DoctrineBundle(),
);
}
Примечание
Please register the bundle before DoctrineBundle. This is important as we use listeners which have to be processed first.
Put this configuration inside your app/config/config.yml
.
sylius_variation:
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
softdeleteable: true
Run the following command.
$ php app/console doctrine:schema:update --force
Предупреждение
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.
sylius_variation:
driver: ~ # The driver used for persistence layer. Currently only `doctrine/orm` is supported.
classes:
# `variation_name` can be any name, for example `product`, `ad`, or `blog_post`
variation_name:
variable: ~ # Required: The variable model class implementing `VariableInterface`
# of which variants can be created from
variant:
model: ~ # Required: The variant model class implementing `VariantInterface`
repository: ~ # Required: The repository class for the variant
controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController
form: Sylius\Bundle\VariationBundle\Form\Type\VariantType
option:
model: ~ # Required: The option model class implementing `OptionInterface`
repository: ~ # Required: The repository class for the option
controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController
form: Sylius\Bundle\VariationBundle\Form\Type\OptionType
option_value:
model: ~ # Required: The option value model class implementing `OptionValueInterface`
repository: ~ # Required: The repository class for the option value
controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController
form: Sylius\Bundle\VariationBundle\Form\Type\OptionValueType
validation_groups:
# `variation_name` should be same name as the name key defined for the classes section above.
variation_name:
variant: [ sylius ]
option: [ sylius ]
option_value: [ sylius ]
E-Commerce components for PHP.
Sylius Addressing component for PHP E-Commerce applications.
The component provides you with basic Address, Country, Province and Zone models.
We assume you’re familiar with Composer, a dependency manager for PHP. Use following command to add the component to your composer.json and download package.
If you have Composer installed globally.
$ composer require sylius/addressing:*
Otherwise you have to download .phar file.
$ curl -sS https://getcomposer.org/installer | php
$ php composer.phar require sylius/addressing:*
This is a very basic representation of Address model.
Attribute | Description |
---|---|
id | Unique id of the address |
firstname | |
lastname | |
company | |
country | Reference to Country model |
province | Reference to Province model |
street | |
city | |
postcode | |
createdAt | Date when address was created |
updatedAt | Date of last address update |
Creating and modifying Address object is very simple.
<?php
use Sylius\Component\Addressing\Address;
$address = new Address();
$address
->setFirstname('John')
->setLastname('Doe')
->setStreet('Superstreet 14')
->setCity('New York')
->setPostcode('13111')
;
$user = // Get your user from somewhere or any model which can reference an Address.
$user->setShippingAddress($address);
“Country” model represents a geographical area of a country.
Attribute | Description |
---|---|
id | Unique id of the country |
name | |
isoName | |
provinces | Collection of provinces |
createdAt | Date when country was created |
updatedAt | Date of last country update |
<?php
use Sylius\Component\Addressing\Address;
use Sylius\Component\Addressing\Country;
$poland = new Country();
$poland
->setName('Poland')
->setIsoName('PL')
;
$address
->setFirstname('Pawel')
->setLastname('Jedrzejewski')
->setCountry($poland)
->setStreet('Testing 123')
->setCity('Lodz')
->setPostcode('21-242')
;
“Province” is a smaller area inside of a Country. You can use it to manage provinces or states and assign it to an address as well.
Attribute | Description |
---|---|
id | Unique id of the province |
name | |
country | Reference to a country |
createdAt | Date when province was created |
updatedAt | Date of last update |
<?php
use Sylius\Component\Addressing\Address;
use Sylius\Component\Addressing\Country;
use Sylius\Component\Addressing\Province;
$usa = new Country();
$usa
->setName('United States of America')
->setIsoName('US')
;
$tennessee = new Province();
$tennessee->setName('Tennessee');
$address
->setFirstname('John')
->setLastname('Deo')
->setCountry($usa)
->setProvince($tennessee)
->setStreet('Testing 111')
->setCity('Nashville')
->setPostcode('123123')
;
This library allows you to define Zones, which represent a specific geographical area.
Every Zone is represented by the following model:
Attribute | Description |
---|---|
id | Unique id of the zone |
name | |
type | String type of zone |
members | Zone members |
createdAt | Date when zone was created |
updatedAt | Date of last update |
It exposes the following API:
<?php
$zone->getName(); // Name of the zone, for example "EU".
$zone->getType(); // Type, for example "country".
foreach ($zone->getMembers() as $member) {
echo $member->getName();
}
$zone->getCreatedAt();
$zone->getUpdatedAt();
Three different types of zones are supported out-of-the-box.
country
zone, which consists of many countries.province
zone, which is constructed from multiple provinces.zone
, which is a group of other zones.Each zone type has different ZoneMember model, but they all expose the same API:
<?php
foreach ($zone->getMembers() as $member) {
echo $member->getName();
echo $member->getZone()->getName(); // Name of the zone.
}
There are following models and each of them represents a different zone member:
ZoneMemberCountry
ZoneMemberProvince
ZoneMemberZone
Each ZoneMember instance holds a reference to the Zone object and appropriate area entity, for example a Country.
Creating a zone requires adding appropriate members:
<?php
use Sylius\Component\Addressing\Country;
use Sylius\Component\Addressing\Zone;
use Sylius\Component\Addressing\ZoneInterface;
use Sylius\Component\Addressing\ZoneMemberCountry;
$eu = new Zone();
$eu
->setName('European Union')
->setType(ZoneInterface::TYPE_COUNTRY)
;
$germany = new Country();
$germany
->setName('Germany')
->setIsoName('DE')
;
$france = new Country();
$france
->setName('France')
->setIsoName('FR')
;
$poland = new Country();
$poland
->setName('Poland')
->setIsoName('PL')
;
$germanyMember = new ZoneMemberCountry();
$germanyMember->setCountry($germany)
$franceMember = new ZoneMemberCountry();
$franceMember->setCountry($france)
$polandMember = new ZoneMemberCountry();
$polandMember->setCountry($poland)
$eu
->addMember($germany)
->addMember($france)
->addMember($poland)
;
Совет
Default zone types are defined as constants in the ZoneInterface
interface.
Exactly the same process applies to different types of Zones.
Zones are not very useful by themselves, but they can be 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.
Default implementation uses a collaborator implementing RepositoryInterface
to obtain all available zones and then compares them with the given Address.
<?php
use Sylius\Component\Addressing\Matcher\ZoneMatcher;
$zoneRepository = // Get the repository.
$zoneMatcher = new ZoneMatcher($zoneRepository);
$zone = $zoneMatcher->match($user->getAddress());
// Apply appropriate taxes or return shipping methods supported for given zone.
Zone matcher can also return all matching zones. (not only the best one)
<?php
$zones = $zoneMatcher->matchAll($user->getAddress());
// Inventory can be take from stock locations in the following zones...
There are many other use-cases!
This component uses GitHub issues. If you have found bug, please create an issue.
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.
We assume you’re familiar with Composer, a dependency manager for PHP. Use following command to add the component to your composer.json and download package.
If you have Composer installed globally.
$ composer require sylius/attribute:*
Otherwise you have to download .phar file.
$ curl -sS https://getcomposer.org/installer | php
$ php composer.phar require sylius/attribute:*
Every attribute is represented by the Attribute instance and has following properties:
Attribute | Description |
---|---|
id | Unique id of the attribute |
name | Name of the attribute (“tshirt_material”) |
presentation | Pretty name visible for user (“Material”) |
type | Attribute type |
createdAt | Date when attribute was created |
updatedAt | Date of last attribute update |
The following attribute types are available by default.
Type | Related constant |
---|---|
text | TEXT |
number | NUMBER |
percentage | PERCENTAGE |
checkbox | CHECKBOX |
choice | CHOICE |
money | MONEY |
You can get all the the available type by using the static method AttributeTypes::getChoices()
This model binds the subject and the attribute, it is used to store the value of the attribute for the subject. It has following properties:
Attribute | Description |
---|---|
subject | Subject (Example: a product) |
attribute | Attribute (Example: a description) |
value | Value |
Примечание
AttributeValues::getValue()
will throw \BadMethodCallException
if the attribute is not set
To characterize an object with attributes, the object class needs to implement the AttributeSubjectInterface
.
Method | Description | Returned value |
---|---|---|
getAttributes() | Returns all attributes of the subject. | AttributeValueInterface[] |
setAttributes(Collection $attributes) | Sets all attributes of the subject. | Void |
addAttribute(AttributeValue $attribute) | Adds an attribute to the subject. | Void |
removeAttribute(AttributeValue $attribute) | Removes an attribute from the subject. | Void |
hasAttribute(AttributeValue $attribute) | Checks whether the subject has a given attribute. | Boolean |
hasAttributeByName($attributeName) | Checks whether the subject has a given attribute, access by name. | Boolean |
getAttributeByName($attributeName) | Returns an attribute of the subject by its name. | AttributeValueInterface |
This component uses GitHub issues. If you have found bug, please create an issue.
Common models, services and interface to handle a shopping cart in PHP e-commerce application.
We assume you’re familiar with Composer, a dependency manager for PHP. Use following command to add the component to your composer.json and download package.
If you have Composer installed globally.
$ composer require sylius/cart:*
Otherwise you have to download .phar file.
$ curl -sS https://getcomposer.org/installer | php
$ php composer.phar require sylius/cart:*
The cart is represented by Cart instance. It inherits all the properties from Sylius\Component\Order\Model\Order
and has following property:
Method | Description | Type |
---|---|---|
expiresAt | Expiration time | DateTime |
Примечание
This model implements CartInterface
The items of the cart are represented by CartItem instance and it has not property but it inherits all of them from Sylius\Component\Order\Model\OrderItem
Примечание
This model implements CartItemInterface
A cart provider retrieves existing cart or create new one based on storage. To characterize an object which is a Provider,
it needs to implement the CartProviderInterface
and the following method:
Method | Description | Returned value |
---|---|---|
hasCart() | Check if the the cart exists | boolean |
getCart() | Get current cart. If none found is by storage, it should create new one and save it | CartInterface |
setCart(CartInterface $cart) | Sets given cart as current one | Void |
abandonCart() | Abandon current cart | Void |
A cart storage stores current cart id. To characterize an object which is a Storage, it needs to implement the CartProviderInterface
and the following method:
Method | Description | Returned value |
---|---|---|
getCurrentCartIdentifier() | Returns current cart id, used then to retrieve the cart | mixed |
setCurrentCartIdentifier(CartInterface $cart) | Sets current cart id and persists it | CartInterface |
resetCurrentCartIdentifier() | Resets current cart identifier, it means abandoning current cart | Void |
A cart resolver returns cart item that needs to be added based on given data. To characterize an object which is a Resolver,
it needs to implement the ItemResolverInterface
and the following method:
Method | Description | Returned value |
---|---|---|
resolve(CartItemInterface $item, $data) | Returns item that will be added into the cart | CartItemInterface |
Примечание
This method throw ItemResolvingException
if an error occurs
A cart purger purges all expired carts. To characterize an object which is a Purger, it needs to implement the PurgerInterface
and the following method:
Method | Description | Returned value |
---|---|---|
purge() | Purge all expired carts | boolean |
This component uses GitHub issues. If you have found bug, please create an issue.
Managing different currencies, exchange rates and converting cash amounts for PHP apps.
We assume you’re familiar with Composer, a dependency manager for PHP. Use following command to add the component to your composer.json and download package.
If you have Composer installed globally.
$ composer require sylius/currency:*
Otherwise you have to download .phar file.
$ curl -sS https://getcomposer.org/installer | php
$ php composer.phar require sylius/currency:*
Every currency is represented by Currency instance and has following properties:
Method | Description | Type |
---|---|---|
code | Code of the currency | string |
exchangeRate | Exchange rate | float |
enabled | boolean | |
createdAt | Date of creation | DateTime |
updatedAt | Date of last update | DateTime |
Примечание
This interface asks you to implement a extra method named CurrencyInterface::getName()
which will return the human-friendly
name of the currency
The CurrencyProvider allows you to get the available currencies, it implements the CurrencyProviderInterface
.
$currencyRepository = new EntityRepository();
$currencyProvider = new CurrencyProvider($currencyRepository);
$availableCurrencies = $currencyProvider->getAvailableCurrencies();
foreach ($availableCurrencies as $currency) {
echo $currency->getCode();
}
This component uses GitHub issues. If you have found bug, please create an issue.
Managing different locales for PHP apps.
We assume you’re familiar with Composer, a dependency manager for PHP. Use following command to add the component to your composer.json and download package.
If you have Composer installed globally.
$ composer require sylius/locale:*
Otherwise you have to download .phar file.
$ curl -sS https://getcomposer.org/installer | php
$ php composer.phar require sylius/locale:*
This component uses GitHub issues. If you have found bug, please create an issue.
Inventory management for PHP applications.
We assume you’re familiar with Composer, a dependency manager for PHP. Use following command to add the component to your composer.json and download package.
If you have Composer installed globally.
$ composer require sylius/inventory:*
Otherwise you have to download .phar file.
$ curl -sS https://getcomposer.org/installer | php
$ php composer.phar require sylius/inventory:*
This component uses GitHub issues. If you have found bug, please create an issue.
E-Commerce PHP library for creating and managing sales orders.
We assume you’re familiar with Composer, a dependency manager for PHP. Use following command to add the component to your composer.json and download package.
If you have Composer installed globally.
$ composer require sylius/order:*
Otherwise you have to download .phar file.
$ curl -sS https://getcomposer.org/installer | php
$ php composer.phar require sylius/order:*
The library comes with 2 basic models representing the Order and it OrderItems.
Order is constructed with the following attributes:
Attribute | Description | Returned value |
---|---|---|
id | Unique id of the order | mixed |
number | Human-friendly number | string |
state | String represeting the status | string |
items | Collection of OrderItems | OrderItemInterface[] |
itemsTotal | Total value of the items | integer |
adjustments | Collection of Adjustments | AdjustmentInterface[] |
adjustmentsTotal | Total value of adjustments | integer |
total | Order grand total | integer |
confirmed | Boolean indicator of order confirmation | boolean |
confirmationToken | Random string for order confirmation | string |
createdAt | Date when order was created | DateTime |
updatedAt | Date of last change | DateTime |
completedAt | Checkout completion time | DateTime |
deletedAt | Date of deletion | DateTime |
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');
To check whether the order is confirmed or not, you can use the isConfirmed()
method, which returns a true/false value.
To change that status, you can use the confirmation setter, setConfirmed(false)
. All orders are confirmed by default.
Order can contain a confirmation token, accessible by the appropriate getter and setter.
<?php
if ($order->isConfirmed()) {
echo 'This one is confirmed, great!';
}
Примечание
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(); // 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.
Recalculation of totals can happen by calling ->calculateTotal()
method, using the simplest math. It will also update the item totals.
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')
->setUnitPrice(1999) // 19.99!
->setQuantity(2)
;
$item1 = new OrderItem();
$item1
->setName('Interesting t-shirt')
->setUnitPrice(2549) // 25.49!
;
$order
->addItem($item1)
->addItem($item2)
->removeItem($item1)
;
OrderItem model has the attributes listed below:
Attribute | Description | Returned value |
---|---|---|
id | Unique id of the item | mixed |
order | Reference to an Order | OrderInterface |
unitPrice | The price of a single unit | integer |
quantity | Quantity of sold item | integer |
adjustments | Collection of Adjustments | adjustmentInterface[] |
adjustmentsTotal | Total value of adjustments | integer |
total | Order grand total | integer |
createdAt | Date when order was created | DateTime |
updatedAt | Date of last change | DateTime |
An order item model has only the id property as identifier and it has the order reference, accessible via ->getOrder()
method.
<?php
echo $item->getId(); / Prints e.g. 12.
$item->setName($book);
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
->setName('Game of Thrones')
->setUnitPrice(2000)
->setQuantity(4)
->calculateTotal()
;
echo $item->getTotal(); // 8000.
An OrderItem can also hold adjustments.
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:
Attribute | Description | Returned value |
---|---|---|
id | Unique id of the adjustment | mixed |
adjustable | Reference to Order or OrderItem | OrderInterface|OrderItemInterface |
label | Type of the adjustment (e.g. “tax””) | string |
description | e.g. “Clothing Tax 9%” | string |
amount | Integer amount | integer |
neutral | Boolean flag of neutrality | boolean |
createdAt | Date when adjustment was created | DateTime |
updatedAt | Date of last change | DateTime |
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
->setName('Awesome T-Shirt')
->setUnitPrice(4999)
;
$shippingFees = new Adjustment();
$shippingFees->setAmount(1000);
$tax = new Adjustment();
$tax
->setAmount(1150)
->setLabel
->setNeutral(true)
;
echo $order
->addItem($tshirt)
->addAdjustment($shippingFees)
->addAdjustment($tax)
->calculateTotal()
->getTotal()
;
// Output will be 5999.
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
->setName('Awesome T-Shirt')
->setUnitPrice(4999)
;
$shippingFees = new Adjustment();
$shippingFees->setAmount(1000);
$tax = new Adjustment();
$tax
->setAmount(1150)
->setLabel
->setNeutral(true)
;
$discount = new Adjustment();
$discount->setAmount(500);
echo $order
->addItem($tshirt)
->addAdjustment($shippingFees)
->addAdjustment($tax)
->addAdjustment($discount)
->calculateTotal()
->getTotal()
;
// Output will be 5499.
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:
State | Description |
---|---|
cart | Unconfirmed order, ready to add/remove items |
cart_locked | Cart which should not be removed when expired |
pending | Order waiting for confirmation |
confirmed | Confirmed and completed order |
shipped | Order has been shipped to the customer |
abandoned | Been waiting too long for confirmation |
cancelled | Cancelled by customer or manager |
returned | Order has been returned and refunded |
Примечание
Please keep in mind that these states are just default, you can define and use your own. If you use this component with SyliusOrderBundle and Symfony2, you will have full state machine configuration at your disposal.
This component uses GitHub issues. If you have found bug, please create an issue.
PHP library which provides abstraction of payments management. It ships with default Payment, PaymentMethod and CreditCard models.
Примечание
This component does not provide any payment gateway. Integrating it with Payum.
We assume you’re familiar with Composer, a dependency manager for PHP. Use following command to add the component to your composer.json and download package.
If you have Composer installed globally.
$ composer require sylius/payment:*
Otherwise you have to download .phar file.
$ curl -sS https://getcomposer.org/installer | php
$ php composer.phar require sylius/payment:*
Every credit card is represented by CreditCard instance and has following properties:
Method | Description | Type |
---|---|---|
token | Payment gateway token | string |
type | Type of credit card (VISA, MasterCard...) | string |
cardholderName | Cardholder name | string |
number | Card number | string |
securityCode | Security code | string |
expiryMonth | Expiry month | integer |
expiryYear | Expiry year | integer |
createdAt | Date of creation | DateTime |
updatedAt | Date of the last update | DateTime |
Примечание
This model implements CreditCardInterface
. You need to implement an extra method getMaskedNumber
which will return the last 4 digits of card number
Every payment is represented by Payment instance and has following properties:
Method | Description | Type |
---|---|---|
method | payment method associated with this payment | null|PaymentMethodInterface |
currency | payment currency | string |
amount | amount | integer |
state | state | string |
creditCard | Credit card as a source | CreditCardInterface |
details | details | string |
createdAt | Date of creation | DateTime |
updatedAt | Date of the last update | DateTime |
The following payment types are available :
Type | Related constant |
---|---|
visa | BRAND_VISA |
mastercard | BRAND_MASTERCARD |
discover | BRAND_DISCOVER |
amex | BRAND_AMEX |
diners_club | BRAND_DINERS_CLUB |
jcb | BRAND_JCB |
switch | BRAND_SWITCH |
solo | BRAND_SOLO |
dankort | BRAND_DANKORT |
maestro | BRAND_MAESTRO |
forbrugsforeningen | BRAND_FORBRUGSFORENINGEN |
laser | BRAND_LASER |
Примечание
This model implements PaymentInterface
.
Every method of payment is represented by PaymentMethod instance and has following properties:
Method | Description | Type |
---|---|---|
name | Payments method name | string |
type | Enable or disable the payments method | boolean |
description | Payment method description | string |
gateway | Payment gateway to use | string |
environment | Required app environment | string |
createdAt | Date of creation | DateTime |
updatedAt | Date of the last update | DateTime |
Примечание
This model implements PaymentMethodInterface
.
To characterize an object with payment, the object class needs to implement the PaymentsSubjectInterface
.
Method | Description | Return value |
---|---|---|
getPayments() | Get all payments associated with the payment subject | PaymentInterface[] |
hasPayments() | Check if order has any payments | Boolean |
addPayment(PaymentInterface $payment) | Add a payment | Void |
removePayment(PaymentInterface $payment) | Remove a payment | Void |
hasPayment(PaymentInterface $payment) | Check if the payment subject has certain payment | Boolean |
This component uses GitHub issues. If you have found bug, please create an issue.
Calculating prices is a common task for most PHP E-Commerce applications. This library provides multiple strategies and flexible calculators system.
We assume you’re familiar with Composer, a dependency manager for PHP. Use following command to add the component to your composer.json and download package.
If you have Composer installed globally.
$ composer require sylius/pricing:*
Otherwise you have to download .phar file.
$ curl -sS https://getcomposer.org/installer | php
$ php composer.phar require sylius/pricing:*
This component uses GitHub issues. If you have found bug, please create an issue.
Powerful products catalog for PHP applications.
We assume you’re familiar with Composer, a dependency manager for PHP. Use following command to add the component to your composer.json and download package.
If you have Composer installed globally.
$ composer require sylius/product:*
Otherwise you have to download .phar file.
$ curl -sS https://getcomposer.org/installer | php
$ php composer.phar require sylius/product:*
Product is the main model in SyliusProductComponent. This simple class represents every unique product in the catalog. The default interface contains the following attributes with appropriate setters and getters.
Attribute | Description |
---|---|
id | Unique id of the product |
name | Name of the product |
slug | SEO slug, by default created from the name |
description | Description of your great product |
availableOn | Date when product becomes available in catalog |
metaDescription | Description for search engines |
metaKeywords | Comma separated list of keywords for product (SEO) |
createdAt | Date when product was created |
updatedAt | Date of last product update |
deletedAt | Date of deletion from catalog |
Except products, you can also define Properties (think Attributes) and define their values on each product. Default property model has following structure.
Attribute | Description |
---|---|
id | Unique id of the property |
name | Name of the property (“T-Shirt Material”) |
presentation | Pretty name visible for user (“Material”) |
type | Property type |
createdAt | Date when property was created |
updatedAt | Date of last property update |
Currently there are several different property types are available, a proper form widget (Symfony Form type) will be rendered on product form for entering the value.
Type |
---|
text |
number |
percentage |
checkbox |
choice |
This component uses GitHub issues. If you have found bug, please create an issue.
Super-flexible promotions system with support of complex rules and actions. Coupon codes included!
We assume you’re familiar with Composer, a dependency manager for PHP. Use following command to add the component to your composer.json and download package.
If you have Composer installed globally.
$ composer require sylius/promotion:*
Otherwise you have to download .phar file.
$ curl -sS https://getcomposer.org/installer | php
$ php composer.phar require sylius/promotion:*
This component uses GitHub issues. If you have found bug, please create an issue.
Simple registry component useful for all type of applications.
We assume you’re familiar with Composer, a dependency manager for PHP. Use following command to add the component to your composer.json and download package.
If you have Composer installed globally.
$ composer require sylius/registry:*
Otherwise you have to download .phar file.
$ curl -sS https://getcomposer.org/installer | php
$ php composer.phar require sylius/registry:*
A registry object acts as a collection of objects. The sylius registry allows you to store object which implements a specific interface. Let’s see how it works!
// First you need to create the registry object and tell what kind of interface that you want to work with.
$registry = new ServiceRegistry('Sylius\Component\Pricing\Calculator\CalculatorInterface');
// After that you can register a object.
// The first argument is key which identify your object and the second one is the object that you want to register
$registry->register('volume_based', new VolumeBasedCalculator());
$registry->register('standard', new StandardCalculator());
// You can unregister a object too
$registry->unregister('standard');
// You can check if your object is registered, the following method method return a boolean
$registry->has('volume_based');
// And finally, you can get the instance of your object
$registry->get('volume_based');
Примечание
If you try to register a object which not implement the right interface or is not a object a InvalidArgumentException will be thrown.
If you try to register an existing object a ExistingServiceException will be thrown
If you try to unregister a non existing object a NonExistingServiceException will be thrown
This component uses GitHub issues. If you have found bug, please create an issue.
Domain management abstraction for PHP. It provides interface for most common operations on the application resources.
We assume you’re familiar with Composer, a dependency manager for PHP. Use following command to add the component to your composer.json and download package.
If you have Composer installed globally.
$ composer require sylius/resource:*
Otherwise you have to download .phar file.
$ curl -sS https://getcomposer.org/installer | php
$ php composer.phar require sylius/resource:*
This interface will ask you to implement the following methods to your model, they will use by the soft deletable Doctrine2 extension. :
Method | Description | Returned value |
---|---|---|
isDeleted() | Check if the resource has been deleted | boolean |
getDeletedAt() | Get the time of deletion | DateTime |
setDeletedAt(DateTime $deletedAt) | Cet deletion time. | Void |
This interface will ask you to implement the following methods to your model, they will use by the timestampable Doctrine2 extension. :
Method | Description | Returned value |
---|---|---|
getCreatedAt() | Get creation time | DateTime |
getUpdatedAt() | Get the time of last update | DateTime |
setCreatedAt(DateTime $createdAt) | Set creation time | Void |
setUpdatedAt(DateTime $updatedAt) | Set the time of last update | Void |
This interface will ask you to implement two methods to your repositories:
Method | Description | Returned value |
---|---|---|
createNew() | Create a new instance of your resource | mixed |
createPaginator(array $criteria = null, array $orderBy = null) | Get paginated collection of your resources | mixed |
This component uses GitHub issues. If you have found bug, please create an issue.
Component for generating number/hash sequences in PHP.
We assume you’re familiar with Composer, a dependency manager for PHP. Use following command to add the component to your composer.json and download package.
If you have Composer installed globally.
$ composer require sylius/sequence:*
Otherwise you have to download .phar file.
$ curl -sS https://getcomposer.org/installer | php
$ php composer.phar require sylius/sequence:*
This component uses GitHub issues. If you have found bug, please create an issue.
Shipments and shipping methods management for PHP E-Commerce apps. It contains flexible calculators system for computing the shipping costs.
We assume you’re familiar with Composer, a dependency manager for PHP. Use following command to add the component to your composer.json and download package.
If you have Composer installed globally.
$ composer require sylius/shipping:*
Otherwise you have to download .phar file.
$ curl -sS https://getcomposer.org/installer | php
$ php composer.phar require sylius/shipping:*
This component uses GitHub issues. If you have found bug, please create an issue.
Tax rates and tax classification for PHP apps. You can define different tax categories and match them to objects.
We assume you’re familiar with Composer, a dependency manager for PHP. Use following command to add the component to your composer.json and download package.
If you have Composer installed globally.
$ composer require sylius/taxation:*
Otherwise you have to download .phar file.
$ curl -sS https://getcomposer.org/installer | php
$ php composer.phar require sylius/taxation:*
This component uses GitHub issues. If you have found bug, please create an issue.
Basic taxonomies library for any PHP application.
We assume you’re familiar with Composer, a dependency manager for PHP. Use following command to add the component to your composer.json and download package.
If you have Composer installed globally.
$ composer require sylius/taxonomy:*
Otherwise you have to download .phar file.
$ curl -sS https://getcomposer.org/installer | php
$ php composer.phar require sylius/taxonomy:*
Taxonomy is a list constructed from individual Taxons. Every taxonomy has one special taxon, which serves as a root of the tree. All taxons can have many child taxons, you can define as many of them as you need.
A good examples of taxonomies are “Categories” and “Brands”. Below you can see an example tree.
| Categories
|-- T-Shirts
| |-- Men
| `-- Women
|-- Stickers
|-- Mugs
`-- Books
| Brands
|-- SuperTees
|-- Stickypicky
|-- Mugland
`-- Bookmania
Attribute | Description | Type |
---|---|---|
id | Unique id of the taxonomy | mixed |
name | Name of the taxonomy | string |
root | First, “root” Taxon | TaxonInterface |
createdAt | Date when taxonomy was created | DateTime |
updatedAt | Date of last update | DateTime |
This model implements TaxonomyInterface
, it implements these extra methods:
Method | Description | Returned value |
---|---|---|
getTaxons() | Adds option value | TaxonInterface[] |
hasTaxon(TaxonInterface $taxon) | Check if the taxonomy has taxon | boolean |
addTaxon(TaxonInterface $taxon) | Add a taxon | Void |
removeTaxon(TaxonInterface $taxon) | Remove a taxon a taxon | Void |
Attribute | Description | Type |
---|---|---|
id | Unique id of the taxon | mixed |
name | Name of the taxon | string |
slug | Urlized name | string |
permalink | Full permalink for given taxon | string |
description | Description of taxon | string |
taxonomy | Taxonomy | TaxonomyInterface |
parent | Parent taxon | TaxonInterface |
children | Sub taxons | Collection |
left | Location within taxonomy | mixed |
right | Location within taxonomy | mixed |
level | How deep it is in the tree | mixed |
createdAt | Date when taxon was created | DateTime |
updatedAt | Date of last update | DateTime |
This model implements TaxonInterface
, it implements these extra methods:
Method | Description | Returned value |
---|---|---|
hasChild() | Check whether the taxon has a child | boolean |
addChild(TaxonInterface $taxon) | Add child taxon | Void |
removeChild(TaxonInterface $taxon) | Remove child taxon. | Void |
This interface should be implemented by models that support taxons.
Method | Description | Returned value |
---|---|---|
getTaxons($taxonomy = null) | Get all taxons | VariantInterface |
setTaxons(Collection $collection) | Set the taxons | Void |
hasTaxon(TaxonInterface $taxon) | Checks whether object has taxon | Boolean |
addTaxon(TaxonInterface $taxon) | Add a taxon | VariantInterface[] |
removeTaxon(TaxonInterface $taxon) | Remove a taxon | Void |
This component uses GitHub issues. If you have found bug, please create an issue.
Library for managing object variants and options. This functionality can be attached to any object to create different configurations.
We assume you’re familiar with Composer, a dependency manager for PHP. Use following command to add the component to your composer.json and download package.
If you have Composer installed globally.
$ composer require sylius/variation:*
Otherwise you have to download .phar file.
$ curl -sS https://getcomposer.org/installer | php
$ php composer.phar require sylius/variation:*
Every variant is represented by Variant instance and has following properties:
Method | Description | Type |
---|---|---|
master | Defines whether variant is master | boolean |
presentation | Name displayed to user. | string |
object | Related product | VariableInterface |
options | Option values | OptionValueInterface[] |
createdAt | Date of creation | DateTime |
updatedAt | Date of the last update | DateTime |
This model implements VariantInterface
, you need to implement these extra methods:
Method | Description | Returned value |
---|---|---|
addOption(OptionValueInterface $option) | Adds option value | Void |
removeOption(OptionValueInterface $option) | Removes option from variant | Void |
hasOption(OptionValueInterface $option) | Checks whether variant has given option | Boolean |
setDefaults(VariantInterface $masterVariant) | This method is used toinherit values from a master variant | Void |
Every variant option is represented by Option instance and has following properties:
Method | Description | Type |
---|---|---|
name | Internal name | string |
presentation | Name displayed to user | string |
values | Option values | OptionValueInterface[] |
createdAt | Date of creation | DateTime |
updatedAt | Date of the last update | DateTime |
This model implements OptionInterface
, you need to implement these extra methods:
Method | Description | Returned value |
---|---|---|
addValue(OptionValueInterface $option) | Adds option value | Void |
removeValue(OptionValueInterface $option) | Removes option value | Void |
removeValue(OptionValueInterface $option) | Checks whether option has given value | Boolean |
Every variant option value is represented by OptionValue instance and has following properties:
Method | Description | Type |
---|---|---|
value | Option internal value | string |
option | Option | OptionInterface |
createdAt | Date of creation | DateTime |
updatedAt | Date of the last update | DateTime |
This model implements OptionInterface
, you need to implement these extra methods:
Method | Description | Returned value |
---|---|---|
getName() | Proxy method to access the name of real option object | string |
getPresentation() | Proxy method to access the presentation of real option object | string |
This interface should be implemented by models that support variants and options.
Method | Description | Returned value |
---|---|---|
getMasterVariant() | Returns master variant | VariantInterface |
setMasterVariant(VariantInterface $variant) | Sets master variant | Void |
hasVariants() | Checks whether object has variant | Boolean |
getVariants() | Returns all object variants | VariantInterface[] |
setVariants(Collection $variants) | Sets all object variants | Void |
addVariant(VariantInterface $variant) | Adds variant | Void |
removeVariant(VariantInterface $variant) | Removes variant from object | Void |
hasVariant(VariantInterface $variant) | Checks whether object has given variant | Boolean |
hasOptions() | Checks whether object has given option | Boolean |
getOptions() | Returns all object options | OptionInterface[] |
setOptions(Collection $options) | Sets all object options | Void |
addOption(OptionInterface $option) | Adds option | Void |
removeOption(OptionInterface $option) | Removes option from product | Void |
hasOption(OptionInterface $option) | Checks whether object has given option | Boolean |
This is used to create all possible combinations of an object’s options and to create Variant
models from them.
Example:
If an object such as a t-shirt has 2 options - Color and Size - with 3 possible values per option, then the generator will create 9 variants and assign them to the object.
The generator will ignore invalid variants or variants that already exist.
T-Shirt Options
Colors | Size |
Black | Small |
White | Medium |
Red | Large |
Possible T-Shirt Variants
These variants should be generated by the VariantGenerator
from the options above.
The Code
use Sylius\Component\Resource\Repository\RepositoryInterface;
use Sylius\Component\Variation\Generator\VariantGenerator;
use Sylius\Component\Variation\Model\VariableInterface;
/** @var RepositoryInterface $variantRepository */
$variantRepository = /*...*/
/**
* From the example above, this would be the T-Shirt product.
*
* @var VariableInterface $variable
*/
$variable = /*...*/
$generator = new VariantGenerator($variantRepository);
// Generate all possible variants if they don't exist currently.
$generator->generate($variable)
Примечание
The variant generator implements Sylius\Component\Variation\Generator\VariantGeneratorInterface
.
This component uses GitHub issues. If you have found bug, please create an issue.
A guide to contribute to Sylius.
Примечание
This section is based on the great Symfony2 documentation.
Whenever you find a bug in Sylius, we kindly ask you to report it. It helps us make a better e-commerce solution for PHP.
Осторожно
If you think you’ve found a security issue, please use the special procedure instead.
Before submitting a bug:
If your problem definitely looks like a bug, report it using the official bug tracker and follow some basic rules:
Patches are the best way to provide a bug fix or to propose enhancements to Sylius.
Before working on Sylius, setup a Symfony2 friendly environment with the following software:
Set up your user information with your real name and a working email address:
$ git config --global user.name "Your Name"
$ git config --global user.email you@example.com
Совет
If you are new to Git, you are highly recommended to read the excellent and free ProGit book.
Совет
If your IDE creates configuration files inside the project’s directory,
you can use global .gitignore
file (for all projects) or
.git/info/exclude
file (per project) to ignore them. See
Github’s documentation.
Совет
Windows users: when installing Git, the installer will ask what to do with line endings, and suggests 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:
Sylius
directory):$ git clone git@github.com:USERNAME/Sylius.git
$ cd sylius
$ git remote add upstream git://github.com/Sylius/Sylius.git
Before you start, you must know that all the patches you are going to submit must be released under the MIT license, unless explicitly specified in your commits.
Each time you want to work on a patch for a bug or on an enhancement, create a topic branch:
$ git checkout -b BRANCH_NAME master
Совет
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 commands automatically switch the code to the newly created
branch (check the branch you are working on with git branch
).
Work on the code as much as you want and commit as much as you want; but keep in mind the following:
git diff --check
to check for
trailing spaces – also read the tip below);git rebase
to
have a clean and logical history);Совет
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.
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:
CHANGELOG
file(s) (the
[BC BREAK]
or the [DEPRECATION]
prefix must be used when relevant);UPGRADE
file(s) if the changes break backward compatibility or if you
deprecate something that will ultimately break backward compatibility.Whenever you feel that your patch is ready for submission, follow the following steps.
Before submitting your patch, update your branch (needed if it takes you a while to finish your changes):
$ git checkout master
$ git fetch upstream
$ git merge upstream/master
$ git checkout BRANCH_NAME
$ git rebase master
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 origin BRANCH_NAME
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
| ------------- | ---
| Bug fix? | [yes|no]
| New feature? | [yes|no]
| BC breaks? | [yes|no]
| Deprecations? | [yes|no]
| Fixed tickets | [comma separated list of tickets fixed by the PR]
| License | MIT
| Doc PR | [The reference to the documentation PR if any]
An example submission could now look as follows:
| Q | A
| ------------- | ---
| Bug fix? | no
| New feature? | no
| BC breaks? | no
| Deprecations? | no
| Fixed tickets | #12, #43
| License | MIT
| Doc PR | Sylius/Sylius-Docs#123
The whole table must be included (do not remove lines that you think are not relevant). For simple typos, minor changes in the PHPDocs, or changes in translation files, use the shorter version of the check-list:
| Q | A
| ------------- | ---
| Fixed tickets | [comma separated list of tickets fixed by the PR]
| License | MIT
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 “Fixed tickets”;
- If you answer yes to “New feature?”, you must submit a pull request to the documentation and reference it under the “Doc PR” section;
- If you answer yes to “BC breaks?”, the patch must contain updates to the relevant
CHANGELOG
andUPGRADE
files;- If you answer yes to “Deprecations?”, the patch must contain updates to the relevant
CHANGELOG
andUPGRADE
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.
In addition to this “code” pull request, you must also send a pull request to the documentation repository to update the documentation when appropriate.
Based on the feedback on the pull request, you might need to rework your
patch. Before re-submitting the patch, rebase with upstream/master
, don’t merge; and force the push to the origin:
$ git rebase -f upstream/master
$ git push --force origin BRANCH_NAME
Примечание
When doing a push --force
, always specify the branch name explicitly
to avoid messing other branches in the repo (--force
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/master
$ git push --force 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.
This document explains how Sylius issues are handled by the Sylius core team.
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 [at] sylius.org. Emails sent to this address are forwarded to the Sylius core team members.
For each report, we first try to confirm the vulnerability. When it is confirmed, the team works on a solution following these steps:
Примечание
Releases that include security issues should not be done on Saturday or Sunday, except if the vulnerability has been publicly posted.
Примечание
While we are working on a patch, please do not reveal the issue publicly.
Примечание
This part of documentation is inspired by the official PHPSpec docs.
Sylius adopted the full-stack BDD methodology for its development processes.
According to Wikipedia:
“BDD is a software development process based on test-driven development (TDD). Behavior-driven development combines the general techniques and principles of TDD with ideas from domain-driven design and object-oriented analysis and design to provide software developers and business analysts with shared tools and a shared process to collaborate on software development, with the aim of delivering software that matters.”
To run the entire suite of features and specs, including the ones that depend on external
dependencies, Sylius needs to be able to autoload them. By default, they are
autoloaded from vendor/
under the main root directory (see
autoload.php.dist
).
To install them all, use Composer:
Step 1: Get Composer
$ curl -s http://getcomposer.org/installer | php
Make sure you download composer.phar
in the same folder where
the composer.json
file is located.
Step 2: Install vendors
$ php composer.phar install
Примечание
Note that the script takes some time (several minutes) to finish.
Примечание
If you don’t have curl
installed, you can also just download the installer
file manually at http://getcomposer.org/installer. Place this file into your
project and then run:
$ php installer
$ php composer.phar install
Add this VirtualHost configuration:
<VirtualHost *:80>
ServerName sylius-test.local
RewriteEngine On
DocumentRoot /var/www/sylius/web
<Directory /var/www/sylius/web>
Options Indexes FollowSymLinks MultiViews
AllowOverride None
Order allow,deny
allow from all
</Directory>
RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f
RewriteRule ^(.*) %{DOCUMENT_ROOT}/app_test.php [QSA,L]
ErrorLog ${APACHE_LOG_DIR}/sylius-test-error.log
LogLevel warn
CustomLog ${APACHE_LOG_DIR}/sylius-test-access.log combined
</VirtualHost>
Update your /etc/hosts
file to include the VirtualHost hostname:
127.0.0.1 sylius-test.local
Additionally, copy behat.yml.dist
to behat.yml
, edit base_url parameter to match your host:
default:
...
extensions:
Behat\MinkExtension\Extension:
...
base_url: http://sylius-test.local/app_test.php/
We use Behat for StoryBDD and you should always write new scenarios when adding a feature, or update existing stories to adapt Sylius to business requirements changes.
Sylius is an open source project, so the client is not clearly visible at first look. But they are here - the Sylius users. We have our needs and Behat helps us understand and satisfy these needs.
Примечание
To be written.
You can launch Selenium by issuing the following command:
$ java -jar selenium-server-standalone-2.38.0.jar
Configure behat for Selenium:
default:
...
extensions:
Behat\MinkExtension\Extension:
default_session: selenium2
browser_name: firefox
base_url: http://sylius-test.local/app_test.php
selenium2:
capabilities: { "browser": "firefox", "version": "28"}
Run your scenario using the behat
console:
$ bin/behat
PHPSpec is a PHP toolset to drive emergent design by specification. It is not really a testing tool, but a design instrument, which helps structuring the objects and how they work together.
Sylius approach is to always describe the behavior of the next object you are about to implement.
As an example, we’ll write a service, which updates product prices based on an external API.
To initialize a new spec, use the desc
command.
We just need to tell PHPSpec we will be working on the PriceUpdater class.
$ bin/phpspec desc "Sylius\Bundle\CoreBundle\Pricing\PriceUpdater"
Specification for PriceUpdater created in spec.
What have we just done? PHPSpec has created the spec for us. You can navigate to the spec folder and see the spec there:
<?php
namespace spec\Sylius\Bundle\CoreBundle\Pricing;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class PriceUpdaterSpec extends ObjectBehavior
{
function it_is_initializable()
{
$this->shouldHaveType('Sylius\Bundle\CoreBundle\Pricing\PriceUpdater');
}
}
The object behavior is made of examples. Examples are encased in public methods,
started with it_
.
or its_
.
PHPSpec searches for such methods in your specification to run.
Why underscores for example names? just_because_its_much_easier_to_read
than someLongCamelCasingLikeThat
.
Now, let’s write first example which will update the products price:
<?php
namespace spec\Sylius\Bundle\CoreBundle\Pricing;
use Acme\ApiClient;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Sylius\Bundle\CoreBundle\Model\ProductInterface;
class PriceUpdaterSpec extends ObjectBehavior
{
function let(ApiClient $api)
{
$this->beConstructedWith($api);
}
function it_updates_product_price_through_api($api, ProductInterface $product)
{
$product->getSku()->shouldBeCalled()->willReturn('TES-12-A-1090');
$api->getCurrentProductPrice('TES-12-A-1090')->shouldBeCalled()->willReturn(1545);
$product->setPrice(1545)->shouldBeCalled();
$this->updatePrice($product);
}
}
The example looks clear and simple, the PriceUpdater
service should obtain the SKU of the product, call the external API and update products price accordingly.
Try running the example by using the following command:
$ bin/phpspec run
> spec\Sylius\Bundle\CoreBundle\Pricing\PriceUpdater
✘ it updates product price through api
Class PriceUpdater does not exists.
Do you want me to create it for you? [Y/n]
Once the class is created and you run the command again, PHPSpec will ask if it should create the method as well. Start implementing the very initial version of the price updater.
<?php
namespace Sylius\Bundle\CoreBundle\Pricing;
use Sylius\Bundle\CoreBundle\Model\ProductInterface;
use Acme\ApiClient;
class PriceUpdater
{
private $api;
public function __construct(ApiClient $api)
{
$this->api = $api;
}
public function updatePrice(ProductInterface $product)
{
$price = $this->api->getCurrentProductPrice($product->getSku());
$product->setPrice($price);
}
}
Done! If you run PHPSpec again, you should see the following output:
$ bin/phpspec run
> spec\Sylius\Bundle\CoreBundle\Pricing\PriceUpdater
✔ it updates product price through api
1 examples (1 passed)
223ms
This example is greatly simplified, in order to illustrate how we work. There should be few more examples, which cover errors, API exceptions and other edge-cases.
Few tips & rules to follow when working with PHPSpec & Sylius:
public
keyword;_
) in the examples;Happy coding!
When contributing code to Sylius, you must follow its coding standards.
Sylius follows the standards defined in the PSR-0, PSR-1 and PSR-2 documents.
Here is a short example containing most features described below:
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Acme;
/**
* Coding standards demonstration.
*/
class FooBar
{
const SOME_CONST = 42;
private $fooBar;
/**
* @param string $dummy Some argument description
*/
public function __construct($dummy)
{
$this->fooBar = $this->transformText($dummy);
}
/**
* @param string $dummy Some argument description
* @param array $options
*
* @return string|null Transformed input
*
* @throws \RuntimeException
*/
private function transformText($dummy, array $options = array())
{
$mergedOptions = array_merge(
array(
'some_default' => 'values',
'another_default' => 'more values',
),
$options
);
if (true === $dummy) {
return;
}
if ('string' === $dummy) {
if ('values' === $mergedOptions['some_default']) {
return substr($dummy, 0, 5);
}
return ucwords($dummy);
}
throw new \RuntimeException(sprintf('Unrecognized dummy option "%s"', $dummy));
}
}
==
, &&
, ...);return
statements, unless the return is alone
inside a statement-group (like an if
statement);Abstract
.Interface
;Trait
;Exception
;sylius
as first group;service_name.class
convention.@return
tag if the method does not return anything;@package
and @subpackage
annotations are not used.This document describes coding standards and conventions used in the Sylius codebase to make it more consistent and predictable.
When an object has a “main” many relation with related “things” (objects, parameters, ...), the method names are normalized:
get()
set()
has()
all()
replace()
remove()
clear()
isEmpty()
add()
register()
count()
keys()
The usage of these methods are only allowed when it is clear that there is a main relation:
CookieJar
has many Cookie
objects;Container
has many services and many parameters (as services
is the main relation, the naming convention is used for this relation);Input
has many arguments and many options. There is no “main”
relation, and so the naming convention does not apply.For many relations where the convention does not apply, the following methods
must be used instead (where XXX
is the name of the related thing):
Main Relation | Other Relations |
---|---|
get() |
getXXX() |
set() |
setXXX() |
n/a | replaceXXX() |
has() |
hasXXX() |
all() |
getXXXs() |
replace() |
setXXXs() |
remove() |
removeXXX() |
clear() |
clearXXX() |
isEmpty() |
isEmptyXXX() |
add() |
addXXX() |
register() |
registerXXX() |
count() |
countXXX() |
keys() |
n/a |
Примечание
While “setXXX” and “replaceXXX” are very similar, there is one notable difference: “setXXX” may replace, or add new elements to the relation. “replaceXXX”, on the other hand, cannot add new elements. If an unrecognized key is passed to “replaceXXX” it must throw an exception.
Предупреждение
Sylius is the pre-alpha development stage. We release minor version before every larger change, but be prepared for BC breaks to happen until 1.0.0 release.
From time to time, some classes and/or methods are deprecated in the framework; that happens when a feature implementation cannot be changed because of backward compatibility issues, but we still want to propose a “better” alternative. In that case, the old implementation can simply be deprecated.
A feature is marked as deprecated by adding a @deprecated
phpdoc to
relevant classes, methods, properties, ...:
/**
* @deprecated Deprecated since version 1.X, to be removed in 1.Y. Use XXX instead.
*/
The deprecation message should indicate the version when the class/method was deprecated, the version when it will be removed, and whenever possible, how the feature was replaced.
A PHP E_USER_DEPRECATED
error must also be triggered to help people with
the migration starting one or two minor versions before the version where the
feature will be removed (depending on the criticality of the removal):
trigger_error(
'XXX() is deprecated since version 2.X and will be removed in 2.Y. Use XXX instead.',
E_USER_DEPRECATED
);
This document explains some conventions and specificities in the way we manage the Sylius code with Git.
Whenever a pull request is merged, all the information contained in the pull request is saved in the repository.
You can easily spot pull request merges as the commit message always follows this pattern:
merged branch USER_NAME/BRANCH_NAME (PR #1111)
The PR reference allows you to have a look at the original pull request on GitHub: https://github.com/Sylius/Sylius/pull/1111. Often, this can help understand what the changes were about and the reasoning behind the changes.
Sylius is released under the MIT license.
According to Wikipedia:
“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.”
Copyright (c) 2011-2014 Paweł Jędrzejewski
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.
We have very friendly community which provides support for all Sylius users seeking help!
There are 2 channels available on Freenode IRC network, where you can meet other Sylius developers, ask for help or discuss ideas.
Channel #sylius is for Sylius users - a place where you should seek help and advices. Usually you can meet there several people eager to provide your with community support and answer your questions.
We invite everyone interested in using Sylius to this room, we warmly welcome new people in our growing community!
sylius-dev is the channel where you’ll most likely meet Sylius core team and contributors. It is the right place to discuss about Pull Requests, ideas and architecture. It will be perfect starting point if you want to contribute to the project or chat about concept you’d like to introduce at Sylius.
Now the best part! You do not need to be a Symfony2 guru to help! There is plenty of work which can be done by people starting with Symfony, and to us, every contribution is invaluable!
You should pick a nice username and connect to irc.freenode.org
via XChat or any other IRC client. You can also use web client.
We encourage asking Sylius related questions on the stackoverflow.com platform. Be sure to tag them with sylius tag - it will make it easier to find for people who can answer it.
To view all Sylius related questions - visit this link. You can also search for phrase.
You can always check sylius.org/community to see an overview of our community at work!
Below, you can find more useful links:
Documentation is as important as 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 more.
Before contributing, you need to become familiar with the markup language used by the documentation.
The Sylius documentation is hosted on GitHub:
https://github.com/Sylius/Sylius-Docs
If you want to submit a patch, fork the official repository on GitHub and then clone your fork:
$ git clone git://github.com/YOURUSERNAME/Sylius-Docs.git
The master
branch holds the documentation for the development branch of the code.
Create a dedicated branch for your changes (for organization):
$ git checkout -b improving_foo_and_bar
You can now make your changes directly to this branch and commit them. When you’re done, push this branch to your GitHub fork and initiate a pull request.
Following the example, the pull request will default to be between your
improving_foo_and_bar
branch and the Sylius-Docs
master
branch.
GitHub covers the topic of pull requests in detail.
Примечание
The Sylius documentation is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License.
You can also 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.[WCM]
(Waiting Code Merge) is used when you’re documenting a new feature
or change that hasn’t been accepted yet into the core code. The pull request
will not be merged until it is merged in the core code (or closed if the
change is rejected).Unless you’re fixing some minor typos, the pull request description must include the following checklist to ensure that contributions may be reviewed without needless feedback loops and that your contributions can be included into the documentation as quickly as possible:
| Q | A
| ------------- | ---
| Doc fix? | [yes|no]
| New docs? | [yes|no] (PR # on Sylius/Sylius if applicable)
| Fixed tickets | [comma separated list of tickets fixed by the PR]
An example submission could now look as follows:
| Q | A
| ------------- | ---
| Doc fix? | yes
| New docs? | yes (Sylius/Sylius#1250)
| Fixed tickets | #1075
Совет
Online documentation is rebuilt on every code-push to github.
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.1
The ``getProductDiscount`` method was introduced in Sylius 1.1.
All documentation in the Sylius Documentation should follow the documentation standards.
The most easy contribution you can make is reporting issues: a typo, a grammar mistake, a bug in a code example, a missing explanation, and so on.
Steps:
Read the dedicated document
.
The Sylius documentation uses reStructuredText as its markup language and Sphinx for building the output (HTML, PDF, ...).
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 or by reading the reStructuredText Primer on the Sphinx website.
If you are familiar with Markdown, be careful as things are sometimes very similar but different:
``like this``
).Sphinx is a build system that adds some nice tools to create documentation from reStructuredText documents. As such, it adds new directives and interpreted text roles to standard reST markup.
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(); ?>
Примечание
A list of supported languages is available on the Pygments website.
Whenever you show a configuration, you must use the configuration-block
directive to show the configuration in all supported configuration formats
(PHP
, YAML
, and XML
)
.. configuration-block::
.. code-block:: yaml
# Configuration in YAML
.. code-block:: xml
<!-- Configuration in XML //-->
.. code-block:: php
// Configuration in PHP
The previous reST snippet renders as follow:
# Configuration in YAML
<!-- Configuration in XML //-->
// Configuration in PHP
The current list of supported formats are the following:
Markup format | Displayed |
---|---|
html | HTML |
xml | XML |
php | PHP |
yaml | YAML |
jinja | Twig |
html+jinja | Twig |
html+php | PHP |
ini | INI |
php-annotations | Annotations |
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:`/bundles/SyliusAddressingBundle/installation`
The link text will be the main heading of the document linked to. You can also specify alternative text for the link:
:doc:`Simple CRUD </bundles/SyliusResourceBundle/installation>`
You can also add links to the API documentation:
:namespace:`Sylius\\Bundle\\CoreBundle`
:class:`Sylius\\Bundle\\CoreBundle\\Model\\Order`
:method:`Sylius\\Bundle\\AddressingBundle\\Matcher\\ZoneMatcher::match`
and to the PHP documentation:
:phpclass:`SimpleXMLElement`
:phpmethod:`DateTime::createFromFormat`
:phpfunction:`iterator_to_array`
To test documentation before a commit:
make html
and view the generated HTML in the build
directory.sensio
directory to the _exts
folder under your source
folder (where conf.py
is located)conf.py
file:# ...
sys.path.append(os.path.abspath('_exts'))
# adding PhpLexer
from sphinx.highlighting import lexers
from pygments.lexers.web import PhpLexer
# ...
# add the extensions to the list of extensions
extensions = [..., 'sensio.sphinx.refinclude', 'sensio.sphinx.configurationblock', 'sensio.sphinx.phpcode']
# enable highlighting for PHP code not between ``<?php ... ?>`` by default
lexers['php'] = PhpLexer(startinline=True)
lexers['php-annotations'] = PhpLexer(startinline=True)
lexers['php-standalone'] = PhpLexer(startinline=True)
lexers['php-symfony'] = PhpLexer(startinline=True)
# use PHP as the primary domain
primary_domain = 'php'
# set URL for API links
api_url = 'http://api.sylius.org/master/%s'
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.
=
, level 2 -
, level 3 ~
, level 4 .
and level 5 "
;::
shorthand is preferred over .. code-block:: php
to begin a PHP
code block (read the Sphinx documentation to see when you should use the
shorthand);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`: http://docs.sylius.org/en/latest/contributing/documentation/standards.html
...
in a comment at the point
of the fold. These comments are: // ...
(php), # ...
(yaml/bash), {# ... #}
(twig), <!-- ... -->
(xml/html), ; ...
(ini), ...
(text);...
(without comment)
at the place of the fold;...
If you fold only part of a line: the description can be placed before the line;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;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;$
in front of every bash line.Configuration examples should show recommended formats using configuration blocks. The recommended formats (and their orders) are:
// src/Foo/Bar.php
namespace Foo;
use Acme\Demo\Cat;
// ...
class Bar
{
// ...
public function foo($bar)
{
// set foo with a value of bar
$foo = ...;
$cat = new Cat($foo);
// ... check if $bar has the correct value
return $cat->baz($bar, ...);
}
}
Осторожно
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'}
).
For sections, use the following capitalization rules: Capitalization of the first word, and all other words, except for closed-class words:
The Vitamins are in my Fresh California Raisins
Do not use Serial (Oxford) Commas;
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
The Sylius documentation is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License.
You are free:
Under the following conditions:
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).