SlideShare a Scribd company logo
1 of 67
Download to read offline
Sylius 2.0 New Features
Unveiling the Future
Łukasz Chruściel
Introduction
Timeline
v1.13 - April 2024
Symfony Work
fl
ow support
PriceHistory
Attributes & auto con
fi
guration
API not experimental anymore
v1.13 - April 2024
Symfony Work
fl
ow support
Symfony Work
fl
ow
Symfony Work
fl
ow
sylius_state_machine_abstraction:
default_adapter: symfony_workflow
Attributes
#[AsCommandDataTransformer]
#[AsDocumentationModi
fi
er]
#[AsPaymentCon
fi
gurationProvider]
Attributes in API
#[AsOrderProcessor(priority: 10)]
#[AsCartContext]
Attributes in OrderBundle
#[AsCatalogPromotionApplicatorCriteria]
#[AsCatalogPromotionPriceCalculator]
#[AsEntityObserver]
#[AsOrderItemUnitsTaxesApplicator]
#[AsOrderItemUnitsTaxesApplicator]
#[AsProductVariantMapProvider]
#[AsTaxCalculationStrategy]
#[AsUriBasedSectionResolver]
#[AsProductVariantMapProvider]
#[AsProductVariantMapProvider]
Attributes in CoreBundle
#[AsLocaleContext]
Attributes in LocaleBundle
#[AsCurrencyContext]
Attributes in CurrencyBundle
#[AsProductVariantResolver]
Attributes in ProductBundle
#[AsTaxCalculator]
Attributes in TaxationBundle
#[AsShippingCalculator]
#[AsShippingMethodResolver]
#[AsShippingMethodRuleChecker]
#[AsShippingCalculator]
Attributes in ShippingBundle
#[AsPromotionAction]
#[AsPromotionCouponEligibilityChecker]
#[AsPromotionEligibilityChecker]
#[AsPromotionRuleChecker]
Attributes in PromotionBundle
#[AsAttributeType]
Attributes in AttributeBundle
#[AsChannelContext]
#[AsRequestBasedChannelResolver]
Attributes in ChannelBundle
v1.14 - Autumn 2024
v1.14 - Autumn 2024
2.0 Compatibility layer
Resource
Attributes
Again
#[AsResource]
Operations
#[Index]
#[Show]
#[Create]
...
#[SyliusCrudRoutes]
New Controller
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace SyliusBundleResourceBundleController;
use DoctrinePersistenceObjectManager;
use FOSRestBundleViewView;
use SyliusBundleResourceBundleEventResourceControllerEvent;
use SyliusComponentResourceExceptionDeleteHandlingException;
use SyliusComponentResourceExceptionUpdateHandlingException;
use SyliusComponentResourceResourceActions;
use SyliusResourceDoctrinePersistenceRepositoryInterface;
use SyliusResourceFactoryFactoryInterface;
use SyliusResourceMetadataMetadataInterface;
use SyliusResourceModelResourceInterface;
use SymfonyComponentDependencyInjectionContainerAwareTrait;
use SymfonyComponentDependencyInjectionContainerInterface;
use SymfonyComponentHttpFoundationRequest;
use SymfonyComponentHttpFoundationResponse;
use SymfonyComponentHttpKernelExceptionBadRequestHttpException;
use SymfonyComponentHttpKernelExceptionHttpException;
use SymfonyComponentHttpKernelExceptionNotFoundHttpException;
use SymfonyComponentSecurityCoreExceptionAccessDeniedException;
class ResourceController
{
use ControllerTrait;
use ContainerAwareTrait;
protected MetadataInterface $metadata;
protected RequestConfigurationFactoryInterface $requestConfigurationFactory;
protected ?ViewHandlerInterface $viewHandler;
protected RepositoryInterface $repository;
protected FactoryInterface $factory;
protected NewResourceFactoryInterface $newResourceFactory;
protected ObjectManager $manager;
protected SingleResourceProviderInterface $singleResourceProvider;
protected ResourcesCollectionProviderInterface $resourcesCollectionProvider;
protected ResourceFormFactoryInterface $resourceFormFactory;
protected RedirectHandlerInterface $redirectHandler;
protected FlashHelperInterface $flashHelper;
protected AuthorizationCheckerInterface $authorizationChecker;
protected EventDispatcherInterface $eventDispatcher;
protected ?StateMachineInterface $stateMachine;
protected ResourceUpdateHandlerInterface $resourceUpdateHandler;
protected ResourceDeleteHandlerInterface $resourceDeleteHandler;
public function __construct(
MetadataInterface $metadata,
RequestConfigurationFactoryInterface $requestConfigurationFactory,
?ViewHandlerInterface $viewHandler,
RepositoryInterface $repository,
FactoryInterface $factory,
NewResourceFactoryInterface $newResourceFactory,
ObjectManager $manager,
SingleResourceProviderInterface $singleResourceProvider,
ResourcesCollectionProviderInterface $resourcesFinder,
ResourceFormFactoryInterface $resourceFormFactory,
RedirectHandlerInterface $redirectHandler,
FlashHelperInterface $flashHelper,
AuthorizationCheckerInterface $authorizationChecker,
EventDispatcherInterface $eventDispatcher,
?StateMachineInterface $stateMachine,
ResourceUpdateHandlerInterface $resourceUpdateHandler,
ResourceDeleteHandlerInterface $resourceDeleteHandler,
) {
$this->metadata = $metadata;
$this->requestConfigurationFactory = $requestConfigurationFactory;
$this->viewHandler = $viewHandler;
$this->repository = $repository;
$this->factory = $factory;
$this->newResourceFactory = $newResourceFactory;
$this->manager = $manager;
$this->singleResourceProvider = $singleResourceProvider;
$this->resourcesCollectionProvider = $resourcesFinder;
$this->resourceFormFactory = $resourceFormFactory;
$this->redirectHandler = $redirectHandler;
$this->flashHelper = $flashHelper;
$this->authorizationChecker = $authorizationChecker;
$this->eventDispatcher = $eventDispatcher;
$this->stateMachine = $stateMachine;
$this->resourceUpdateHandler = $resourceUpdateHandler;
$this->resourceDeleteHandler = $resourceDeleteHandler;
}
public function showAction(Request $request): Response
{
$configuration = $this->requestConfigurationFactory->create($this->metadata,
$request);
$this->isGrantedOr403($configuration, ResourceActions::SHOW);
$resource = $this->findOr404($configuration);
$event = $this->eventDispatcher->dispatch(ResourceActions::SHOW, $configuration,
$resource);
$eventResponse = $event->getResponse();
if (null !== $eventResponse) {
return $eventResponse;
}
if ($configuration->isHtmlRequest()) {
return $this->render($configuration->getTemplate(ResourceActions::SHOW .
'.html'), [
'configuration' => $configuration,
'metadata' => $this->metadata,
'resource' => $resource,
$this->metadata->getName() => $resource,
]);
}
return $this->createRestView($configuration, $resource);
}
public function indexAction(Request $request): Response
{
$configuration = $this->requestConfigurationFactory->create($this->metadata,
}
return $this->createRestView($configuration, $resources);
}
public function createAction(Request $request): Response
{
$configuration = $this->requestConfigurationFactory->create($this->metadata,
$request);
$this->isGrantedOr403($configuration, ResourceActions::CREATE);
$newResource = $this->newResourceFactory->create($configuration, $this->factory);
$form = $this->resourceFormFactory->create($configuration, $newResource);
$form->handleRequest($request);
if ($request->isMethod('POST') && $form->isSubmitted() && $form->isValid()) {
$newResource = $form->getData();
$event = $this->eventDispatcher->dispatchPreEvent(ResourceActions::CREATE,
$configuration, $newResource);
if ($event->isStopped() && !$configuration->isHtmlRequest()) {
throw new HttpException($event->getErrorCode(), $event->getMessage());
}
if ($event->isStopped()) {
$this->flashHelper->addFlashFromEvent($configuration, $event);
$eventResponse = $event->getResponse();
if (null !== $eventResponse) {
return $eventResponse;
}
return $this->redirectHandler->redirectToIndex($configuration,
$newResource);
}
if ($configuration->hasStateMachine()) {
$stateMachine = $this->getStateMachine();
$stateMachine->apply($configuration, $newResource);
}
$this->repository->add($newResource);
if ($configuration->isHtmlRequest()) {
$this->flashHelper->addSuccessFlash($configuration,
ResourceActions::CREATE, $newResource);
}
$postEvent = $this->eventDispatcher->dispatchPostEvent(ResourceActions::CREATE,
$configuration, $newResource);
if (!$configuration->isHtmlRequest()) {
return $this->createRestView($configuration, $newResource,
Response::HTTP_CREATED);
}
$postEventResponse = $postEvent->getResponse();
if (null !== $postEventResponse) {
return $postEventResponse;
}
return $this->redirectHandler->redirectToResource($configuration,
$newResource);
}
if ($request->isMethod('POST') && $form->isSubmitted() && !$form->isValid()) {
$responseCode = Response::HTTP_UNPROCESSABLE_ENTITY;
}
if (!$configuration->isHtmlRequest()) {
return $this->createRestView($configuration, $form,
Response::HTTP_BAD_REQUEST);
}
$initializeEvent = $this->eventDispatcher-
>dispatchInitializeEvent(ResourceActions::CREATE, $configuration, $newResource);
$initializeEventResponse = $initializeEvent->getResponse();
if (null !== $initializeEventResponse) {
return $initializeEventResponse;
}
return $this->render($configuration->getTemplate(ResourceActions::CREATE .
'.html'), [
'configuration' => $configuration,
'metadata' => $this->metadata,
'resource' => $newResource,
$this->metadata->getName() => $newResource,
'form' => $form->createView(),
], null, $responseCode ?? Response::HTTP_OK);
}
public function updateAction(Request $request): Response
{
$configuration = $this->requestConfigurationFactory->create($this->metadata,
$request);
$this->isGrantedOr403($configuration, ResourceActions::UPDATE);
$resource = $this->findOr404($configuration);
$form = $this->resourceFormFactory->create($configuration, $resource);
$form->handleRequest($request);
if (
in_array($request->getMethod(), ['POST', 'PUT', 'PATCH'], true) &&
$form->isSubmitted() &&
$form->isValid()
) {
$resource = $form->getData();
/** @var ResourceControllerEvent $event */
$event = $this->eventDispatcher->dispatchPreEvent(ResourceActions::UPDATE,
$configuration, $resource);
if ($event->isStopped() && !$configuration->isHtmlRequest()) {
throw new HttpException($event->getErrorCode(), $event->getMessage());
}
if ($event->isStopped()) {
$this->flashHelper->addFlashFromEvent($configuration, $event);
$eventResponse = $event->getResponse();
if (null !== $eventResponse) {
return $eventResponse;
}
return $this->redirectHandler->redirectToResource($configuration,
$resource);
}
try {
$this->resourceUpdateHandler->handle($resource, $configuration, $this-
>manager);
} catch (UpdateHandlingException $exception) {
if (!$configuration->isHtmlRequest()) {
return $this->createRestView($configuration, $form, $exception-
>getApiResponseCode());
return $this->createRestView($configuration, null,
Response::HTTP_NO_CONTENT);
}
$postEventResponse = $postEvent->getResponse();
if (null !== $postEventResponse) {
return $postEventResponse;
}
return $this->redirectHandler->redirectToResource($configuration, $resource);
}
if (in_array($request->getMethod(), ['POST', 'PUT', 'PATCH'], true) && $form-
>isSubmitted() && !$form->isValid()) {
$responseCode = Response::HTTP_UNPROCESSABLE_ENTITY;
}
if (!$configuration->isHtmlRequest()) {
return $this->createRestView($configuration, $form,
Response::HTTP_BAD_REQUEST);
}
$initializeEvent = $this->eventDispatcher-
>dispatchInitializeEvent(ResourceActions::UPDATE, $configuration, $resource);
$initializeEventResponse = $initializeEvent->getResponse();
if (null !== $initializeEventResponse) {
return $initializeEventResponse;
}
return $this->render($configuration->getTemplate(ResourceActions::UPDATE .
'.html'), [
'configuration' => $configuration,
'metadata' => $this->metadata,
'resource' => $resource,
$this->metadata->getName() => $resource,
'form' => $form->createView(),
], null, $responseCode ?? Response::HTTP_OK);
}
public function deleteAction(Request $request): Response
{
$configuration = $this->requestConfigurationFactory->create($this->metadata,
$request);
$this->isGrantedOr403($configuration, ResourceActions::DELETE);
$resource = $this->findOr404($configuration);
if ($configuration->isCsrfProtectionEnabled() && !$this->isCsrfTokenValid((string)
$resource->getId(), (string) $request->request->get('_csrf_token'))) {
throw new HttpException(Response::HTTP_FORBIDDEN, 'Invalid csrf token.');
}
$event = $this->eventDispatcher->dispatchPreEvent(ResourceActions::DELETE,
$configuration, $resource);
if ($event->isStopped() && !$configuration->isHtmlRequest()) {
throw new HttpException($event->getErrorCode(), $event->getMessage());
}
if ($event->isStopped()) {
$this->flashHelper->addFlashFromEvent($configuration, $event);
$eventResponse = $event->getResponse();
if (null !== $eventResponse) {
return $eventResponse;
}
return $this->redirectHandler->redirectToIndex($configuration, $resource);
}
try {
$this->resourceDeleteHandler->handle($resource, $this->repository);
} catch (DeleteHandlingException $exception) {
if (!$configuration->isHtmlRequest()) {
return $this->createRestView($configuration, null, $exception-
>getApiResponseCode());
}
$this->flashHelper->addErrorFlash($configuration, $exception->getFlash());
return $this->redirectHandler->redirectToReferer($configuration);
}
if ($configuration->isHtmlRequest()) {
$this->flashHelper->addSuccessFlash($configuration, ResourceActions::DELETE,
$resource);
}
$postEvent = $this->eventDispatcher->dispatchPostEvent(ResourceActions::DELETE,
$configuration, $resource);
if (!$configuration->isHtmlRequest()) {
return $this->createRestView($configuration, null, Response::HTTP_NO_CONTENT);
}
$postEventResponse = $postEvent->getResponse();
if (null !== $postEventResponse) {
return $postEventResponse;
}
return $this->redirectHandler->redirectToIndex($configuration, $resource);
}
public function bulkDeleteAction(Request $request): Response
{
$configuration = $this->requestConfigurationFactory->create($this->metadata,
$request);
$this->isGrantedOr403($configuration, ResourceActions::BULK_DELETE);
$resources = $this->resourcesCollectionProvider->get($configuration, $this-
>repository);
if (
$configuration->isCsrfProtectionEnabled() &&
!$this->isCsrfTokenValid(ResourceActions::BULK_DELETE, (string) $request-
>request->get('_csrf_token'))
) {
throw new HttpException(Response::HTTP_FORBIDDEN, 'Invalid csrf token.');
}
$this->eventDispatcher->dispatchMultiple(ResourceActions::BULK_DELETE,
$configuration, $resources);
foreach ($resources as $resource) {
$event = $this->eventDispatcher->dispatchPreEvent(ResourceActions::DELETE,
$configuration, $resource);
if ($event->isStopped() && !$configuration->isHtmlRequest()) {
throw new HttpException($event->getErrorCode(), $event->getMessage());
}
if ($event->isStopped()) {
$this->flashHelper->addFlashFromEvent($configuration, $event);
$eventResponse = $event->getResponse();
if (null !== $eventResponse) {
return $eventResponse;
}
if (!$configuration->isHtmlRequest()) {
return $this->createRestView($configuration, null, Response::HTTP_NO_CONTENT);
}
$this->flashHelper->addSuccessFlash($configuration, ResourceActions::BULK_DELETE);
if (isset($postEvent)) {
$postEventResponse = $postEvent->getResponse();
if (null !== $postEventResponse) {
return $postEventResponse;
}
}
return $this->redirectHandler->redirectToIndex($configuration);
}
public function applyStateMachineTransitionAction(Request $request): Response
{
$stateMachine = $this->getStateMachine();
$configuration = $this->requestConfigurationFactory->create($this->metadata,
$request);
$this->isGrantedOr403($configuration, ResourceActions::UPDATE);
$resource = $this->findOr404($configuration);
if ($configuration->isCsrfProtectionEnabled() && !$this->isCsrfTokenValid((string)
$resource->getId(), $request->get('_csrf_token'))) {
throw new HttpException(Response::HTTP_FORBIDDEN, 'Invalid CSRF token.');
}
$event = $this->eventDispatcher->dispatchPreEvent(ResourceActions::UPDATE,
$configuration, $resource);
if ($event->isStopped() && !$configuration->isHtmlRequest()) {
throw new HttpException($event->getErrorCode(), $event->getMessage());
}
if ($event->isStopped()) {
$this->flashHelper->addFlashFromEvent($configuration, $event);
$eventResponse = $event->getResponse();
if (null !== $eventResponse) {
return $eventResponse;
}
return $this->redirectHandler->redirectToResource($configuration, $resource);
}
if (!$stateMachine->can($configuration, $resource)) {
throw new BadRequestHttpException();
}
try {
$this->resourceUpdateHandler->handle($resource, $configuration, $this-
>manager);
} catch (UpdateHandlingException $exception) {
if (!$configuration->isHtmlRequest()) {
return $this->createRestView($configuration, $resource, $exception-
>getApiResponseCode());
}
$this->flashHelper->addErrorFlash($configuration, $exception->getFlash());
return $this->redirectHandler->redirectToReferer($configuration);
}
if ($configuration->isHtmlRequest()) {
$this->flashHelper->addSuccessFlash($configuration, ResourceActions::UPDATE,
$resource);
}
$postEvent = $this->eventDispatcher->dispatchPostEvent(ResourceActions::UPDATE,
$configuration, $resource);
if (!$configuration->isHtmlRequest()) {
if ($configuration->getParameters()->get('return_content', true)) {
return $this->createRestView($configuration, $resource, Response::HTTP_OK);
}
return $this->createRestView($configuration, null, Response::HTTP_NO_CONTENT);
}
$postEventResponse = $postEvent->getResponse();
if (null !== $postEventResponse) {
return $postEventResponse;
}
return $this->redirectHandler->redirectToResource($configuration, $resource);
}
/**
* @return mixed
*/
protected function getParameter(string $name)
{
if (!$this->container instanceof ContainerInterface) {
throw new RuntimeException(sprintf(
'Container passed to "%s" has to implements "%s".',
self::class,
ContainerInterface::class,
));
}
return $this->container->getParameter($name);
}
/**
* @throws AccessDeniedException
*/
protected function isGrantedOr403(RequestConfiguration $configuration, string
$permission): void
{
if (!$configuration->hasPermission()) {
return;
}
$permission = $configuration->getPermission($permission);
if (!$this->authorizationChecker->isGranted($configuration, $permission)) {
throw new AccessDeniedException();
}
}
/**
* @throws NotFoundHttpException
*/
protected function findOr404(RequestConfiguration $configuration): ResourceInterface
{
if (null === $resource = $this->singleResourceProvider->get($configuration, $this-
>repository)) {
throw new NotFoundHttpException(sprintf('The "%s" has not been found', $this-
>metadata->getHumanizedName()));
}
return $resource;
}
Old Architecture
ResourceController -> 582+456 LoC
New Controller
<?php
final class MainController
{
public function __construct(
private HttpOperationInitiatorInterface $operationInitiator,
private RequestContextInitiatorInterface $requestContextInitiator,
private ProviderInterface $provider,
private ProcessorInterface $processor,
) {
}
public function __invoke(Request $request): Response
{
$operation = $this->operationInitiator->initializeOperation($request);
if (null === $operation) {
throw new RuntimeException('Operation should not be null.');
}
$context = $this->requestContextInitiator->initializeContext($request);
if (null === $operation->canWrite()) {
$operation = $operation->withWrite(!$request->isMethodSafe());
}
$data = $this->provider->provide($operation, $context);
$valid = $request->attributes->getBoolean('is_valid', true);
if (!$valid) {
$operation = $operation->withWrite(false);
}
return $this->processor->process($data, $operation, $context);
}
}
Brand New Architecture
Providers
Processors
Factories
Responders
Metadata
Ground up rework
Fully backward compatible
Improved debugger
sylius:debug:resource
Grid
New
fl
avours
PHP Con
fi
g
<?php
return static function (GridConfig $grid) {
$grid->addGrid(
GridBuilder::create('app_ticket_php')
->addField(StringField::create('email'))
);
};
Service based
final class TicketGrid extends AbstractGrid
{
public static function getName(): string
{
return 'app_ticket';
}
public function buildGrid(GridBuilderInterface $gridBuilder): void
{
$gridBuilder
->addField(
StringField::create('email')
)
;
}
}
make:grid
Attributes
#[Index(grid: 'app_test')]
v2.0 - SyliusCon 2024
Payments
API-
fi
rst
Architecture
Messenger
Work
fl
ow
UI
Admin
Sylius TwigHooks
https://syliusdev-demobap.bunnyenv.com/admin/
Shop
API
Symfony compatibility
Symfony compatibility
Work
fl
ow vs WinzouStateMachine
Clock vs Sylius/Calendar
Symfony v7.0
New folder structure
src/
Addressing/
Model/
Symfony/
Doctrine/
Anything more?
Version compatibility
V1
V2
V1 V2
V1
Magento
Sylius
Release cycle
Symfony +1 year
Thank you!

More Related Content

Similar to Unveiling the Future: Sylius 2.0 New Features

Lift 2 0
Lift 2 0Lift 2 0
Lift 2 0
SO
 

Similar to Unveiling the Future: Sylius 2.0 New Features (20)

Silex meets SOAP & REST
Silex meets SOAP & RESTSilex meets SOAP & REST
Silex meets SOAP & REST
 
How I started to love design patterns
How I started to love design patternsHow I started to love design patterns
How I started to love design patterns
 
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you need
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you needDutch PHP Conference - PHPSpec 2 - The only Design Tool you need
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you need
 
Introduction to Zend Framework web services
Introduction to Zend Framework web servicesIntroduction to Zend Framework web services
Introduction to Zend Framework web services
 
Zend framework service
Zend framework serviceZend framework service
Zend framework service
 
Zend framework service
Zend framework serviceZend framework service
Zend framework service
 
Understanding backbonejs
Understanding backbonejsUnderstanding backbonejs
Understanding backbonejs
 
Apostrophe
ApostropheApostrophe
Apostrophe
 
Min-Maxing Software Costs
Min-Maxing Software CostsMin-Maxing Software Costs
Min-Maxing Software Costs
 
Using the Features API
Using the Features APIUsing the Features API
Using the Features API
 
Symfony CoP: Form component
Symfony CoP: Form componentSymfony CoP: Form component
Symfony CoP: Form component
 
Agile data presentation 3 - cambridge
Agile data   presentation 3 - cambridgeAgile data   presentation 3 - cambridge
Agile data presentation 3 - cambridge
 
Reduxing like a pro
Reduxing like a proReduxing like a pro
Reduxing like a pro
 
WordPress REST API hacking
WordPress REST API hackingWordPress REST API hacking
WordPress REST API hacking
 
Lift 2 0
Lift 2 0Lift 2 0
Lift 2 0
 
I Phone On Rails
I Phone On RailsI Phone On Rails
I Phone On Rails
 
Advanced redux
Advanced reduxAdvanced redux
Advanced redux
 
ngMess: AngularJS Dependency Injection
ngMess: AngularJS Dependency InjectionngMess: AngularJS Dependency Injection
ngMess: AngularJS Dependency Injection
 
“iOS 11 в App in the Air”, Пронин Сергей, App in the Air
“iOS 11 в App in the Air”, Пронин Сергей, App in the Air“iOS 11 в App in the Air”, Пронин Сергей, App in the Air
“iOS 11 в App in the Air”, Пронин Сергей, App in the Air
 
Application Layer in PHP
Application Layer in PHPApplication Layer in PHP
Application Layer in PHP
 

More from Łukasz Chruściel

More from Łukasz Chruściel (20)

ConFoo 2024 - Need for Speed: Removing speed bumps in API Projects
ConFoo 2024  - Need for Speed: Removing speed bumps in API ProjectsConFoo 2024  - Need for Speed: Removing speed bumps in API Projects
ConFoo 2024 - Need for Speed: Removing speed bumps in API Projects
 
ConFoo 2024 - Sylius 2.0, top-notch eCommerce for customizable solution
ConFoo 2024 - Sylius 2.0, top-notch eCommerce for customizable solutionConFoo 2024 - Sylius 2.0, top-notch eCommerce for customizable solution
ConFoo 2024 - Sylius 2.0, top-notch eCommerce for customizable solution
 
SyliusCon - Typical pitfalls of Sylius development.pdf
SyliusCon - Typical pitfalls of Sylius development.pdfSyliusCon - Typical pitfalls of Sylius development.pdf
SyliusCon - Typical pitfalls of Sylius development.pdf
 
Need for Speed: Removing speed bumps in API Projects
Need for Speed: Removing speed bumps in API ProjectsNeed for Speed: Removing speed bumps in API Projects
Need for Speed: Removing speed bumps in API Projects
 
SymfonyLive Online 2023 - Is SOLID dead? .pdf
SymfonyLive Online 2023 - Is SOLID dead? .pdfSymfonyLive Online 2023 - Is SOLID dead? .pdf
SymfonyLive Online 2023 - Is SOLID dead? .pdf
 
Worldwide Software Architecture Summit'23 - BDD and why most of us do it wron...
Worldwide Software Architecture Summit'23 - BDD and why most of us do it wron...Worldwide Software Architecture Summit'23 - BDD and why most of us do it wron...
Worldwide Software Architecture Summit'23 - BDD and why most of us do it wron...
 
4Developers - Rozterki i decyzje.pdf
4Developers - Rozterki i decyzje.pdf4Developers - Rozterki i decyzje.pdf
4Developers - Rozterki i decyzje.pdf
 
4Developers - Sylius CRUD generation revisited.pdf
4Developers - Sylius CRUD generation revisited.pdf4Developers - Sylius CRUD generation revisited.pdf
4Developers - Sylius CRUD generation revisited.pdf
 
BoilingFrogs - Rozterki i decyzje. Czego się nauczyliśmy projektując API Syliusa
BoilingFrogs - Rozterki i decyzje. Czego się nauczyliśmy projektując API SyliusaBoilingFrogs - Rozterki i decyzje. Czego się nauczyliśmy projektując API Syliusa
BoilingFrogs - Rozterki i decyzje. Czego się nauczyliśmy projektując API Syliusa
 
What we've learned designing new Sylius API
What we've learned designing new Sylius APIWhat we've learned designing new Sylius API
What we've learned designing new Sylius API
 
How to optimize background processes.pdf
How to optimize background processes.pdfHow to optimize background processes.pdf
How to optimize background processes.pdf
 
SymfonyCon - Dilemmas and decisions..pdf
SymfonyCon - Dilemmas and decisions..pdfSymfonyCon - Dilemmas and decisions..pdf
SymfonyCon - Dilemmas and decisions..pdf
 
How to optimize background processes - when Sylius meets Blackfire
How to optimize background processes - when Sylius meets BlackfireHow to optimize background processes - when Sylius meets Blackfire
How to optimize background processes - when Sylius meets Blackfire
 
Symfony World - Symfony components and design patterns
Symfony World - Symfony components and design patternsSymfony World - Symfony components and design patterns
Symfony World - Symfony components and design patterns
 
Sylius and Api Platform The story of integration
Sylius and Api Platform The story of integrationSylius and Api Platform The story of integration
Sylius and Api Platform The story of integration
 
Dutch php a short tale about state machine
Dutch php   a short tale about state machineDutch php   a short tale about state machine
Dutch php a short tale about state machine
 
A short tale about state machine
A short tale about state machineA short tale about state machine
A short tale about state machine
 
A short tale about state machine
A short tale about state machineA short tale about state machine
A short tale about state machine
 
BDD in practice based on an open source project
BDD in practice based on an open source projectBDD in practice based on an open source project
BDD in practice based on an open source project
 
Diversified application testing based on a Sylius project
Diversified application testing based on a Sylius projectDiversified application testing based on a Sylius project
Diversified application testing based on a Sylius project
 

Recently uploaded

Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
VictoriaMetrics
 

Recently uploaded (20)

WSO2CON 2024 - Navigating API Complexity: REST, GraphQL, gRPC, Websocket, Web...
WSO2CON 2024 - Navigating API Complexity: REST, GraphQL, gRPC, Websocket, Web...WSO2CON 2024 - Navigating API Complexity: REST, GraphQL, gRPC, Websocket, Web...
WSO2CON 2024 - Navigating API Complexity: REST, GraphQL, gRPC, Websocket, Web...
 
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
 
WSO2CON 2024 - Unlocking the Identity: Embracing CIAM 2.0 for a Competitive A...
WSO2CON 2024 - Unlocking the Identity: Embracing CIAM 2.0 for a Competitive A...WSO2CON 2024 - Unlocking the Identity: Embracing CIAM 2.0 for a Competitive A...
WSO2CON 2024 - Unlocking the Identity: Embracing CIAM 2.0 for a Competitive A...
 
WSO2CON 2024 - Not Just Microservices: Rightsize Your Services!
WSO2CON 2024 - Not Just Microservices: Rightsize Your Services!WSO2CON 2024 - Not Just Microservices: Rightsize Your Services!
WSO2CON 2024 - Not Just Microservices: Rightsize Your Services!
 
WSO2CON 2024 - Cloud Native Middleware: Domain-Driven Design, Cell-Based Arch...
WSO2CON 2024 - Cloud Native Middleware: Domain-Driven Design, Cell-Based Arch...WSO2CON 2024 - Cloud Native Middleware: Domain-Driven Design, Cell-Based Arch...
WSO2CON 2024 - Cloud Native Middleware: Domain-Driven Design, Cell-Based Arch...
 
WSO2Con2024 - From Blueprint to Brilliance: WSO2's Guide to API-First Enginee...
WSO2Con2024 - From Blueprint to Brilliance: WSO2's Guide to API-First Enginee...WSO2Con2024 - From Blueprint to Brilliance: WSO2's Guide to API-First Enginee...
WSO2Con2024 - From Blueprint to Brilliance: WSO2's Guide to API-First Enginee...
 
Evolving Data Governance for the Real-time Streaming and AI Era
Evolving Data Governance for the Real-time Streaming and AI EraEvolving Data Governance for the Real-time Streaming and AI Era
Evolving Data Governance for the Real-time Streaming and AI Era
 
AzureNativeQumulo_HPC_Cloud_Native_Benchmarks.pdf
AzureNativeQumulo_HPC_Cloud_Native_Benchmarks.pdfAzureNativeQumulo_HPC_Cloud_Native_Benchmarks.pdf
AzureNativeQumulo_HPC_Cloud_Native_Benchmarks.pdf
 
WSO2CON 2024 - Designing Event-Driven Enterprises: Stories of Transformation
WSO2CON 2024 - Designing Event-Driven Enterprises: Stories of TransformationWSO2CON 2024 - Designing Event-Driven Enterprises: Stories of Transformation
WSO2CON 2024 - Designing Event-Driven Enterprises: Stories of Transformation
 
WSO2Con2024 - WSO2's IAM Vision: Identity-Led Digital Transformation
WSO2Con2024 - WSO2's IAM Vision: Identity-Led Digital TransformationWSO2Con2024 - WSO2's IAM Vision: Identity-Led Digital Transformation
WSO2Con2024 - WSO2's IAM Vision: Identity-Led Digital Transformation
 
WSO2CON 2024 - Freedom First—Unleashing Developer Potential with Open Source
WSO2CON 2024 - Freedom First—Unleashing Developer Potential with Open SourceWSO2CON 2024 - Freedom First—Unleashing Developer Potential with Open Source
WSO2CON 2024 - Freedom First—Unleashing Developer Potential with Open Source
 
WSO2CON 2024 - WSO2's Digital Transformation Journey with Choreo: A Platforml...
WSO2CON 2024 - WSO2's Digital Transformation Journey with Choreo: A Platforml...WSO2CON 2024 - WSO2's Digital Transformation Journey with Choreo: A Platforml...
WSO2CON 2024 - WSO2's Digital Transformation Journey with Choreo: A Platforml...
 
WSO2CON 2024 - Building a Digital Government in Uganda
WSO2CON 2024 - Building a Digital Government in UgandaWSO2CON 2024 - Building a Digital Government in Uganda
WSO2CON 2024 - Building a Digital Government in Uganda
 
WSO2Con2024 - GitOps in Action: Navigating Application Deployment in the Plat...
WSO2Con2024 - GitOps in Action: Navigating Application Deployment in the Plat...WSO2Con2024 - GitOps in Action: Navigating Application Deployment in the Plat...
WSO2Con2024 - GitOps in Action: Navigating Application Deployment in the Plat...
 
WSO2CON 2024 - Lessons from the Field: Legacy Platforms – It's Time to Let Go...
WSO2CON 2024 - Lessons from the Field: Legacy Platforms – It's Time to Let Go...WSO2CON 2024 - Lessons from the Field: Legacy Platforms – It's Time to Let Go...
WSO2CON 2024 - Lessons from the Field: Legacy Platforms – It's Time to Let Go...
 
WSO2Con2024 - Navigating the Digital Landscape: Transforming Healthcare with ...
WSO2Con2024 - Navigating the Digital Landscape: Transforming Healthcare with ...WSO2Con2024 - Navigating the Digital Landscape: Transforming Healthcare with ...
WSO2Con2024 - Navigating the Digital Landscape: Transforming Healthcare with ...
 
WSO2Con2024 - Simplified Integration: Unveiling the Latest Features in WSO2 L...
WSO2Con2024 - Simplified Integration: Unveiling the Latest Features in WSO2 L...WSO2Con2024 - Simplified Integration: Unveiling the Latest Features in WSO2 L...
WSO2Con2024 - Simplified Integration: Unveiling the Latest Features in WSO2 L...
 
WSO2Con2024 - Software Delivery in Hybrid Environments
WSO2Con2024 - Software Delivery in Hybrid EnvironmentsWSO2Con2024 - Software Delivery in Hybrid Environments
WSO2Con2024 - Software Delivery in Hybrid Environments
 
WSO2CON2024 - It's time to go Platformless
WSO2CON2024 - It's time to go PlatformlessWSO2CON2024 - It's time to go Platformless
WSO2CON2024 - It's time to go Platformless
 
WSO2CON2024 - Why Should You Consider Ballerina for Your Next Integration
WSO2CON2024 - Why Should You Consider Ballerina for Your Next IntegrationWSO2CON2024 - Why Should You Consider Ballerina for Your Next Integration
WSO2CON2024 - Why Should You Consider Ballerina for Your Next Integration
 

Unveiling the Future: Sylius 2.0 New Features

  • 1. Sylius 2.0 New Features Unveiling the Future Łukasz Chruściel
  • 5. Symfony Work fl ow support PriceHistory Attributes & auto con fi guration API not experimental anymore v1.13 - April 2024
  • 22. v1.14 - Autumn 2024 2.0 Compatibility layer
  • 29. * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace SyliusBundleResourceBundleController; use DoctrinePersistenceObjectManager; use FOSRestBundleViewView; use SyliusBundleResourceBundleEventResourceControllerEvent; use SyliusComponentResourceExceptionDeleteHandlingException; use SyliusComponentResourceExceptionUpdateHandlingException; use SyliusComponentResourceResourceActions; use SyliusResourceDoctrinePersistenceRepositoryInterface; use SyliusResourceFactoryFactoryInterface; use SyliusResourceMetadataMetadataInterface; use SyliusResourceModelResourceInterface; use SymfonyComponentDependencyInjectionContainerAwareTrait; use SymfonyComponentDependencyInjectionContainerInterface; use SymfonyComponentHttpFoundationRequest; use SymfonyComponentHttpFoundationResponse; use SymfonyComponentHttpKernelExceptionBadRequestHttpException; use SymfonyComponentHttpKernelExceptionHttpException; use SymfonyComponentHttpKernelExceptionNotFoundHttpException; use SymfonyComponentSecurityCoreExceptionAccessDeniedException; class ResourceController { use ControllerTrait; use ContainerAwareTrait; protected MetadataInterface $metadata; protected RequestConfigurationFactoryInterface $requestConfigurationFactory; protected ?ViewHandlerInterface $viewHandler; protected RepositoryInterface $repository; protected FactoryInterface $factory; protected NewResourceFactoryInterface $newResourceFactory; protected ObjectManager $manager; protected SingleResourceProviderInterface $singleResourceProvider; protected ResourcesCollectionProviderInterface $resourcesCollectionProvider; protected ResourceFormFactoryInterface $resourceFormFactory; protected RedirectHandlerInterface $redirectHandler; protected FlashHelperInterface $flashHelper; protected AuthorizationCheckerInterface $authorizationChecker; protected EventDispatcherInterface $eventDispatcher; protected ?StateMachineInterface $stateMachine; protected ResourceUpdateHandlerInterface $resourceUpdateHandler; protected ResourceDeleteHandlerInterface $resourceDeleteHandler; public function __construct( MetadataInterface $metadata, RequestConfigurationFactoryInterface $requestConfigurationFactory, ?ViewHandlerInterface $viewHandler, RepositoryInterface $repository, FactoryInterface $factory, NewResourceFactoryInterface $newResourceFactory, ObjectManager $manager, SingleResourceProviderInterface $singleResourceProvider, ResourcesCollectionProviderInterface $resourcesFinder, ResourceFormFactoryInterface $resourceFormFactory, RedirectHandlerInterface $redirectHandler, FlashHelperInterface $flashHelper, AuthorizationCheckerInterface $authorizationChecker, EventDispatcherInterface $eventDispatcher, ?StateMachineInterface $stateMachine, ResourceUpdateHandlerInterface $resourceUpdateHandler, ResourceDeleteHandlerInterface $resourceDeleteHandler, ) { $this->metadata = $metadata; $this->requestConfigurationFactory = $requestConfigurationFactory; $this->viewHandler = $viewHandler; $this->repository = $repository; $this->factory = $factory; $this->newResourceFactory = $newResourceFactory; $this->manager = $manager; $this->singleResourceProvider = $singleResourceProvider; $this->resourcesCollectionProvider = $resourcesFinder; $this->resourceFormFactory = $resourceFormFactory; $this->redirectHandler = $redirectHandler; $this->flashHelper = $flashHelper; $this->authorizationChecker = $authorizationChecker; $this->eventDispatcher = $eventDispatcher; $this->stateMachine = $stateMachine; $this->resourceUpdateHandler = $resourceUpdateHandler; $this->resourceDeleteHandler = $resourceDeleteHandler; } public function showAction(Request $request): Response { $configuration = $this->requestConfigurationFactory->create($this->metadata, $request); $this->isGrantedOr403($configuration, ResourceActions::SHOW); $resource = $this->findOr404($configuration); $event = $this->eventDispatcher->dispatch(ResourceActions::SHOW, $configuration, $resource); $eventResponse = $event->getResponse(); if (null !== $eventResponse) { return $eventResponse; } if ($configuration->isHtmlRequest()) { return $this->render($configuration->getTemplate(ResourceActions::SHOW . '.html'), [ 'configuration' => $configuration, 'metadata' => $this->metadata, 'resource' => $resource, $this->metadata->getName() => $resource, ]); } return $this->createRestView($configuration, $resource); } public function indexAction(Request $request): Response { $configuration = $this->requestConfigurationFactory->create($this->metadata, } return $this->createRestView($configuration, $resources); } public function createAction(Request $request): Response { $configuration = $this->requestConfigurationFactory->create($this->metadata, $request); $this->isGrantedOr403($configuration, ResourceActions::CREATE); $newResource = $this->newResourceFactory->create($configuration, $this->factory); $form = $this->resourceFormFactory->create($configuration, $newResource); $form->handleRequest($request); if ($request->isMethod('POST') && $form->isSubmitted() && $form->isValid()) { $newResource = $form->getData(); $event = $this->eventDispatcher->dispatchPreEvent(ResourceActions::CREATE, $configuration, $newResource); if ($event->isStopped() && !$configuration->isHtmlRequest()) { throw new HttpException($event->getErrorCode(), $event->getMessage()); } if ($event->isStopped()) { $this->flashHelper->addFlashFromEvent($configuration, $event); $eventResponse = $event->getResponse(); if (null !== $eventResponse) { return $eventResponse; } return $this->redirectHandler->redirectToIndex($configuration, $newResource); } if ($configuration->hasStateMachine()) { $stateMachine = $this->getStateMachine(); $stateMachine->apply($configuration, $newResource); } $this->repository->add($newResource); if ($configuration->isHtmlRequest()) { $this->flashHelper->addSuccessFlash($configuration, ResourceActions::CREATE, $newResource); } $postEvent = $this->eventDispatcher->dispatchPostEvent(ResourceActions::CREATE, $configuration, $newResource); if (!$configuration->isHtmlRequest()) { return $this->createRestView($configuration, $newResource, Response::HTTP_CREATED); } $postEventResponse = $postEvent->getResponse(); if (null !== $postEventResponse) { return $postEventResponse; } return $this->redirectHandler->redirectToResource($configuration, $newResource); } if ($request->isMethod('POST') && $form->isSubmitted() && !$form->isValid()) { $responseCode = Response::HTTP_UNPROCESSABLE_ENTITY; } if (!$configuration->isHtmlRequest()) { return $this->createRestView($configuration, $form, Response::HTTP_BAD_REQUEST); } $initializeEvent = $this->eventDispatcher- >dispatchInitializeEvent(ResourceActions::CREATE, $configuration, $newResource); $initializeEventResponse = $initializeEvent->getResponse(); if (null !== $initializeEventResponse) { return $initializeEventResponse; } return $this->render($configuration->getTemplate(ResourceActions::CREATE . '.html'), [ 'configuration' => $configuration, 'metadata' => $this->metadata, 'resource' => $newResource, $this->metadata->getName() => $newResource, 'form' => $form->createView(), ], null, $responseCode ?? Response::HTTP_OK); } public function updateAction(Request $request): Response { $configuration = $this->requestConfigurationFactory->create($this->metadata, $request); $this->isGrantedOr403($configuration, ResourceActions::UPDATE); $resource = $this->findOr404($configuration); $form = $this->resourceFormFactory->create($configuration, $resource); $form->handleRequest($request); if ( in_array($request->getMethod(), ['POST', 'PUT', 'PATCH'], true) && $form->isSubmitted() && $form->isValid() ) { $resource = $form->getData(); /** @var ResourceControllerEvent $event */ $event = $this->eventDispatcher->dispatchPreEvent(ResourceActions::UPDATE, $configuration, $resource); if ($event->isStopped() && !$configuration->isHtmlRequest()) { throw new HttpException($event->getErrorCode(), $event->getMessage()); } if ($event->isStopped()) { $this->flashHelper->addFlashFromEvent($configuration, $event); $eventResponse = $event->getResponse(); if (null !== $eventResponse) { return $eventResponse; } return $this->redirectHandler->redirectToResource($configuration, $resource); } try { $this->resourceUpdateHandler->handle($resource, $configuration, $this- >manager); } catch (UpdateHandlingException $exception) { if (!$configuration->isHtmlRequest()) { return $this->createRestView($configuration, $form, $exception- >getApiResponseCode()); return $this->createRestView($configuration, null, Response::HTTP_NO_CONTENT); } $postEventResponse = $postEvent->getResponse(); if (null !== $postEventResponse) { return $postEventResponse; } return $this->redirectHandler->redirectToResource($configuration, $resource); } if (in_array($request->getMethod(), ['POST', 'PUT', 'PATCH'], true) && $form- >isSubmitted() && !$form->isValid()) { $responseCode = Response::HTTP_UNPROCESSABLE_ENTITY; } if (!$configuration->isHtmlRequest()) { return $this->createRestView($configuration, $form, Response::HTTP_BAD_REQUEST); } $initializeEvent = $this->eventDispatcher- >dispatchInitializeEvent(ResourceActions::UPDATE, $configuration, $resource); $initializeEventResponse = $initializeEvent->getResponse(); if (null !== $initializeEventResponse) { return $initializeEventResponse; } return $this->render($configuration->getTemplate(ResourceActions::UPDATE . '.html'), [ 'configuration' => $configuration, 'metadata' => $this->metadata, 'resource' => $resource, $this->metadata->getName() => $resource, 'form' => $form->createView(), ], null, $responseCode ?? Response::HTTP_OK); } public function deleteAction(Request $request): Response { $configuration = $this->requestConfigurationFactory->create($this->metadata, $request); $this->isGrantedOr403($configuration, ResourceActions::DELETE); $resource = $this->findOr404($configuration); if ($configuration->isCsrfProtectionEnabled() && !$this->isCsrfTokenValid((string) $resource->getId(), (string) $request->request->get('_csrf_token'))) { throw new HttpException(Response::HTTP_FORBIDDEN, 'Invalid csrf token.'); } $event = $this->eventDispatcher->dispatchPreEvent(ResourceActions::DELETE, $configuration, $resource); if ($event->isStopped() && !$configuration->isHtmlRequest()) { throw new HttpException($event->getErrorCode(), $event->getMessage()); } if ($event->isStopped()) { $this->flashHelper->addFlashFromEvent($configuration, $event); $eventResponse = $event->getResponse(); if (null !== $eventResponse) { return $eventResponse; } return $this->redirectHandler->redirectToIndex($configuration, $resource); } try { $this->resourceDeleteHandler->handle($resource, $this->repository); } catch (DeleteHandlingException $exception) { if (!$configuration->isHtmlRequest()) { return $this->createRestView($configuration, null, $exception- >getApiResponseCode()); } $this->flashHelper->addErrorFlash($configuration, $exception->getFlash()); return $this->redirectHandler->redirectToReferer($configuration); } if ($configuration->isHtmlRequest()) { $this->flashHelper->addSuccessFlash($configuration, ResourceActions::DELETE, $resource); } $postEvent = $this->eventDispatcher->dispatchPostEvent(ResourceActions::DELETE, $configuration, $resource); if (!$configuration->isHtmlRequest()) { return $this->createRestView($configuration, null, Response::HTTP_NO_CONTENT); } $postEventResponse = $postEvent->getResponse(); if (null !== $postEventResponse) { return $postEventResponse; } return $this->redirectHandler->redirectToIndex($configuration, $resource); } public function bulkDeleteAction(Request $request): Response { $configuration = $this->requestConfigurationFactory->create($this->metadata, $request); $this->isGrantedOr403($configuration, ResourceActions::BULK_DELETE); $resources = $this->resourcesCollectionProvider->get($configuration, $this- >repository); if ( $configuration->isCsrfProtectionEnabled() && !$this->isCsrfTokenValid(ResourceActions::BULK_DELETE, (string) $request- >request->get('_csrf_token')) ) { throw new HttpException(Response::HTTP_FORBIDDEN, 'Invalid csrf token.'); } $this->eventDispatcher->dispatchMultiple(ResourceActions::BULK_DELETE, $configuration, $resources); foreach ($resources as $resource) { $event = $this->eventDispatcher->dispatchPreEvent(ResourceActions::DELETE, $configuration, $resource); if ($event->isStopped() && !$configuration->isHtmlRequest()) { throw new HttpException($event->getErrorCode(), $event->getMessage()); } if ($event->isStopped()) { $this->flashHelper->addFlashFromEvent($configuration, $event); $eventResponse = $event->getResponse(); if (null !== $eventResponse) { return $eventResponse; } if (!$configuration->isHtmlRequest()) { return $this->createRestView($configuration, null, Response::HTTP_NO_CONTENT); } $this->flashHelper->addSuccessFlash($configuration, ResourceActions::BULK_DELETE); if (isset($postEvent)) { $postEventResponse = $postEvent->getResponse(); if (null !== $postEventResponse) { return $postEventResponse; } } return $this->redirectHandler->redirectToIndex($configuration); } public function applyStateMachineTransitionAction(Request $request): Response { $stateMachine = $this->getStateMachine(); $configuration = $this->requestConfigurationFactory->create($this->metadata, $request); $this->isGrantedOr403($configuration, ResourceActions::UPDATE); $resource = $this->findOr404($configuration); if ($configuration->isCsrfProtectionEnabled() && !$this->isCsrfTokenValid((string) $resource->getId(), $request->get('_csrf_token'))) { throw new HttpException(Response::HTTP_FORBIDDEN, 'Invalid CSRF token.'); } $event = $this->eventDispatcher->dispatchPreEvent(ResourceActions::UPDATE, $configuration, $resource); if ($event->isStopped() && !$configuration->isHtmlRequest()) { throw new HttpException($event->getErrorCode(), $event->getMessage()); } if ($event->isStopped()) { $this->flashHelper->addFlashFromEvent($configuration, $event); $eventResponse = $event->getResponse(); if (null !== $eventResponse) { return $eventResponse; } return $this->redirectHandler->redirectToResource($configuration, $resource); } if (!$stateMachine->can($configuration, $resource)) { throw new BadRequestHttpException(); } try { $this->resourceUpdateHandler->handle($resource, $configuration, $this- >manager); } catch (UpdateHandlingException $exception) { if (!$configuration->isHtmlRequest()) { return $this->createRestView($configuration, $resource, $exception- >getApiResponseCode()); } $this->flashHelper->addErrorFlash($configuration, $exception->getFlash()); return $this->redirectHandler->redirectToReferer($configuration); } if ($configuration->isHtmlRequest()) { $this->flashHelper->addSuccessFlash($configuration, ResourceActions::UPDATE, $resource); } $postEvent = $this->eventDispatcher->dispatchPostEvent(ResourceActions::UPDATE, $configuration, $resource); if (!$configuration->isHtmlRequest()) { if ($configuration->getParameters()->get('return_content', true)) { return $this->createRestView($configuration, $resource, Response::HTTP_OK); } return $this->createRestView($configuration, null, Response::HTTP_NO_CONTENT); } $postEventResponse = $postEvent->getResponse(); if (null !== $postEventResponse) { return $postEventResponse; } return $this->redirectHandler->redirectToResource($configuration, $resource); } /** * @return mixed */ protected function getParameter(string $name) { if (!$this->container instanceof ContainerInterface) { throw new RuntimeException(sprintf( 'Container passed to "%s" has to implements "%s".', self::class, ContainerInterface::class, )); } return $this->container->getParameter($name); } /** * @throws AccessDeniedException */ protected function isGrantedOr403(RequestConfiguration $configuration, string $permission): void { if (!$configuration->hasPermission()) { return; } $permission = $configuration->getPermission($permission); if (!$this->authorizationChecker->isGranted($configuration, $permission)) { throw new AccessDeniedException(); } } /** * @throws NotFoundHttpException */ protected function findOr404(RequestConfiguration $configuration): ResourceInterface { if (null === $resource = $this->singleResourceProvider->get($configuration, $this- >repository)) { throw new NotFoundHttpException(sprintf('The "%s" has not been found', $this- >metadata->getHumanizedName())); } return $resource; } Old Architecture ResourceController -> 582+456 LoC
  • 30. New Controller <?php final class MainController { public function __construct( private HttpOperationInitiatorInterface $operationInitiator, private RequestContextInitiatorInterface $requestContextInitiator, private ProviderInterface $provider, private ProcessorInterface $processor, ) { } public function __invoke(Request $request): Response { $operation = $this->operationInitiator->initializeOperation($request); if (null === $operation) { throw new RuntimeException('Operation should not be null.'); } $context = $this->requestContextInitiator->initializeContext($request); if (null === $operation->canWrite()) { $operation = $operation->withWrite(!$request->isMethodSafe()); } $data = $this->provider->provide($operation, $context); $valid = $request->attributes->getBoolean('is_valid', true); if (!$valid) { $operation = $operation->withWrite(false); } return $this->processor->process($data, $operation, $context); } }
  • 32. Ground up rework Fully backward compatible
  • 35. Grid
  • 37. PHP Con fi g <?php return static function (GridConfig $grid) { $grid->addGrid( GridBuilder::create('app_ticket_php') ->addField(StringField::create('email')) ); };
  • 38. Service based final class TicketGrid extends AbstractGrid { public static function getName(): string { return 'app_ticket'; } public function buildGrid(GridBuilderInterface $gridBuilder): void { $gridBuilder ->addField( StringField::create('email') ) ; } }
  • 44.
  • 47.
  • 48. UI
  • 49. Admin
  • 50.
  • 51.
  • 52.
  • 55. Shop
  • 56.
  • 57. API
  • 58.
  • 60. Symfony compatibility Work fl ow vs WinzouStateMachine Clock vs Sylius/Calendar Symfony v7.0