4 min. read
Categories:
Simpler CRUD for Symfony2
Learn what resource management mechanisms are powering Sylius and how you can use them in your next Symfony2 project.
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 FOSRestBundleFOSRestBundle(),
        new JMSSerializerBundleJMSSerializerBundle($this),
        new WhiteOctoberPagerfantaBundleWhiteOctoberPagerfantaBundle(),
        new SyliusBundleResourceBundleSyliusResourceBundle(),
    );
}

Registering model as resource

# app/config/config.yml

sylius_resource:
    resources:
        acme.user:
            driver: doctrine/orm
            templates: AcmeShopBundle:User
            classes:
                model: AcmeShopBundleEntityUser

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: AcmeShopBundleEntityUser
                repository: AcmeShopBundleEntityUserRepository
                controller: AcmeShopBundleControllerUserController

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!

Tags:
Share:
Paweł Jędrzejewski
Self-taught developer and entrepreneur, who has built an entire career and business through Open Source technology. Speaker at international conferences. PHPers meetups co-organizer. Keen on helping other young entrepreneurs and companies who may want to follow a similar path.
More from our blog
Cloud 4 min read 17.06.2024
We are thrilled to announce that we just signed a strategic partnership with Platform.sh, and as a result, we are extending our offer with Sylius Cloud powered by Platform.sh. Platform.sh is a modern Platform-as-a-Service (PaaS) solution that allows businesses to leverage the cloud environment without losing access to the code… Read More
Technical 4 min read 11.06.2024
Abstract 1.12 released in Q4 2022 1.13 on Apr 23rd, 2024 (a year later than we anticipated while releasing 1.12) 3859 commits 23 contributors A stabilized Sylius API powered by API Platform It’s been a long and bumpy road. Having it behind our backs was a highway that led Sylius… Read More
Business Ecosystem News 4 min read 06.06.2024
Welcome to the May summary! As an open-source eCommerce framework, Sylius continues to evolve with significant contributions from our vibrant community and valuable product updates. Apart from describing the technical changes, we will also quickly summarize the Sylius Technical Fundamentals & Sylius Polish Community Meetup and eCommerce Day Kaunas, as… Read More
Comments