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

Abortion Pills For Sale WhatsApp[[+27737758557]] In Birch Acres, Abortion Pil...
Abortion Pills For Sale WhatsApp[[+27737758557]] In Birch Acres, Abortion Pil...Abortion Pills For Sale WhatsApp[[+27737758557]] In Birch Acres, Abortion Pil...
Abortion Pills For Sale WhatsApp[[+27737758557]] In Birch Acres, Abortion Pil...
drm1699
 
Jax, FL Admin Community Group 05.14.2024 Combined Deck
Jax, FL Admin Community Group 05.14.2024 Combined DeckJax, FL Admin Community Group 05.14.2024 Combined Deck
Jax, FL Admin Community Group 05.14.2024 Combined Deck
Marc Lester
 
Team Transformation Tactics for Holistic Testing and Quality (NewCrafts Paris...
Team Transformation Tactics for Holistic Testing and Quality (NewCrafts Paris...Team Transformation Tactics for Holistic Testing and Quality (NewCrafts Paris...
Team Transformation Tactics for Holistic Testing and Quality (NewCrafts Paris...
Lisi Hocke
 

Recently uploaded (20)

Weeding your micro service landscape.pdf
Weeding your micro service landscape.pdfWeeding your micro service landscape.pdf
Weeding your micro service landscape.pdf
 
Novo Nordisk: When Knowledge Graphs meet LLMs
Novo Nordisk: When Knowledge Graphs meet LLMsNovo Nordisk: When Knowledge Graphs meet LLMs
Novo Nordisk: When Knowledge Graphs meet LLMs
 
A Deep Dive into Secure Product Development Frameworks.pdf
A Deep Dive into Secure Product Development Frameworks.pdfA Deep Dive into Secure Product Development Frameworks.pdf
A Deep Dive into Secure Product Development Frameworks.pdf
 
CERVED e Neo4j su una nuvola, migrazione ed evoluzione di un grafo mission cr...
CERVED e Neo4j su una nuvola, migrazione ed evoluzione di un grafo mission cr...CERVED e Neo4j su una nuvola, migrazione ed evoluzione di un grafo mission cr...
CERVED e Neo4j su una nuvola, migrazione ed evoluzione di un grafo mission cr...
 
Your Ultimate Web Studio for Streaming Anywhere | Evmux
Your Ultimate Web Studio for Streaming Anywhere | EvmuxYour Ultimate Web Studio for Streaming Anywhere | Evmux
Your Ultimate Web Studio for Streaming Anywhere | Evmux
 
Abortion Clinic In Springs ](+27832195400*)[ 🏥 Safe Abortion Pills in Springs...
Abortion Clinic In Springs ](+27832195400*)[ 🏥 Safe Abortion Pills in Springs...Abortion Clinic In Springs ](+27832195400*)[ 🏥 Safe Abortion Pills in Springs...
Abortion Clinic In Springs ](+27832195400*)[ 🏥 Safe Abortion Pills in Springs...
 
Abortion Pill Prices Germiston ](+27832195400*)[ 🏥 Women's Abortion Clinic in...
Abortion Pill Prices Germiston ](+27832195400*)[ 🏥 Women's Abortion Clinic in...Abortion Pill Prices Germiston ](+27832195400*)[ 🏥 Women's Abortion Clinic in...
Abortion Pill Prices Germiston ](+27832195400*)[ 🏥 Women's Abortion Clinic in...
 
Wired_2.0_CREATE YOUR ULTIMATE LEARNING ENVIRONMENT_JCON_16052024
Wired_2.0_CREATE YOUR ULTIMATE LEARNING ENVIRONMENT_JCON_16052024Wired_2.0_CREATE YOUR ULTIMATE LEARNING ENVIRONMENT_JCON_16052024
Wired_2.0_CREATE YOUR ULTIMATE LEARNING ENVIRONMENT_JCON_16052024
 
Prompt Engineering - an Art, a Science, or your next Job Title?
Prompt Engineering - an Art, a Science, or your next Job Title?Prompt Engineering - an Art, a Science, or your next Job Title?
Prompt Engineering - an Art, a Science, or your next Job Title?
 
Abortion Pill Prices Mthatha (@](+27832195400*)[ 🏥 Women's Abortion Clinic In...
Abortion Pill Prices Mthatha (@](+27832195400*)[ 🏥 Women's Abortion Clinic In...Abortion Pill Prices Mthatha (@](+27832195400*)[ 🏥 Women's Abortion Clinic In...
Abortion Pill Prices Mthatha (@](+27832195400*)[ 🏥 Women's Abortion Clinic In...
 
Encryption Recap: A Refresher on Key Concepts
Encryption Recap: A Refresher on Key ConceptsEncryption Recap: A Refresher on Key Concepts
Encryption Recap: A Refresher on Key Concepts
 
Abortion Pills For Sale WhatsApp[[+27737758557]] In Birch Acres, Abortion Pil...
Abortion Pills For Sale WhatsApp[[+27737758557]] In Birch Acres, Abortion Pil...Abortion Pills For Sale WhatsApp[[+27737758557]] In Birch Acres, Abortion Pil...
Abortion Pills For Sale WhatsApp[[+27737758557]] In Birch Acres, Abortion Pil...
 
[GeeCON2024] How I learned to stop worrying and love the dark silicon apocalypse
[GeeCON2024] How I learned to stop worrying and love the dark silicon apocalypse[GeeCON2024] How I learned to stop worrying and love the dark silicon apocalypse
[GeeCON2024] How I learned to stop worrying and love the dark silicon apocalypse
 
The Evolution of Web App Testing_ An Ultimate Guide to Future Trends.pdf
The Evolution of Web App Testing_ An Ultimate Guide to Future Trends.pdfThe Evolution of Web App Testing_ An Ultimate Guide to Future Trends.pdf
The Evolution of Web App Testing_ An Ultimate Guide to Future Trends.pdf
 
Jax, FL Admin Community Group 05.14.2024 Combined Deck
Jax, FL Admin Community Group 05.14.2024 Combined DeckJax, FL Admin Community Group 05.14.2024 Combined Deck
Jax, FL Admin Community Group 05.14.2024 Combined Deck
 
Anypoint Code Builder - Munich MuleSoft Meetup - 16th May 2024
Anypoint Code Builder - Munich MuleSoft Meetup - 16th May 2024Anypoint Code Builder - Munich MuleSoft Meetup - 16th May 2024
Anypoint Code Builder - Munich MuleSoft Meetup - 16th May 2024
 
Auto Affiliate AI Earns First Commission in 3 Hours..pdf
Auto Affiliate  AI Earns First Commission in 3 Hours..pdfAuto Affiliate  AI Earns First Commission in 3 Hours..pdf
Auto Affiliate AI Earns First Commission in 3 Hours..pdf
 
architecting-ai-in-the-enterprise-apis-and-applications.pdf
architecting-ai-in-the-enterprise-apis-and-applications.pdfarchitecting-ai-in-the-enterprise-apis-and-applications.pdf
architecting-ai-in-the-enterprise-apis-and-applications.pdf
 
Abortion Pill Prices Jane Furse ](+27832195400*)[ 🏥 Women's Abortion Clinic i...
Abortion Pill Prices Jane Furse ](+27832195400*)[ 🏥 Women's Abortion Clinic i...Abortion Pill Prices Jane Furse ](+27832195400*)[ 🏥 Women's Abortion Clinic i...
Abortion Pill Prices Jane Furse ](+27832195400*)[ 🏥 Women's Abortion Clinic i...
 
Team Transformation Tactics for Holistic Testing and Quality (NewCrafts Paris...
Team Transformation Tactics for Holistic Testing and Quality (NewCrafts Paris...Team Transformation Tactics for Holistic Testing and Quality (NewCrafts Paris...
Team Transformation Tactics for Holistic Testing and Quality (NewCrafts Paris...
 

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