How We Moved Away from Spryker to Symfony
Asmir Mustafic
Berlin Symfony Meetup
July 2023
(and deleted 2M lines of code)
- Twitter: @goetas_asmir
- Mastodon: @goetas@phpc.social
- LinkedIn: @goetas
- WWW: goetas.com
- 1+ bln known parts
- 10m-40m pre-know sell-able parts
- ~10-15 people in the main team
What is Spryker?
Project A
Why Spryker?
- It is PHP
- You will find an agency in Berlin
- Some built-in stuff
- Built on industry knowledge acquired in years
- Standardized style
Show time (1/2)
Why not Spryker?
- Agencies are not cheap
- Devs are rare
- Bloated
- New framework to learn
- Not invented here syndrome
- Questionable dependency injection
- Questionable performance
- Questionable architecture
- Application guidelines are over-engineered
- Code code code code code code code code
Why not Spryker for us?
- Did not scale (scalability issues with ~20k parts)
- In 2017-2018 was not that mature
- We had an uncommon UX for the feature available at that time
- Did not talk well with other systems (poor apis)
- Not agile? (app guidelines too strict)
- Very few developers in US
- No need for dynamic translations (big performance hit)
- No need for CMS (too simple)
How we wanted to do it
- No feature freeze
- No maintenance screen
- No dedicated teams
- No green field
-
No blockers for other features
Technical Goals
- Symfony standard project
- Any Symfony dev should be able to contribute easily
- Stop not-invented-here syndrome
- Inherit most of the advantages of using symfony
- bundles, components
- stack overflow
- community of developers to hire
- fun
What was migrated already
How to move away from it?
Show time (2/2)
Steps
- Wrap stuff in Symfony
- Build new services in Symfony
- Share services between Yves and Symfony Yves
- Repeat until everything is in Symfony
<?php
class RequestHandler
{
public function __construct(ContainerInterface $container, string $cacheDir)
{
$this->container = $container;
self::$staticContainer = $container;
}
public static function getContainer(): ContainerInterface
{
return self::$staticContainer;
}
public function getBootstrap(): YvesBootstrap
{
if ($this->bootstrap) return $this->bootstrap;
return $this->bootstrap = new YvesBootstrap();
}
public function getApplication(): Application
{
if ($this->app) return $this->app;
$this->app = $this->getBootstrap()->boot();
$this->app->boot();
return $this->app;
}
public function handle(Request $request): Response
{
return $this->getApplication()->handle($request->duplicate());
}
}
<?php
class SearchApiFactory extends AbstractFactory // Spryker app service
{
/**
* @return \Pyz\Client\SearchApi\Parts\PartsAutocompletionSearch
*/
public function createPartsAutocompleteSearch(): PartsAutocompletionSearch
{
return new PartsAutocompletionSearch(
SprykerFacade::getContainer()->get('guzzle_client'),
$this->getConfig()
);
}
}
<?php
class CategoryFinder // Symfony app service
{
public function __construct(SprykerFacade $spryker)
{
$sprkerFormFactory = $spryker->getApplication()->get('form.factory');
}
}
<?php
class ServiceControllerResolver extends AbstractControllerResolver
{
protected function getResolvedClassInstance()
{
$container = SprykerFacade::getContainer();
$resolvedClassName = $this->resolvedClassName();
if ($container->has($resolvedClassName)) {
return $container->get($resolvedClassName);
}
return parent::getResolvedClassInstance();
}
}
Symfony controller autowiring
services:
_defaults:
autowire: true
autoconfigure: true
public: false
Pyz\Yves\Customer\Controller\AddressBookController:
public: true
<?php
class AddressBookController extends AbstractCustomerController
{
public function __construct(
AddressRepository $addressRepository,
AccountRepository $accountRepository
) {
$this->addressRepository = $addressRepository;
$this->accountRepository = $accountRepository;
}
}
Symfony controller autowiring
Steps
- Create new API application
- create doctrine entities based on db schema
- Replace any call from Yves to Zed with calls to new API
- Repeat until no API calls are done to Zed
- Api can still tak to Zed for complex stuff (state machine)
- Frontend can call API directly to avoid Yves proxy for no reason
Steps
- Move all the emails in the new system
- Have Spryker sending emails by calling the new system
Steps
- Create backend UI system (Sonata admin)
- Re implement everything it is use in Zed backend UI
- Talk to zed for complex stuff (state machine)
Steps
- Move state machine into new code base
State Machine (before)
State Machine (migrated)
Symfony workflow component
Done
(delete leftovers)
Was it worth ?
YES
start development
mvp release
introduction to symfony
introduction to wiliam
introduction to
xyla
removed zed
end of spryker
me
Current state of the project
- 3 developers less
- 1 more project
- no vendor lock-in
(we are not looking for spryker experience anymore) - simpler to reason about
Thank you!
- Twitter: @goetas_asmir
- Github: @goetas
- LinkedIn: @goetas
- WWW: goetas.com
How We Moved Away from Spryker to Symfony
By Asmir Mustafic
How We Moved Away from Spryker to Symfony
- 445