Skip to main content

Migrating from Monofony to Sylius Stack

· 3 min read
Stephan Hochdörfer
Head of IT Business Operations

Last year, we built a project with Monofony, a simple and flexible plugin built on top of the Sylius e-commerce platform. When Sylius Stack was announced, I decided to see if we could easily migrate to Sylius Stack.

While there may be a future version of Monofony supporting Sylius 2.0 and Sylius Stack, I decided I did not want to wait and test whether migrating from Monofony to Sylius Stack was feasible for us. I feared that if we don't make the switch now, we may never do it. And since Monofony was very much tied to the Sylius notion of customers and admin users, it felt a bit odd for our use case.

I decided against a step-by-step migration. The application we built wasn't that big, a big-bang upgrade was okay for me.

So, I started setting up a new Symfony project from scratch, installing and configuring the API Platform and all the Sylius Stack components we needed.

Once everything was running fine, I added user authentication for the Admin panel and the frontend. For this, I copied the admin user and merchant entities from the Monofony application and made some adjustments. The adjustments were mainly to switch to annotations to configure the resources. As an example, this is how the Admin user entity looks like:

<?php

declare(strict_types=1);

namespace App\Entity\Admin;

use App\Form\Type\Admin\UserType;
use App\Grid\Admin\UserGrid;
use App\Repository\Admin\UserRepository;
use Doctrine\ORM\Mapping as ORM;
use Sylius\Component\User\Model\UserInterface;
use Sylius\Resource\Metadata\AsResource;
use Sylius\Resource\Metadata\BulkDelete;
use Sylius\Resource\Metadata\Create;
use Sylius\Resource\Metadata\Delete;
use Sylius\Resource\Metadata\Index;
use Sylius\Resource\Metadata\Update;

#[AsResource(section: 'admin', formType: UserType::class, templatesDir: '@SyliusAdminUi/crud', routePrefix: 'admin')]
#[Index(grid: UserGrid::class)]
#[Create(formType: UserType::class, redirectToRoute: 'sylius_admin_admin_user_index')]
#[BulkDelete(repositoryArguments: ['ids' => "request.get('ids')"], repositoryMethod: 'findByIds', redirectToRoute: 'sylius_admin_admin_user_index')]
#[Update]
#[Delete]
#[ORM\Entity(repositoryClass: UserRepository::class)]
#[ORM\Table(name: 'admin_user')]
#[ORM\UniqueConstraint(name: 'UNIQ_ADMN_EMAIL', fields: ['email'])]
#[ORM\UniqueConstraint(name: 'UNIQ_ADMN_USERNAME', fields: ['username'])]
class User implements UserInterface
{
// properties here...
}

In the same process, I also copied the Admin user form type and the Admin user repository. For the Grid configuration, I decided to switch from the YAML declaration to a PHP class style declaration which looks like this:

<?php

declare(strict_types=1);

namespace App\Grid\Admin;

use App\Entity\Admin\User;
use Sylius\Bundle\GridBundle\Builder\Action\CreateAction;
use Sylius\Bundle\GridBundle\Builder\Action\DeleteAction;
use Sylius\Bundle\GridBundle\Builder\Action\UpdateAction;
use Sylius\Bundle\GridBundle\Builder\ActionGroup\BulkActionGroup;
use Sylius\Bundle\GridBundle\Builder\ActionGroup\ItemActionGroup;
use Sylius\Bundle\GridBundle\Builder\ActionGroup\MainActionGroup;
use Sylius\Bundle\GridBundle\Builder\Field\StringField;
use Sylius\Bundle\GridBundle\Builder\GridBuilderInterface;
use Sylius\Bundle\GridBundle\Grid\AbstractGrid;
use Sylius\Bundle\GridBundle\Grid\ResourceAwareGridInterface;

final class UserGrid extends AbstractGrid implements ResourceAwareGridInterface
{
public static function getName(): string
{
return 'sylius_admin_admin_user';
}

public function buildGrid(GridBuilderInterface $gridBuilder): void
{
$gridBuilder
->addField(
StringField::create('username')
->setLabel('sylius.grid.admin_user.username')
->setSortable(true),
)
->addField(
StringField::create('email')
->setLabel('sylius.grid.admin_user.email')
->setSortable(true),
)
->addActionGroup(
MainActionGroup::create(
CreateAction::create(),
),
)
->addActionGroup(
ItemActionGroup::create(
UpdateAction::create(),
DeleteAction::create(),
),
)
->addActionGroup(
BulkActionGroup::create(
DeleteAction::create(),
),
);
}

public function getResourceClass(): string
{
return User::class;
}
}

Once I got everything working, I started migrating the API Platform-specific logic over. Upgrading from API Platform 3.x to 4.x resulted in a few adaptations.

When the Admin panel was fully functional, I transitioned to the Merchant panel, which requires a separate login and offers distinct features. The most challenging aspect was grasping the functionality of the Twig Hooks component and replicating the Admin panel's UI for the Merchant panel without excessive duplication of templates.

Overall, I am extremely happy with the end result. The migration took a lot longer than I expected, mainly because I needed to understand what Monofony does under the hood and how to port that over to Sylius Stack.