Sylius v1.0.0 Released! Try it out now!

Blog

Welcome to our blog, where we share news related to Sylius and post about technology & eCommerce.

Simpler CRUD for Symfony2

As you should already know, Sylius is constructed from fully decoupled bundles. They are all connected together inside the core, powering a standard webshop application. Every aspect of our e-commerce platform comes from a standalone component and you can use these components in your own Symfony2 project.

For example, if you have a book catalogue application, you could integrate the cart bundle to introduce shopping feature for the users, based on your existing books collection.

Despite this separation of concerns, functionality like model persistence or CRUD actions is common for all bundles. Initially, every model had its' own controller, or even 2 controllers, one for backend and another for frontend. Additionally, we had 1 manager and 1 manipulator class per entity, which was tremendous amount of duplicated code in every bundle.

I wanted to tackle several problems at once.

  • Removing tons of duplicated code in controllers for basic CRUD actions.
  • Removing the manager and manipulator classes, relying on Doctrine instead.
  • Removing the "frontend" and "backend" controllers.
  • Getting rid of very simple actions just to modify the sorting or filtering.
  • Supporting different persistence layers, not only Doctrine ORM, but with minimal effort.
  • Make the controllers format agnostic. (API)

To achieve this, I created SyliusResourceBundle.

Installation and configuration

Use the following command to add the bundle to your composer.json and download the package.

$ composer require sylius/resource-bundle:*

Adding required bundles to the kernel

<?php

// app/AppKernel.php

public function registerBundles()
{
    $bundles = array(
        new FOS\RestBundle\FOSRestBundle(),
        new JMS\SerializerBundle\JMSSerializerBundle($this),
        new WhiteOctober\PagerfantaBundle\WhiteOctoberPagerfantaBundle(),
        new Sylius\Bundle\ResourceBundle\SyliusResourceBundle(),
    );
}

Registering model as resource

# app/config/config.yml

sylius_resource:
    resources:
        acme.user:
            driver: doctrine/orm
            templates: AcmeShopBundle:User
            classes:
                model: Acme\ShopBundle\Entity\User

With this configuration, the bundle registers several services, useful for managing the User entity.

Controller

acme.controller.user service is now available! It is an instance of the generic ResourceController class. It works on top of the excellent FOSRestBundle.

It comes with a set of very basic, but extremely customizable, CRUD actions.

  • showAction for displaying a single user. By default it searches by the id, but it can be easily customized.
  • indexAction for retrieving a collection of users. Supports flat and paginated lists, sorting and basic filtering.
  • createAction for creating a new user.
  • updateAction for editing an existing user.
  • deleteAction for deleting an user.

Some examples...

To get a single user instance and render AcmeShopBundle:User:show.html.twig template, define the following route.

# app/config/routing.yml

acme_user_show:
    pattern: /users/{id}
    methods: [GET]
    defaults:
        _controller: acme.controller.user:showAction

Pretty simple, but let's display only enabled users, using their username and render a custom template.

# app/config/routing.yml

acme_profile_show:
    pattern: /profile/{username}
    methods: [GET]
    defaults:
        _controller: acme.controller.user:showAction
        _sylius:
            template: AcmeShopBundle:Profile:show.html.twig
            criteria: { username: $username, enabled: true }

What if you need to customize the query, add some joins to optimize the performance? Let us use a custom repository action.

# app/config/routing.yml

acme_profile_show:
    pattern: /profile/{username}
    methods: [GET]
    defaults:
        _controller: acme.controller.user:showAction
        _sylius:
            template: AcmeShopBundle:Profile:show.html.twig
            method: findOneForProfilePage
            arguments: [$username]

Now, let's assume we want to list all disabled accounts, as a paginated list.

# app/config/routing.yml

acme_user_disabled:
    pattern: /users/disabled
    methods: [GET]
    defaults:
        _controller: acme.controller.user:indexAction
        _sylius:
            template: AcmeShopBundle:User:disabled.html.twig
            criteria: { enabled: false }

The bundle uses Pagerfanta library, and passes paginator instance as users variable in the template.

What if you want only five recently registered users, without pagination? indexAction supports flat list as well.

# app/config/routing.yml

acme_user_recently_registered:
    pattern: /users/recently-registered
    methods: [GET]
    defaults:
        _controller: acme.controller.user:indexAction
        _sylius:
            template: AcmeShopBundle:User:recentlyRegistered.html.twig
            criteria: { enabled: true }
            sorting: { createdAt: desc }
            paginate: false
            limit: 5

Thanks to the flexibility of Symfony, you can render routes as blocks and embed such list on another page. (example)

Just like for the showAction, you can use custom repository method and arguments.

createAction, updateAction and deleteAction represent similar level of flexibility, you can use custom form types per action and much more.

# app/config/routing.yml

acme_user_update_addresses:
    pattern: /users/{id}/update-addresses
    methods: [GET]
    defaults:
        _controller: acme.controller.user:updateAction
        _sylius:
            template: AcmeShopBundle:User:updateAddresses.html.twig
            form: acme_user_addresses # 'acme_user' type is default.

There is a lot of other possibilities, you can find out more by checking out the documentation.

The bundle dispatches a set of very useful events for every action and is format agnostic, thanks to the FOSRestBundle it can also serve json and xml responses. You can customize the redirection after creating/updating/deleting resources or even add your own actions.

Manager and Repository

Except the controller, acme.manager.user is registered, but it is only an alias to the real ObjectManager service. (EntityManager for Doctrine ORM, or DocumentManager for MongoDB ODM) You can safely use it, but it's not a requirement.

Additionally, the entity repository class is changed to a slightly customized EntityRepository (or DocumentRepository). It is available as acme.repository.user service.

It contains two extra methods, the createNew() and createPaginator() functions. First returns a new instance of the entity. The second method creates a Pagerfanta instance for specific criteria and sorting.

Overriding guide

The controller and repository classes are configurable, you can define them next to the model setting.

# app/config/config.yml

sylius_resource:
    resources:
        acme.user:
            driver: doctrine/orm
            templates: AcmeShopBundle:User
            classes:
                model: Acme\ShopBundle\Entity\User
                repository: Acme\ShopBundle\Entity\UserRepository
                controller: Acme\ShopBundle\Controller\UserController

With such changes, the app.repository.user and app.controller.user services will use your own classes.

Final thoughts

When implementing this bundle, I was a bit worried that I'm giving too much power to the routing ... but I got convinced when I realized how easy it is to manage the models, add new actions and features.

All bundles are using this technique - everything mentioned above is possible in Sylius, which makes the customization process very simple.

The bundle supports Doctrine ORM and Doctrine MongoDB ODM, thanks to user contributions.

Please leave your thoughts in the comments section below. Thank you!


More from our blog

#SyliusOneZero Contest Results

Time to announce #SyliusOneZero Contest Results. Read the blog post to find out who wins and how great our community has reacted to the Stable Release.

Sylius v1.0.0 Released

We did it folks! We are excited to announce the availability of the first stable release of Sylius! This milestone is an amazing achievement of our community. We believe it will make eCommerce developers enjoy their work again.

A Month of Sylius #2 (August 2017)

We’re pleased to invite you to our second monthly summary. A lot has happened and even more is going to happen as we’re just about to release the first stable version of our framework.

Extending Sylius with Plugins & Bundles

We are approaching our first Stable Release and it is high time to publish an official listing of Plugins & Bundles which you can use to extend standard Sylius functionality.

Want to boost your business with Sylius?

CONTACT US